<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>iOS » MszPro・株式会社Smartソフト</title>
	<atom:link href="https://mszpro.com/category/ios/feed" rel="self" type="application/rss+xml" />
	<link>https://mszpro.com</link>
	<description>iOS VisionOS SwiftUI Programming Blog. Dream it, Chase it, Code it.</description>
	<lastBuildDate>Wed, 18 Dec 2024 07:10:26 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.1</generator>

<image>
	<url>https://static-assets.mszpro.com/2024/12/cropped-Unknown-32x32.webp</url>
	<title>iOS » MszPro・株式会社Smartソフト</title>
	<link>https://mszpro.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Adapt to physical camera control button within your own iOS app (for SwiftUI, Zoom, Exposure, &#038; Custom Controls)</title>
		<link>https://mszpro.com/ios-camera-control-button</link>
		
		<dc:creator><![CDATA[msz]]></dc:creator>
		<pubDate>Wed, 18 Dec 2024 07:04:47 +0000</pubDate>
				<category><![CDATA[iOS]]></category>
		<category><![CDATA[iOS 18]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<guid isPermaLink="false">https://mszpro.com/?p=541</guid>

					<description><![CDATA[<p>Use the physical camera button within your own iOS app to allow user to take pictures, use zoom and exposure control, and add your own control options (WWDC 2024, AVCaptureSessionControlsDelegate, AVCaptureSystemZoomSlider, AVCaptureSystemExposureBiasSlider, AVCaptureSlider, AVCaptureIndexPicker)</p>
<p>The post <a href="https://mszpro.com/ios-camera-control-button">Adapt to physical camera control button within your own iOS app (for SwiftUI, Zoom, Exposure, & Custom Controls)</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>This article talks about adapting the new physical camera button within your iOS app.</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="502" src="https://static-assets.mszpro.com/2024/12/time-travel-with-mszpro-1024x502.png" alt="" class="wp-image-553" srcset="https://static-assets.mszpro.com/2024/12/time-travel-with-mszpro-300x147.png 300w, https://static-assets.mszpro.com/2024/12/time-travel-with-mszpro-1024x502.png 1024w, https://static-assets.mszpro.com/2024/12/time-travel-with-mszpro-768x376.png 768w, https://static-assets.mszpro.com/2024/12/time-travel-with-mszpro-1536x753.png 1536w, https://static-assets.mszpro.com/2024/12/time-travel-with-mszpro-2048x1004.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="502" src="https://static-assets.mszpro.com/2024/12/qiita-switch-1-1024x502.png" alt="" class="wp-image-554" srcset="https://static-assets.mszpro.com/2024/12/qiita-switch-1-300x147.png 300w, https://static-assets.mszpro.com/2024/12/qiita-switch-1-1024x502.png 1024w, https://static-assets.mszpro.com/2024/12/qiita-switch-1-768x376.png 768w, https://static-assets.mszpro.com/2024/12/qiita-switch-1-1536x753.png 1536w, https://static-assets.mszpro.com/2024/12/qiita-switch-1-2048x1004.png 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h1 class="wp-block-heading">Why?</h1>



<p>If you do nothing, and when the user presses the camera button within your app, it always goes to the system Camera app. However, with some minor adjustments, user can use the camera button to take pictures within your own app, and your app can provide custom controls to the camera, for example, allow the user to change the filter by swiping on the physical camera button.</p>



<p>Also, you can add cool new control options, like shown in the above 2 screenshots.</p>



<p>Let&#8217;s get started!</p>



<h1 class="wp-block-heading">Starting point</h1>



<p>We will start from a very simple SwiftUI view that shows a camera and a capture button:</p>



<p>This is the view model that manages the permission to access camera, and take actions when we need to take a picture, and receives the image and saves it to a variable.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="// MARK: - Unified Camera ViewModel
class CameraViewModel: NSObject, ObservableObject {
    
    // Session states
    enum CameraSetupState {
        case idle
        case configured
        case permissionDenied
        case failed
    }
    
    @Published var setupState: CameraSetupState = .idle
    @Published var capturedPhoto: UIImage? = nil
    @Published var permissionGranted: Bool = false
    
    let session = AVCaptureSession()
    private let photoOutput = AVCapturePhotoOutput()
    private var videoInput: AVCaptureDeviceInput?
    
    // Dispatch queue for configuring the session
    private let configurationQueue = DispatchQueue(label: &quot;com.example.camera.config&quot;)
    
    override init() {
        super.init()
    }
    
    deinit {
        stopSession()
    }
    
    // MARK: - Public API
    
    /// Checks camera permissions and configures session if authorized.
    func requestAccessIfNeeded() {
        let authStatus = AVCaptureDevice.authorizationStatus(for: .video)
        switch authStatus {
            case .authorized:
                permissionGranted = true
                configureSessionIfIdle()
            case .notDetermined:
                AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
                    guard let self = self else { return }
                    DispatchQueue.main.async {
                        if granted {
                            self.permissionGranted = true
                            self.configureSessionIfIdle()
                        } else {
                            self.setupState = .permissionDenied
                        }
                    }
                }
            default:
                // Denied or Restricted
                setupState = .permissionDenied
        }
    }
    
    /// Initiate photo capture.
    func capturePhoto() {
        guard setupState == .configured else { return }
        let settings = AVCapturePhotoSettings()
        photoOutput.capturePhoto(with: settings, delegate: self)
    }
    
    // MARK: - Session Configuration
    
    private func configureSessionIfIdle() {
        configurationQueue.async { [weak self] in
            guard let self = self, self.setupState == .idle else { return }
            
            self.session.beginConfiguration()
            self.session.sessionPreset = .photo
            
            self.addCameraInput()
            self.addPhotoOutput()
            
            self.session.commitConfiguration()
            self.startSessionIfReady()
        }
    }
    
    private func addCameraInput() {
        do {
            guard let backCamera = AVCaptureDevice.default(.builtInWideAngleCamera,
                                                           for: .video,
                                                           position: .back) else {
                print(&quot;CameraViewModel: Back camera is unavailable.&quot;)
                setupState = .idle
                session.commitConfiguration()
                return
            }
            
            let cameraInput = try AVCaptureDeviceInput(device: backCamera)
            if session.canAddInput(cameraInput) {
                session.addInput(cameraInput)
                videoInput = cameraInput
                DispatchQueue.main.async {
                    self.setupState = .configured
                }
            } else {
                print(&quot;CameraViewModel: Unable to add camera input to session.&quot;)
                setupState = .idle
                session.commitConfiguration()
            }
        } catch {
            print(&quot;CameraViewModel: Error creating camera input - \(error)&quot;)
            setupState = .failed
            session.commitConfiguration()
        }
    }
    
    private func addPhotoOutput() {
        guard session.canAddOutput(photoOutput) else {
            print(&quot;CameraViewModel: Cannot add photo output.&quot;)
            setupState = .failed
            session.commitConfiguration()
            return
        }
        session.addOutput(photoOutput)
        photoOutput.maxPhotoQualityPrioritization = .quality
        DispatchQueue.main.async {
            self.setupState = .configured
        }
    }
    
    private func startSessionIfReady() {
        guard setupState == .configured else { return }
        session.startRunning()
    }
    
    private func stopSession() {
        configurationQueue.async { [weak self] in
            guard let self = self else { return }
            if self.session.isRunning {
                self.session.stopRunning()
            }
        }
    }
}

// MARK: - AVCapturePhotoCaptureDelegate
extension CameraViewModel: AVCapturePhotoCaptureDelegate {
    func photoOutput(_ output: AVCapturePhotoOutput,
                     didFinishProcessingPhoto photo: AVCapturePhoto,
                     error: Error?) {
        
        guard error == nil else {
            print(&quot;CameraViewModel: Error capturing photo - \(error!)&quot;)
            return
        }
        guard let photoData = photo.fileDataRepresentation() else {
            print(&quot;CameraViewModel: No photo data found.&quot;)
            return
        }
        self.capturedPhoto = UIImage(data: photoData)
    }
}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// MARK: - Unified Camera ViewModel</span></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">CameraViewModel</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">NSObject</span><span style="color: #D8DEE9FF">, </span><span style="color: #8FBCBB; font-weight: bold">ObservableObject </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// Session states</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">enum</span><span style="color: #D8DEE9FF"> CameraSetupState </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">idle</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">configured</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">permissionDenied</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">failed</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">@</span><span style="color: #81A1C1">Published</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> setupState: CameraSetupState </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">idle</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">@</span><span style="color: #81A1C1">Published</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> capturedPhoto: UIImage</span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">@</span><span style="color: #81A1C1">Published</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> permissionGranted: </span><span style="color: #8FBCBB">Bool</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> session </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCaptureSession</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> photoOutput </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCapturePhotoOutput</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> videoInput: AVCaptureDeviceInput</span><span style="color: #81A1C1">?</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// Dispatch queue for configuring the session</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> configurationQueue </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">DispatchQueue</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">label</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">com.example.camera.config</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">override</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">init</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">super</span><span style="color: #D8DEE9FF">.</span><span style="color: #81A1C1">init</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">deinit</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">stopSession</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// MARK: - Public API</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">/// Checks camera permissions and configures session if authorized.</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">requestAccessIfNeeded</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> authStatus </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> AVCaptureDevice.</span><span style="color: #88C0D0">authorizationStatus</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">for</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">video</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">switch</span><span style="color: #D8DEE9FF"> authStatus </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">authorized</span><span style="color: #81A1C1">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">                permissionGranted </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">configureSessionIfIdle</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">notDetermined</span><span style="color: #81A1C1">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">                AVCaptureDevice.</span><span style="color: #88C0D0">requestAccess</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">for</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">video</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> [</span><span style="color: #81A1C1">weak</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">] granted </span><span style="color: #81A1C1">in</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    DispatchQueue.</span><span style="color: #D8DEE9">main</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">async</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> granted </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">permissionGranted</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">                            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">configureSessionIfIdle</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">setupState</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">permissionDenied</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">default:</span></span>
<span class="line"><span style="color: #ECEFF4">                </span><span style="color: #616E88">// Denied or Restricted</span></span>
<span class="line"><span style="color: #D8DEE9FF">                setupState </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">permissionDenied</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">/// Initiate photo capture.</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">capturePhoto</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> setupState </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> .configured </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> settings </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCapturePhotoSettings</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        photoOutput.</span><span style="color: #88C0D0">capturePhoto</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">with</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> settings, </span><span style="color: #88C0D0">delegate</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// MARK: - Session Configuration</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">configureSessionIfIdle</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        configurationQueue.</span><span style="color: #88C0D0">async</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> [</span><span style="color: #81A1C1">weak</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">] </span><span style="color: #81A1C1">in</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">, </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.setupState </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> .idle </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">beginConfiguration</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">sessionPreset</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">photo</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">addCameraInput</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">addPhotoOutput</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">commitConfiguration</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">startSessionIfReady</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">addCameraInput</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">do</span><span style="color: #ECEFF4"> {</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> backCamera </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> AVCaptureDevice.</span><span style="color: #88C0D0">default</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">builtInWideAngleCamera</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                                           </span><span style="color: #88C0D0">for</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">video</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                                           </span><span style="color: #88C0D0">position</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">back</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">CameraViewModel: Back camera is unavailable.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                setupState </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">idle</span></span>
<span class="line"><span style="color: #D8DEE9FF">                session.</span><span style="color: #88C0D0">commitConfiguration</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> cameraInput </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCaptureDeviceInput</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">device</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> backCamera</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> session.</span><span style="color: #88C0D0">canAddInput</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">cameraInput</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                session.</span><span style="color: #88C0D0">addInput</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">cameraInput</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                videoInput </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> cameraInput</span></span>
<span class="line"><span style="color: #D8DEE9FF">                DispatchQueue.</span><span style="color: #D8DEE9">main</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">async</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">setupState</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">configured</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">CameraViewModel: Unable to add camera input to session.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                setupState </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">idle</span></span>
<span class="line"><span style="color: #D8DEE9FF">                session.</span><span style="color: #88C0D0">commitConfiguration</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">CameraViewModel: Error creating camera input - </span><span style="color: #81A1C1">\(</span><span style="color: #A3BE8C">error</span><span style="color: #81A1C1">)</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            setupState </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">failed</span></span>
<span class="line"><span style="color: #D8DEE9FF">            session.</span><span style="color: #88C0D0">commitConfiguration</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">addPhotoOutput</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> session.</span><span style="color: #88C0D0">canAddOutput</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">photoOutput</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">CameraViewModel: Cannot add photo output.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            setupState </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">failed</span></span>
<span class="line"><span style="color: #D8DEE9FF">            session.</span><span style="color: #88C0D0">commitConfiguration</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        session.</span><span style="color: #88C0D0">addOutput</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">photoOutput</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        photoOutput.</span><span style="color: #D8DEE9">maxPhotoQualityPrioritization</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">quality</span></span>
<span class="line"><span style="color: #D8DEE9FF">        DispatchQueue.</span><span style="color: #D8DEE9">main</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">async</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">setupState</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">configured</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">startSessionIfReady</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> setupState </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> .configured </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        session.</span><span style="color: #88C0D0">startRunning</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">stopSession</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        configurationQueue.</span><span style="color: #88C0D0">async</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> [</span><span style="color: #81A1C1">weak</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">] </span><span style="color: #81A1C1">in</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.session.isRunning </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">stopRunning</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// MARK: - AVCapturePhotoCaptureDelegate</span></span>
<span class="line"><span style="color: #81A1C1">extension</span><span style="color: #D8DEE9FF"> CameraViewModel</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">AVCapturePhotoCaptureDelegate </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">photoOutput</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">output</span><span style="color: #D8DEE9FF">: AVCapturePhotoOutput,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                     </span><span style="color: #88C0D0">didFinishProcessingPhoto</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">photo</span><span style="color: #D8DEE9FF">: AVCapturePhoto,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                     </span><span style="color: #88C0D0">error</span><span style="color: #D8DEE9FF">: </span><span style="color: #8FBCBB">Error</span><span style="color: #81A1C1">?</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> error </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">CameraViewModel: Error capturing photo - </span><span style="color: #81A1C1">\(</span><span style="color: #A3BE8C">error</span><span style="color: #81A1C1">!)</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> photoData </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> photo.</span><span style="color: #88C0D0">fileDataRepresentation</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">CameraViewModel: No photo data found.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">capturedPhoto</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">UIImage</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">data</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> photoData</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p>This is our UIKit compatible view so we can show the camera preview layer within SwiftUI:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="// MARK: - SwiftUI Representable for Camera Preview
struct CameraLayerView: UIViewRepresentable {
    
    let cameraSession: AVCaptureSession
    
    func makeUIView(context: Context) -&gt; CameraContainerView {
        let container = CameraContainerView()
        container.backgroundColor = .black
        container.previewLayer.session = cameraSession
        container.previewLayer.videoGravity = .resizeAspect
        return container
    }
    
    func updateUIView(_ uiView: CameraContainerView, context: Context) {
        // No dynamic updates needed
    }
    
    // A UIView subclass that hosts an AVCaptureVideoPreviewLayer
    class CameraContainerView: UIView {
        
        override class var layerClass: AnyClass {
            AVCaptureVideoPreviewLayer.self
        }
        
        var previewLayer: AVCaptureVideoPreviewLayer {
            guard let layer = self.layer as? AVCaptureVideoPreviewLayer else {
                fatalError(&quot;CameraContainerView: Failed casting layer to AVCaptureVideoPreviewLayer.&quot;)
            }
            return layer
        }
    }
}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// MARK: - SwiftUI Representable for Camera Preview</span></span>
<span class="line"><span style="color: #81A1C1">struct</span><span style="color: #D8DEE9FF"> CameraLayerView</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">UIViewRepresentable </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> cameraSession: AVCaptureSession</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">makeUIView</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">context</span><span style="color: #D8DEE9FF">: Context</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9FF"> CameraContainerView </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> container </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">CameraContainerView</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        container.</span><span style="color: #D8DEE9">backgroundColor</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">black</span></span>
<span class="line"><span style="color: #D8DEE9FF">        container.</span><span style="color: #D8DEE9">previewLayer</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> cameraSession</span></span>
<span class="line"><span style="color: #D8DEE9FF">        container.</span><span style="color: #D8DEE9">previewLayer</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">videoGravity</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">resizeAspect</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> container</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">updateUIView</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">uiView</span><span style="color: #D8DEE9FF">: CameraContainerView, </span><span style="color: #88C0D0">context</span><span style="color: #D8DEE9FF">: Context</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// No dynamic updates needed</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// A UIView subclass that hosts an AVCaptureVideoPreviewLayer</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">CameraContainerView</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">UIView </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">override</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> layerClass: </span><span style="color: #8FBCBB">AnyClass</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            AVCaptureVideoPreviewLayer.</span><span style="color: #81A1C1">self</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> previewLayer: AVCaptureVideoPreviewLayer </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> layer </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.layer </span><span style="color: #81A1C1">as?</span><span style="color: #D8DEE9FF"> AVCaptureVideoPreviewLayer </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">fatalError</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">CameraContainerView: Failed casting layer to AVCaptureVideoPreviewLayer.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> layer</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p>And this is our main SwiftUI view:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="// MARK: - SwiftUI Main View
struct ContentView: View {
    
    @ObservedObject var viewModel = CameraViewModel()
    
    var body: some View {
        GeometryReader { _ in
            ZStack(alignment: .bottom) {
                CameraLayerView(cameraSession: viewModel.session)
                    .onAppear {
                        viewModel.requestAccessIfNeeded()
                    }
                    .edgesIgnoringSafeArea(.all)
                
                // Capture button
                VStack {
                    Spacer()
                    
                    Button {
                        viewModel.capturePhoto()
                    } label: {
                        Text(&quot;Take Photo&quot;)
                            .font(.headline)
                            .foregroundColor(.white)
                            .padding()
                            .background(Color.blue)
                            .cornerRadius(10)
                    }
                    .padding(.bottom, 20)
                }
                
                // Thumbnail of the captured photo
                if let image = viewModel.capturedPhoto {
                    Image(uiImage: image)
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: 120, height: 90)
                        .padding(.bottom, 80)
                }
            }
        }
    }
}

// MARK: - SwiftUI Preview
#Preview {
    ContentView()
}
" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// MARK: - SwiftUI Main View</span></span>
<span class="line"><span style="color: #81A1C1">struct</span><span style="color: #D8DEE9FF"> ContentView</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">View </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">@</span><span style="color: #81A1C1">ObservedObject</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> viewModel </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">CameraViewModel</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> body: </span><span style="color: #81A1C1">some</span><span style="color: #D8DEE9FF"> View </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">GeometryReader</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> _ </span><span style="color: #81A1C1">in</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">ZStack</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">alignment</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">bottom</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">CameraLayerView</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">cameraSession</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> viewModel.</span><span style="color: #D8DEE9">session</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    .</span><span style="color: #88C0D0">onAppear</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        viewModel.</span><span style="color: #88C0D0">requestAccessIfNeeded</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    .</span><span style="color: #88C0D0">edgesIgnoringSafeArea</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">all</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span></span>
<span class="line"><span style="color: #ECEFF4">                </span><span style="color: #616E88">// Capture button</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">VStack</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #88C0D0">Spacer</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #88C0D0">Button</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        viewModel.</span><span style="color: #88C0D0">capturePhoto</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">label</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #88C0D0">Text</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Take Photo</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                            .</span><span style="color: #88C0D0">font</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">headline</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                            .</span><span style="color: #88C0D0">foregroundColor</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">white</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                            .</span><span style="color: #88C0D0">padding</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">                            .</span><span style="color: #88C0D0">background</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">Color.</span><span style="color: #D8DEE9">blue</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                            .</span><span style="color: #88C0D0">cornerRadius</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">10</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    .</span><span style="color: #88C0D0">padding</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">bottom</span><span style="color: #D8DEE9FF">, </span><span style="color: #B48EAD">20</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span></span>
<span class="line"><span style="color: #ECEFF4">                </span><span style="color: #616E88">// Thumbnail of the captured photo</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> image </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> viewModel.capturedPhoto </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #88C0D0">Image</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">uiImage</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> image</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        .</span><span style="color: #88C0D0">resizable</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        .</span><span style="color: #88C0D0">aspectRatio</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">contentMode</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">fit</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        .</span><span style="color: #88C0D0">frame</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">width</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">120</span><span style="color: #D8DEE9FF">, </span><span style="color: #88C0D0">height</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">90</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        .</span><span style="color: #88C0D0">padding</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">bottom</span><span style="color: #D8DEE9FF">, </span><span style="color: #B48EAD">80</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// MARK: - SwiftUI Preview</span></span>
<span class="line"><span style="color: #88C0D0">#Preview</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">ContentView</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<h1 class="wp-block-heading">Capture picture when pressing camera button</h1>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="575" src="https://static-assets.mszpro.com/2024/12/60989-125717-60951-125647-888-Camera-Control-xl-xl-1024x575.jpg" alt="" class="wp-image-542" srcset="https://static-assets.mszpro.com/2024/12/60989-125717-60951-125647-888-Camera-Control-xl-xl-300x169.jpg 300w, https://static-assets.mszpro.com/2024/12/60989-125717-60951-125647-888-Camera-Control-xl-xl-1024x575.jpg 1024w, https://static-assets.mszpro.com/2024/12/60989-125717-60951-125647-888-Camera-Control-xl-xl-768x431.jpg 768w, https://static-assets.mszpro.com/2024/12/60989-125717-60951-125647-888-Camera-Control-xl-xl.jpg 1280w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>As we talked about, if your app does not adapt this, when the user presses on the camera button, it will jump to the camera app.</p>



<p>To allow your app to handle the take picture action, simply import the <code>AVKit</code> framework and add a view modifier <code>onCameraCaptureEvent()</code> view modifier to your camera preview layer <code>CameraLayerView</code>:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="CameraLayerView(cameraSession: viewModel.session)
    .onAppear {
        viewModel.requestAccessIfNeeded()
    }
    .edgesIgnoringSafeArea(.all)
    .onCameraCaptureEvent() { event in
        if event.phase == .began {
            self.viewModel.capturePhoto()
        }
    }" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #88C0D0">CameraLayerView</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">cameraSession</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> viewModel.</span><span style="color: #D8DEE9">session</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    .</span><span style="color: #88C0D0">onAppear</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        viewModel.</span><span style="color: #88C0D0">requestAccessIfNeeded</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    .</span><span style="color: #88C0D0">edgesIgnoringSafeArea</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">all</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    .</span><span style="color: #88C0D0">onCameraCaptureEvent</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> event </span><span style="color: #81A1C1">in</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> event.phase </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> .began </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">viewModel</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">capturePhoto</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p>Now, whenever the user presses the camera button on the side, it will call the code block you provided, which you can call the capture photo function within your view model.</p>



<h1 class="wp-block-heading">Checking support</h1>



<p>You can check if the user&#8217;s device has support for camera button by checking the <code>supportsControls</code> parameter within your camera session <code>AVCaptureSession</code>:</p>



<p>In our provided starting point code, you can call it like this</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="struct ContentView: View {
    
    @ObservedObject var viewModel = CameraViewModel()
    
    var body: some View {
        GeometryReader { _ in
            // ... //
        }
        .task {
            let supportsCameraButton = self.viewModel.session.supportsControls
            
        }
    }
}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">struct</span><span style="color: #D8DEE9FF"> ContentView</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">View </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">@</span><span style="color: #81A1C1">ObservedObject</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> viewModel </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">CameraViewModel</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> body: </span><span style="color: #81A1C1">some</span><span style="color: #D8DEE9FF"> View </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">GeometryReader</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> _ </span><span style="color: #81A1C1">in</span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// ... //</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        .</span><span style="color: #88C0D0">task</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> supportsCameraButton </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">viewModel</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">supportsControls</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h1 class="wp-block-heading">Add zoom control</h1>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://static-assets.mszpro.com/2024/12/iphone-16-capture-button-1024x576.jpg" alt="" class="wp-image-544" srcset="https://static-assets.mszpro.com/2024/12/iphone-16-capture-button-300x169.jpg 300w, https://static-assets.mszpro.com/2024/12/iphone-16-capture-button-1024x576.jpg 1024w, https://static-assets.mszpro.com/2024/12/iphone-16-capture-button-768x432.jpg 768w, https://static-assets.mszpro.com/2024/12/iphone-16-capture-button-1536x864.jpg 1536w, https://static-assets.mszpro.com/2024/12/iphone-16-capture-button.jpg 1600w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>We can easily add a control for the zoom level. What&#8217;s great is that iOS system handles the zoom automatically, and your app get notified the zoom level to be shown in your own UI:</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="471" height="1024" src="https://static-assets.mszpro.com/2024/12/IMG_3276-471x1024.png" alt="" class="wp-image-545" srcset="https://static-assets.mszpro.com/2024/12/IMG_3276-138x300.png 138w, https://static-assets.mszpro.com/2024/12/IMG_3276-471x1024.png 471w, https://static-assets.mszpro.com/2024/12/IMG_3276-768x1669.png 768w, https://static-assets.mszpro.com/2024/12/IMG_3276-707x1536.png 707w, https://static-assets.mszpro.com/2024/12/IMG_3276-943x2048.png 943w, https://static-assets.mszpro.com/2024/12/IMG_3276.png 1320w" sizes="auto, (max-width: 471px) 100vw, 471px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="471" height="1024" src="https://static-assets.mszpro.com/2024/12/IMG_3277-471x1024.png" alt="" class="wp-image-546" srcset="https://static-assets.mszpro.com/2024/12/IMG_3277-138x300.png 138w, https://static-assets.mszpro.com/2024/12/IMG_3277-471x1024.png 471w, https://static-assets.mszpro.com/2024/12/IMG_3277-768x1669.png 768w, https://static-assets.mszpro.com/2024/12/IMG_3277-707x1536.png 707w, https://static-assets.mszpro.com/2024/12/IMG_3277-943x2048.png 943w, https://static-assets.mszpro.com/2024/12/IMG_3277.png 1320w" sizes="auto, (max-width: 471px) 100vw, 471px" /></figure>
</div>
</div>



<p>To get started, first, set a camera control delegate to your camera session:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="self.session.setControlsDelegate(self, queue: self.cameraControlQueue)" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">setControlsDelegate</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">, </span><span style="color: #88C0D0">queue</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">cameraControlQueue</span><span style="color: #ECEFF4">)</span></span></code></pre></div>



<p>Now, conform your class to <code>AVCaptureSessionControlsDelegate</code> and add the required functions.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="// MARK: - AVCaptureSessionControlsDelegate
extension CameraViewModel: AVCaptureSessionControlsDelegate {
    
    func sessionControlsDidBecomeActive(_ session: AVCaptureSession) {
        return
    }
    
    func sessionControlsWillEnterFullscreenAppearance(_ session: AVCaptureSession) {
        return
    }
    
    func sessionControlsWillExitFullscreenAppearance(_ session: AVCaptureSession) {
        return
    }
    
    func sessionControlsDidBecomeInactive(_ session: AVCaptureSession) {
        return
    }
    
}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// MARK: - AVCaptureSessionControlsDelegate</span></span>
<span class="line"><span style="color: #81A1C1">extension</span><span style="color: #D8DEE9FF"> CameraViewModel</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">AVCaptureSessionControlsDelegate </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sessionControlsDidBecomeActive</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">: AVCaptureSession</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sessionControlsWillEnterFullscreenAppearance</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">: AVCaptureSession</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sessionControlsWillExitFullscreenAppearance</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">: AVCaptureSession</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sessionControlsDidBecomeInactive</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">: AVCaptureSession</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p>Even if you do nothing in the above functions, you have to implement the delegate in order to use the camera control features.</p>



<p>Now, we will initialize the system zoom control:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="let systemZoomSlider = AVCaptureSystemZoomSlider(device: backCamera) { zoomFactor in
    // Calculate and display a zoom value.
    let displayZoom = backCamera.displayVideoZoomFactorMultiplier * zoomFactor
    // Update the user interface.
    print(displayZoom)
}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> systemZoomSlider </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCaptureSystemZoomSlider</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">device</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> backCamera</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> zoomFactor </span><span style="color: #81A1C1">in</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// Calculate and display a zoom value.</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> displayZoom </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> backCamera.</span><span style="color: #D8DEE9">displayVideoZoomFactorMultiplier</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> zoomFactor</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// Update the user interface.</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">displayZoom</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p>Here, we provide the camera device as the input. Within the code block, the system will run our code whenever the zoom level changes.</p>



<p>We will then remove all existing controls of the camera, and add the system zoom slider:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="/// remove existing camera controls first
self.session.controls.forEach({ self.session.removeControl($0) })

/// add new ones
let controlsToAdd: [AVCaptureControl] = [systemZoomSlider]

for control in controlsToAdd {
    if self.session.canAddControl(control) {
        self.session.addControl(control)
    }
}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">/// remove existing camera controls first</span></span>
<span class="line"><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">controls</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">forEach</span><span style="color: #ECEFF4">({</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">removeControl</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$0</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">})</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">/// add new ones</span></span>
<span class="line"><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> controlsToAdd: </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF">AVCaptureControl</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> [systemZoomSlider]</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">for</span><span style="color: #D8DEE9FF"> control </span><span style="color: #81A1C1">in</span><span style="color: #D8DEE9FF"> controlsToAdd </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.session.</span><span style="color: #88C0D0">canAddControl</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">control</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">addControl</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">control</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p>Here is my updated <code>CameraViewModel</code></p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="// MARK: - Unified Camera ViewModel
class CameraViewModel: NSObject, ObservableObject {
    
    // Session states
    enum CameraSetupState {
        case idle
        case configured
        case permissionDenied
        case failed
    }
    
    @Published var setupState: CameraSetupState = .idle
    @Published var capturedPhoto: UIImage? = nil
    @Published var permissionGranted: Bool = false
    
    let session = AVCaptureSession()
    private let photoOutput = AVCapturePhotoOutput()
    private var videoInput: AVCaptureDeviceInput?
    
    // Dispatch queue for configuring the session
    private let configurationQueue = DispatchQueue(label: &quot;com.example.camera.config&quot;)
    private let cameraControlQueue = DispatchQueue(label: &quot;com.example.camera.control&quot;)
    
    override init() {
        super.init()
    }
    
    deinit {
        stopSession()
    }
    
    // MARK: - Public API
    
    /// Checks camera permissions and configures session if authorized.
    func requestAccessIfNeeded() { ... }
    
    /// Initiate photo capture.
    func capturePhoto() { ... }
    
    // MARK: - Session Configuration
    
    private func configureSessionIfIdle() { ... }
    
    private func addCameraInput() {
        do {
            guard let backCamera = AVCaptureDevice.default(.builtInWideAngleCamera,
                                                           for: .video,
                                                           position: .back) else {
                print(&quot;CameraViewModel: Back camera is unavailable.&quot;)
                setupState = .idle
                session.commitConfiguration()
                return
            }
            
            let cameraInput = try AVCaptureDeviceInput(device: backCamera)
            if session.canAddInput(cameraInput) {
                session.addInput(cameraInput)
                videoInput = cameraInput
                DispatchQueue.main.async {
                    self.setupState = .configured
                }
            } else {
                print(&quot;CameraViewModel: Unable to add camera input to session.&quot;)
                setupState = .idle
                session.commitConfiguration()
            }
            
            // configure for camera control button
            let systemZoomSlider = AVCaptureSystemZoomSlider(device: backCamera) { zoomFactor in
                // Calculate and display a zoom value.
                let displayZoom = backCamera.displayVideoZoomFactorMultiplier * zoomFactor
                // Update the user interface.
                print(displayZoom)
            }
            
            /// remove existing camera controls first
            self.session.controls.forEach({ self.session.removeControl($0) })
            
            /// add new ones
            let controlsToAdd: [AVCaptureControl] = [systemZoomSlider]
            
            for control in controlsToAdd {
                if self.session.canAddControl(control) {
                    self.session.addControl(control)
                }
            }
            
            /// set delegate
            self.session.setControlsDelegate(self, queue: self.cameraControlQueue)
            //
        } catch {
            print(&quot;CameraViewModel: Error creating camera input - \(error)&quot;)
            setupState = .failed
            session.commitConfiguration()
        }
    }
    
    private func addPhotoOutput() { ... }
    
    private func startSessionIfReady() { ... }
    
    private func stopSession() { ... }
}

// MARK: - AVCaptureSessionControlsDelegate
extension CameraViewModel: AVCaptureSessionControlsDelegate {
    
    func sessionControlsDidBecomeActive(_ session: AVCaptureSession) {
        return
    }
    
    func sessionControlsWillEnterFullscreenAppearance(_ session: AVCaptureSession) {
        return
    }
    
    func sessionControlsWillExitFullscreenAppearance(_ session: AVCaptureSession) {
        return
    }
    
    func sessionControlsDidBecomeInactive(_ session: AVCaptureSession) {
        return
    }
    
}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// MARK: - Unified Camera ViewModel</span></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">CameraViewModel</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">NSObject</span><span style="color: #D8DEE9FF">, </span><span style="color: #8FBCBB; font-weight: bold">ObservableObject </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// Session states</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">enum</span><span style="color: #D8DEE9FF"> CameraSetupState </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">idle</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">configured</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">permissionDenied</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">failed</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">@</span><span style="color: #81A1C1">Published</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> setupState: CameraSetupState </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">idle</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">@</span><span style="color: #81A1C1">Published</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> capturedPhoto: UIImage</span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">@</span><span style="color: #81A1C1">Published</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> permissionGranted: </span><span style="color: #8FBCBB">Bool</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> session </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCaptureSession</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> photoOutput </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCapturePhotoOutput</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> videoInput: AVCaptureDeviceInput</span><span style="color: #81A1C1">?</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// Dispatch queue for configuring the session</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> configurationQueue </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">DispatchQueue</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">label</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">com.example.camera.config</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> cameraControlQueue </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">DispatchQueue</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">label</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">com.example.camera.control</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">override</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">init</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">super</span><span style="color: #D8DEE9FF">.</span><span style="color: #81A1C1">init</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">deinit</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">stopSession</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// MARK: - Public API</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">/// Checks camera permissions and configures session if authorized.</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">requestAccessIfNeeded</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">/// Initiate photo capture.</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">capturePhoto</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// MARK: - Session Configuration</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">configureSessionIfIdle</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">addCameraInput</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">do</span><span style="color: #ECEFF4"> {</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> backCamera </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> AVCaptureDevice.</span><span style="color: #88C0D0">default</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">builtInWideAngleCamera</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                                           </span><span style="color: #88C0D0">for</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">video</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                                           </span><span style="color: #88C0D0">position</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">back</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">CameraViewModel: Back camera is unavailable.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                setupState </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">idle</span></span>
<span class="line"><span style="color: #D8DEE9FF">                session.</span><span style="color: #88C0D0">commitConfiguration</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> cameraInput </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCaptureDeviceInput</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">device</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> backCamera</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> session.</span><span style="color: #88C0D0">canAddInput</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">cameraInput</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                session.</span><span style="color: #88C0D0">addInput</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">cameraInput</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                videoInput </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> cameraInput</span></span>
<span class="line"><span style="color: #D8DEE9FF">                DispatchQueue.</span><span style="color: #D8DEE9">main</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">async</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">setupState</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">configured</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">CameraViewModel: Unable to add camera input to session.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                setupState </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">idle</span></span>
<span class="line"><span style="color: #D8DEE9FF">                session.</span><span style="color: #88C0D0">commitConfiguration</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// configure for camera control button</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> systemZoomSlider </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCaptureSystemZoomSlider</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">device</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> backCamera</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> zoomFactor </span><span style="color: #81A1C1">in</span></span>
<span class="line"><span style="color: #ECEFF4">                </span><span style="color: #616E88">// Calculate and display a zoom value.</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> displayZoom </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> backCamera.</span><span style="color: #D8DEE9">displayVideoZoomFactorMultiplier</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> zoomFactor</span></span>
<span class="line"><span style="color: #ECEFF4">                </span><span style="color: #616E88">// Update the user interface.</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">displayZoom</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">/// remove existing camera controls first</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">controls</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">forEach</span><span style="color: #ECEFF4">({</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">removeControl</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$0</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">})</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">/// add new ones</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> controlsToAdd: </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF">AVCaptureControl</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> [systemZoomSlider]</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">for</span><span style="color: #D8DEE9FF"> control </span><span style="color: #81A1C1">in</span><span style="color: #D8DEE9FF"> controlsToAdd </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.session.</span><span style="color: #88C0D0">canAddControl</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">control</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">addControl</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">control</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">/// set delegate</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">setControlsDelegate</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">, </span><span style="color: #88C0D0">queue</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">cameraControlQueue</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">//</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">CameraViewModel: Error creating camera input - </span><span style="color: #81A1C1">\(</span><span style="color: #A3BE8C">error</span><span style="color: #81A1C1">)</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            setupState </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">failed</span></span>
<span class="line"><span style="color: #D8DEE9FF">            session.</span><span style="color: #88C0D0">commitConfiguration</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">addPhotoOutput</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">startSessionIfReady</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">stopSession</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">...</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// MARK: - AVCaptureSessionControlsDelegate</span></span>
<span class="line"><span style="color: #81A1C1">extension</span><span style="color: #D8DEE9FF"> CameraViewModel</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">AVCaptureSessionControlsDelegate </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sessionControlsDidBecomeActive</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">: AVCaptureSession</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sessionControlsWillEnterFullscreenAppearance</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">: AVCaptureSession</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sessionControlsWillExitFullscreenAppearance</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">: AVCaptureSession</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sessionControlsDidBecomeInactive</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">: AVCaptureSession</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p>Notice that I have added a <code>cameraControlQueue</code>, set the delegate, and set the system zoom slider within my <code>addCameraInput</code> function.</p>



<p>Now, if you run your program on your iPhone, you will notice that you can use the zoom slider within your own app:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="320" height="695" src="https://static-assets.mszpro.com/2024/12/ezgif-4-b3e64c3ed3.gif" alt="" class="wp-image-547"/></figure>



<h1 class="wp-block-heading">Add exposure control</h1>



<p>You can easily add control for exposure by adding one more camera control to the array:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="// configure for camera control button
let systemZoomSlider = AVCaptureSystemZoomSlider(device: backCamera) { zoomFactor in
    // Calculate and display a zoom value.
    let displayZoom = backCamera.displayVideoZoomFactorMultiplier * zoomFactor
    // Update the user interface.
    print(displayZoom)
}

let systemBiasSlider = AVCaptureSystemExposureBiasSlider(device: backCamera)

/// remove existing camera controls first
self.session.controls.forEach({ self.session.removeControl($0) })

/// add new ones
let controlsToAdd: [AVCaptureControl] = [systemZoomSlider, systemBiasSlider]

for control in controlsToAdd {
    if self.session.canAddControl(control) {
        self.session.addControl(control)
    }
}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// configure for camera control button</span></span>
<span class="line"><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> systemZoomSlider </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCaptureSystemZoomSlider</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">device</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> backCamera</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> zoomFactor </span><span style="color: #81A1C1">in</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// Calculate and display a zoom value.</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> displayZoom </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> backCamera.</span><span style="color: #D8DEE9">displayVideoZoomFactorMultiplier</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> zoomFactor</span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// Update the user interface.</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">displayZoom</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> systemBiasSlider </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCaptureSystemExposureBiasSlider</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">device</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> backCamera</span><span style="color: #ECEFF4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">/// remove existing camera controls first</span></span>
<span class="line"><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">controls</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">forEach</span><span style="color: #ECEFF4">({</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">removeControl</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$0</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">})</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">/// add new ones</span></span>
<span class="line"><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> controlsToAdd: </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF">AVCaptureControl</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> [systemZoomSlider, systemBiasSlider]</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">for</span><span style="color: #D8DEE9FF"> control </span><span style="color: #81A1C1">in</span><span style="color: #D8DEE9FF"> controlsToAdd </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.session.</span><span style="color: #88C0D0">canAddControl</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">control</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">addControl</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">control</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p>Now, when you launch the app and quickly double tap (not press) on the camera button, you will see 2 options, and you can switch to the exposure control (which is a slider provided by the system)</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="471" height="1024" src="https://static-assets.mszpro.com/2024/12/IMG_3280-471x1024.png" alt="" class="wp-image-548" srcset="https://static-assets.mszpro.com/2024/12/IMG_3280-138x300.png 138w, https://static-assets.mszpro.com/2024/12/IMG_3280-471x1024.png 471w, https://static-assets.mszpro.com/2024/12/IMG_3280-768x1669.png 768w, https://static-assets.mszpro.com/2024/12/IMG_3280-707x1536.png 707w, https://static-assets.mszpro.com/2024/12/IMG_3280-943x2048.png 943w, https://static-assets.mszpro.com/2024/12/IMG_3280.png 1320w" sizes="auto, (max-width: 471px) 100vw, 471px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="471" height="1024" src="https://static-assets.mszpro.com/2024/12/IMG_3279-471x1024.png" alt="" class="wp-image-549" srcset="https://static-assets.mszpro.com/2024/12/IMG_3279-138x300.png 138w, https://static-assets.mszpro.com/2024/12/IMG_3279-471x1024.png 471w, https://static-assets.mszpro.com/2024/12/IMG_3279-768x1669.png 768w, https://static-assets.mszpro.com/2024/12/IMG_3279-707x1536.png 707w, https://static-assets.mszpro.com/2024/12/IMG_3279-943x2048.png 943w, https://static-assets.mszpro.com/2024/12/IMG_3279.png 1320w" sizes="auto, (max-width: 471px) 100vw, 471px" /></figure>
</div>
</div>



<h1 class="wp-block-heading">Add custom slider</h1>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="502" src="https://static-assets.mszpro.com/2024/12/time-travel-with-mszpro-1024x502.png" alt="" class="wp-image-551" srcset="https://static-assets.mszpro.com/2024/12/time-travel-with-mszpro-300x147.png 300w, https://static-assets.mszpro.com/2024/12/time-travel-with-mszpro-1024x502.png 1024w, https://static-assets.mszpro.com/2024/12/time-travel-with-mszpro-768x376.png 768w, https://static-assets.mszpro.com/2024/12/time-travel-with-mszpro-1536x753.png 1536w, https://static-assets.mszpro.com/2024/12/time-travel-with-mszpro-2048x1004.png 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>You can initialize a custom slider, and use <code>setActionQueue</code> to get notified when the value changes.</p>



<p>Here is a little joke where you control the time (4th dimension( with my camera app (fancy right?)</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="let timeTravelSlider = AVCaptureSlider(&quot;MszProと時間旅行&quot;, symbolName: &quot;pawprint.fill&quot;, in: -10...10)
// Perform the slider's action on the session queue.
timeTravelSlider.setActionQueue(self.cameraControlQueue) { position in
    print(position)
}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> timeTravelSlider </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCaptureSlider</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">MszProと時間旅行</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">, </span><span style="color: #88C0D0">symbolName</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">pawprint.fill</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">, </span><span style="color: #88C0D0">in</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">-10</span><span style="color: #81A1C1">...</span><span style="color: #B48EAD">10</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #616E88">// Perform the slider&#39;s action on the session queue.</span></span>
<span class="line"><span style="color: #D8DEE9FF">timeTravelSlider.</span><span style="color: #88C0D0">setActionQueue</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">cameraControlQueue</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> position </span><span style="color: #81A1C1">in</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">position</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<h1 class="wp-block-heading">Add custom picker</h1>



<p>You can also allow the user to pick one of the many given options (for example, a list of filters)</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="502" src="https://static-assets.mszpro.com/2024/12/qiita-switch-1024x502.png" alt="" class="wp-image-552" srcset="https://static-assets.mszpro.com/2024/12/qiita-switch-300x147.png 300w, https://static-assets.mszpro.com/2024/12/qiita-switch-1024x502.png 1024w, https://static-assets.mszpro.com/2024/12/qiita-switch-768x376.png 768w, https://static-assets.mszpro.com/2024/12/qiita-switch-1536x753.png 1536w, https://static-assets.mszpro.com/2024/12/qiita-switch-2048x1004.png 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="let indexPicker = AVCaptureIndexPicker(&quot;Post to&quot;,
                                       symbolName: &quot;square.and.arrow.up&quot;,
                                       localizedIndexTitles: [
                                        &quot;Qiita&quot;,
                                        &quot;Twitter&quot;,
                                        &quot;SoraSNS&quot;
                                       ])
indexPicker.setActionQueue(self.cameraControlQueue) { value in
    print(value)
}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> indexPicker </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCaptureIndexPicker</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Post to</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                       </span><span style="color: #88C0D0">symbolName</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">square.and.arrow.up</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                       </span><span style="color: #88C0D0">localizedIndexTitles</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> [</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Qiita</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Twitter</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                        </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">SoraSNS</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                       ]</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">indexPicker.</span><span style="color: #88C0D0">setActionQueue</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">cameraControlQueue</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> value </span><span style="color: #81A1C1">in</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">value</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p>Notice, the value you get within the function is an index number.</p>



<h2 class="wp-block-heading">That is how you do it!</h2>



<p>Did you learn how to add custom camera control options within your own app? Here is the complete code:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="import SwiftUI
import AVFoundation
import Combine
import AVKit

// MARK: - SwiftUI Representable for Camera Preview
struct CameraLayerView: UIViewRepresentable {
    
    let cameraSession: AVCaptureSession
    
    func makeUIView(context: Context) -&gt; CameraContainerView {
        let container = CameraContainerView()
        container.backgroundColor = .black
        container.previewLayer.session = cameraSession
        container.previewLayer.videoGravity = .resizeAspect
        return container
    }
    
    func updateUIView(_ uiView: CameraContainerView, context: Context) {
        // No dynamic updates needed
    }
    
    // A UIView subclass that hosts an AVCaptureVideoPreviewLayer
    class CameraContainerView: UIView {
        
        override class var layerClass: AnyClass {
            AVCaptureVideoPreviewLayer.self
        }
        
        var previewLayer: AVCaptureVideoPreviewLayer {
            guard let layer = self.layer as? AVCaptureVideoPreviewLayer else {
                fatalError(&quot;CameraContainerView: Failed casting layer to AVCaptureVideoPreviewLayer.&quot;)
            }
            return layer
        }
    }
}

// MARK: - Unified Camera ViewModel
class CameraViewModel: NSObject, ObservableObject {
    
    // Session states
    enum CameraSetupState {
        case idle
        case configured
        case permissionDenied
        case failed
    }
    
    @Published var setupState: CameraSetupState = .idle
    @Published var capturedPhoto: UIImage? = nil
    @Published var permissionGranted: Bool = false
    
    let session = AVCaptureSession()
    private let photoOutput = AVCapturePhotoOutput()
    private var videoInput: AVCaptureDeviceInput?
    
    // Dispatch queue for configuring the session
    private let configurationQueue = DispatchQueue(label: &quot;com.example.camera.config&quot;)
    private let cameraControlQueue = DispatchQueue(label: &quot;com.example.camera.control&quot;)
    
    override init() {
        super.init()
    }
    
    deinit {
        stopSession()
    }
    
    // MARK: - Public API
    
    /// Checks camera permissions and configures session if authorized.
    func requestAccessIfNeeded() {
        let authStatus = AVCaptureDevice.authorizationStatus(for: .video)
        switch authStatus {
            case .authorized:
                permissionGranted = true
                configureSessionIfIdle()
            case .notDetermined:
                AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
                    guard let self = self else { return }
                    DispatchQueue.main.async {
                        if granted {
                            self.permissionGranted = true
                            self.configureSessionIfIdle()
                        } else {
                            self.setupState = .permissionDenied
                        }
                    }
                }
            default:
                // Denied or Restricted
                setupState = .permissionDenied
        }
    }
    
    /// Initiate photo capture.
    func capturePhoto() {
        guard setupState == .configured else { return }
        let settings = AVCapturePhotoSettings()
        photoOutput.capturePhoto(with: settings, delegate: self)
    }
    
    // MARK: - Session Configuration
    
    private func configureSessionIfIdle() {
        configurationQueue.async { [weak self] in
            guard let self = self, self.setupState == .idle else { return }
            
            self.session.beginConfiguration()
            
            self.session.sessionPreset = .photo
            
            self.addCameraInput()
            self.addPhotoOutput()
            
            // save configuration and start camera session
            self.session.commitConfiguration()
            self.startSessionIfReady()
        }
    }
    
    private func addCameraInput() {
        do {
            guard let backCamera = AVCaptureDevice.default(.builtInWideAngleCamera,
                                                           for: .video,
                                                           position: .back) else {
                print(&quot;CameraViewModel: Back camera is unavailable.&quot;)
                setupState = .idle
                session.commitConfiguration()
                return
            }
            
            let cameraInput = try AVCaptureDeviceInput(device: backCamera)
            if session.canAddInput(cameraInput) {
                session.addInput(cameraInput)
                videoInput = cameraInput
                DispatchQueue.main.async {
                    self.setupState = .configured
                }
            } else {
                print(&quot;CameraViewModel: Unable to add camera input to session.&quot;)
                setupState = .idle
                session.commitConfiguration()
            }
            
            // configure for camera control button
            
            /// zoom slider
            let systemZoomSlider = AVCaptureSystemZoomSlider(device: backCamera) { zoomFactor in
                // Calculate and display a zoom value.
                let displayZoom = backCamera.displayVideoZoomFactorMultiplier * zoomFactor
                // Update the user interface.
                print(displayZoom)
            }
            
            /// exposure slider
            let systemBiasSlider = AVCaptureSystemExposureBiasSlider(device: backCamera)
            
            /// custom slider, learn time travel with MszPro
            let timeTravelSlider = AVCaptureSlider(&quot;MszProと時間旅行&quot;, symbolName: &quot;pawprint.fill&quot;, in: -10...10)
            // Perform the slider's action on the session queue.
            timeTravelSlider.setActionQueue(self.cameraControlQueue) { position in
                print(position)
            }
            
            /// custom index picker
            let indexPicker = AVCaptureIndexPicker(&quot;Post to&quot;,
                                                   symbolName: &quot;square.and.arrow.up&quot;,
                                                   localizedIndexTitles: [
                                                    &quot;Qiita&quot;,
                                                    &quot;Twitter&quot;,
                                                    &quot;SoraSNS&quot;
                                                   ])
            indexPicker.setActionQueue(self.cameraControlQueue) { value in
                print(value)
            }
            
            /// remove existing camera controls first
            self.session.controls.forEach({ self.session.removeControl($0) })
            
            /// add new ones
            let controlsToAdd: [AVCaptureControl] = [systemZoomSlider, systemBiasSlider, timeTravelSlider, indexPicker]
            
            for control in controlsToAdd {
                if self.session.canAddControl(control) {
                    self.session.addControl(control)
                }
            }
            
            /// set delegate
            self.session.setControlsDelegate(self, queue: self.cameraControlQueue)
            //
        } catch {
            print(&quot;CameraViewModel: Error creating camera input - \(error)&quot;)
            setupState = .failed
            session.commitConfiguration()
        }
    }
    
    private func addPhotoOutput() {
        guard session.canAddOutput(photoOutput) else {
            print(&quot;CameraViewModel: Cannot add photo output.&quot;)
            setupState = .failed
            session.commitConfiguration()
            return
        }
        session.addOutput(photoOutput)
        photoOutput.maxPhotoQualityPrioritization = .quality
        DispatchQueue.main.async {
            self.setupState = .configured
        }
    }
    
    private func startSessionIfReady() {
        guard setupState == .configured else { return }
        session.startRunning()
    }
    
    private func stopSession() {
        configurationQueue.async { [weak self] in
            guard let self = self else { return }
            if self.session.isRunning {
                self.session.stopRunning()
            }
        }
    }
}

// MARK: - AVCaptureSessionControlsDelegate
extension CameraViewModel: AVCaptureSessionControlsDelegate {
    
    func sessionControlsDidBecomeActive(_ session: AVCaptureSession) {
        return
    }
    
    func sessionControlsWillEnterFullscreenAppearance(_ session: AVCaptureSession) {
        return
    }
    
    func sessionControlsWillExitFullscreenAppearance(_ session: AVCaptureSession) {
        return
    }
    
    func sessionControlsDidBecomeInactive(_ session: AVCaptureSession) {
        return
    }
    
}

// MARK: - AVCapturePhotoCaptureDelegate
extension CameraViewModel: AVCapturePhotoCaptureDelegate {
    func photoOutput(_ output: AVCapturePhotoOutput,
                     didFinishProcessingPhoto photo: AVCapturePhoto,
                     error: Error?) {
        
        guard error == nil else {
            print(&quot;CameraViewModel: Error capturing photo - \(error!)&quot;)
            return
        }
        guard let photoData = photo.fileDataRepresentation() else {
            print(&quot;CameraViewModel: No photo data found.&quot;)
            return
        }
        self.capturedPhoto = UIImage(data: photoData)
    }
}

// MARK: - SwiftUI Main View
struct ContentView: View {
    
    @ObservedObject var viewModel = CameraViewModel()
    
    var body: some View {
        GeometryReader { _ in
            ZStack(alignment: .bottom) {
                CameraLayerView(cameraSession: viewModel.session)
                    .onAppear {
                        viewModel.requestAccessIfNeeded()
                    }
                    .edgesIgnoringSafeArea(.all)
                    .onCameraCaptureEvent() { event in
                        if event.phase == .began {
                            self.viewModel.capturePhoto()
                        }
                    }
                
                // Capture button
                VStack {
                    Spacer()
                    
                    Button {
                        viewModel.capturePhoto()
                    } label: {
                        Text(&quot;Take Photo&quot;)
                            .font(.headline)
                            .foregroundColor(.white)
                            .padding()
                            .background(Color.blue)
                            .cornerRadius(10)
                    }
                    .padding(.bottom, 20)
                }
                
                // Thumbnail of the captured photo
                if let image = viewModel.capturedPhoto {
                    Image(uiImage: image)
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: 120, height: 90)
                        .padding(.bottom, 80)
                }
            }
        }
        .task {
            let supportsCameraButton = self.viewModel.session.supportsControls
            
        }
    }
}

// MARK: - SwiftUI Preview
#Preview {
    ContentView()
}
" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> SwiftUI</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> AVFoundation</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> Combine</span></span>
<span class="line"><span style="color: #81A1C1">import</span><span style="color: #D8DEE9FF"> AVKit</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// MARK: - SwiftUI Representable for Camera Preview</span></span>
<span class="line"><span style="color: #81A1C1">struct</span><span style="color: #D8DEE9FF"> CameraLayerView</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">UIViewRepresentable </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> cameraSession: AVCaptureSession</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">makeUIView</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">context</span><span style="color: #D8DEE9FF">: Context</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9FF"> CameraContainerView </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> container </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">CameraContainerView</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        container.</span><span style="color: #D8DEE9">backgroundColor</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">black</span></span>
<span class="line"><span style="color: #D8DEE9FF">        container.</span><span style="color: #D8DEE9">previewLayer</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> cameraSession</span></span>
<span class="line"><span style="color: #D8DEE9FF">        container.</span><span style="color: #D8DEE9">previewLayer</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">videoGravity</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">resizeAspect</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> container</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">updateUIView</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">uiView</span><span style="color: #D8DEE9FF">: CameraContainerView, </span><span style="color: #88C0D0">context</span><span style="color: #D8DEE9FF">: Context</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">        </span><span style="color: #616E88">// No dynamic updates needed</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// A UIView subclass that hosts an AVCaptureVideoPreviewLayer</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">CameraContainerView</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">UIView </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">override</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> layerClass: </span><span style="color: #8FBCBB">AnyClass</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            AVCaptureVideoPreviewLayer.</span><span style="color: #81A1C1">self</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> previewLayer: AVCaptureVideoPreviewLayer </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> layer </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.layer </span><span style="color: #81A1C1">as?</span><span style="color: #D8DEE9FF"> AVCaptureVideoPreviewLayer </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">fatalError</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">CameraContainerView: Failed casting layer to AVCaptureVideoPreviewLayer.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> layer</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// MARK: - Unified Camera ViewModel</span></span>
<span class="line"><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">CameraViewModel</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">NSObject</span><span style="color: #D8DEE9FF">, </span><span style="color: #8FBCBB; font-weight: bold">ObservableObject </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// Session states</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">enum</span><span style="color: #D8DEE9FF"> CameraSetupState </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">idle</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">configured</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">permissionDenied</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">failed</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">@</span><span style="color: #81A1C1">Published</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> setupState: CameraSetupState </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">idle</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">@</span><span style="color: #81A1C1">Published</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> capturedPhoto: UIImage</span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">nil</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">@</span><span style="color: #81A1C1">Published</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> permissionGranted: </span><span style="color: #8FBCBB">Bool</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">false</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> session </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCaptureSession</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> photoOutput </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCapturePhotoOutput</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> videoInput: AVCaptureDeviceInput</span><span style="color: #81A1C1">?</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// Dispatch queue for configuring the session</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> configurationQueue </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">DispatchQueue</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">label</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">com.example.camera.config</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> cameraControlQueue </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">DispatchQueue</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">label</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">com.example.camera.control</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">override</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">init</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">super</span><span style="color: #D8DEE9FF">.</span><span style="color: #81A1C1">init</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">deinit</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">stopSession</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// MARK: - Public API</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">/// Checks camera permissions and configures session if authorized.</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">requestAccessIfNeeded</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> authStatus </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> AVCaptureDevice.</span><span style="color: #88C0D0">authorizationStatus</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">for</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">video</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">switch</span><span style="color: #D8DEE9FF"> authStatus </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">authorized</span><span style="color: #81A1C1">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">                permissionGranted </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">configureSessionIfIdle</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">case</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">notDetermined</span><span style="color: #81A1C1">:</span></span>
<span class="line"><span style="color: #D8DEE9FF">                AVCaptureDevice.</span><span style="color: #88C0D0">requestAccess</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">for</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">video</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> [</span><span style="color: #81A1C1">weak</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">] granted </span><span style="color: #81A1C1">in</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    DispatchQueue.</span><span style="color: #D8DEE9">main</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">async</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> granted </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">permissionGranted</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">                            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">configureSessionIfIdle</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">setupState</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">permissionDenied</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">default:</span></span>
<span class="line"><span style="color: #ECEFF4">                </span><span style="color: #616E88">// Denied or Restricted</span></span>
<span class="line"><span style="color: #D8DEE9FF">                setupState </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">permissionDenied</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">/// Initiate photo capture.</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">capturePhoto</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> setupState </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> .configured </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> settings </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCapturePhotoSettings</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        photoOutput.</span><span style="color: #88C0D0">capturePhoto</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">with</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> settings, </span><span style="color: #88C0D0">delegate</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// MARK: - Session Configuration</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">configureSessionIfIdle</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        configurationQueue.</span><span style="color: #88C0D0">async</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> [</span><span style="color: #81A1C1">weak</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">] </span><span style="color: #81A1C1">in</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">, </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.setupState </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> .idle </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">beginConfiguration</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">sessionPreset</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">photo</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">addCameraInput</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">addPhotoOutput</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// save configuration and start camera session</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">commitConfiguration</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">startSessionIfReady</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">addCameraInput</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">do</span><span style="color: #ECEFF4"> {</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> backCamera </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> AVCaptureDevice.</span><span style="color: #88C0D0">default</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">builtInWideAngleCamera</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                                           </span><span style="color: #88C0D0">for</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">video</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                                           </span><span style="color: #88C0D0">position</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">back</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">CameraViewModel: Back camera is unavailable.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                setupState </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">idle</span></span>
<span class="line"><span style="color: #D8DEE9FF">                session.</span><span style="color: #88C0D0">commitConfiguration</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> cameraInput </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">try</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCaptureDeviceInput</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">device</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> backCamera</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> session.</span><span style="color: #88C0D0">canAddInput</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">cameraInput</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                session.</span><span style="color: #88C0D0">addInput</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">cameraInput</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                videoInput </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> cameraInput</span></span>
<span class="line"><span style="color: #D8DEE9FF">                DispatchQueue.</span><span style="color: #D8DEE9">main</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">async</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">setupState</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">configured</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">CameraViewModel: Unable to add camera input to session.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                setupState </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">idle</span></span>
<span class="line"><span style="color: #D8DEE9FF">                session.</span><span style="color: #88C0D0">commitConfiguration</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// configure for camera control button</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">/// zoom slider</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> systemZoomSlider </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCaptureSystemZoomSlider</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">device</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> backCamera</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> zoomFactor </span><span style="color: #81A1C1">in</span></span>
<span class="line"><span style="color: #ECEFF4">                </span><span style="color: #616E88">// Calculate and display a zoom value.</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> displayZoom </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> backCamera.</span><span style="color: #D8DEE9">displayVideoZoomFactorMultiplier</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF"> zoomFactor</span></span>
<span class="line"><span style="color: #ECEFF4">                </span><span style="color: #616E88">// Update the user interface.</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">displayZoom</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">/// exposure slider</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> systemBiasSlider </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCaptureSystemExposureBiasSlider</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">device</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> backCamera</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">/// custom slider, learn time travel with MszPro</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> timeTravelSlider </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCaptureSlider</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">MszProと時間旅行</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">, </span><span style="color: #88C0D0">symbolName</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">pawprint.fill</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">, </span><span style="color: #88C0D0">in</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">-10</span><span style="color: #81A1C1">...</span><span style="color: #B48EAD">10</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// Perform the slider&#39;s action on the session queue.</span></span>
<span class="line"><span style="color: #D8DEE9FF">            timeTravelSlider.</span><span style="color: #88C0D0">setActionQueue</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">cameraControlQueue</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> position </span><span style="color: #81A1C1">in</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">position</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">/// custom index picker</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> indexPicker </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">AVCaptureIndexPicker</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Post to</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                                   </span><span style="color: #88C0D0">symbolName</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">square.and.arrow.up</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                                   </span><span style="color: #88C0D0">localizedIndexTitles</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> [</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                                    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Qiita</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                                    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Twitter</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                                    </span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">SoraSNS</span><span style="color: #ECEFF4">&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                                   ]</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            indexPicker.</span><span style="color: #88C0D0">setActionQueue</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">cameraControlQueue</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> value </span><span style="color: #81A1C1">in</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">value</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">/// remove existing camera controls first</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">controls</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">forEach</span><span style="color: #ECEFF4">({</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">removeControl</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">$0</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">})</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">/// add new ones</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> controlsToAdd: </span><span style="color: #ECEFF4">[</span><span style="color: #D8DEE9FF">AVCaptureControl</span><span style="color: #ECEFF4">]</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> [systemZoomSlider, systemBiasSlider, timeTravelSlider, indexPicker]</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">for</span><span style="color: #D8DEE9FF"> control </span><span style="color: #81A1C1">in</span><span style="color: #D8DEE9FF"> controlsToAdd </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.session.</span><span style="color: #88C0D0">canAddControl</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">control</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">addControl</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">control</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">/// set delegate</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">setControlsDelegate</span><span style="color: #ECEFF4">(</span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">, </span><span style="color: #88C0D0">queue</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">cameraControlQueue</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">//</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">catch</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">CameraViewModel: Error creating camera input - </span><span style="color: #81A1C1">\(</span><span style="color: #A3BE8C">error</span><span style="color: #81A1C1">)</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            setupState </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">failed</span></span>
<span class="line"><span style="color: #D8DEE9FF">            session.</span><span style="color: #88C0D0">commitConfiguration</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">addPhotoOutput</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> session.</span><span style="color: #88C0D0">canAddOutput</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">photoOutput</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">CameraViewModel: Cannot add photo output.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            setupState </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">failed</span></span>
<span class="line"><span style="color: #D8DEE9FF">            session.</span><span style="color: #88C0D0">commitConfiguration</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        session.</span><span style="color: #88C0D0">addOutput</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">photoOutput</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        photoOutput.</span><span style="color: #D8DEE9">maxPhotoQualityPrioritization</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">quality</span></span>
<span class="line"><span style="color: #D8DEE9FF">        DispatchQueue.</span><span style="color: #D8DEE9">main</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">async</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">setupState</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">configured</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">startSessionIfReady</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> setupState </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> .configured </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        session.</span><span style="color: #88C0D0">startRunning</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">stopSession</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        configurationQueue.</span><span style="color: #88C0D0">async</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> [</span><span style="color: #81A1C1">weak</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">] </span><span style="color: #81A1C1">in</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.session.isRunning </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">stopRunning</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// MARK: - AVCaptureSessionControlsDelegate</span></span>
<span class="line"><span style="color: #81A1C1">extension</span><span style="color: #D8DEE9FF"> CameraViewModel</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">AVCaptureSessionControlsDelegate </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sessionControlsDidBecomeActive</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">: AVCaptureSession</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sessionControlsWillEnterFullscreenAppearance</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">: AVCaptureSession</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sessionControlsWillExitFullscreenAppearance</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">: AVCaptureSession</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">sessionControlsDidBecomeInactive</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">: AVCaptureSession</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// MARK: - AVCapturePhotoCaptureDelegate</span></span>
<span class="line"><span style="color: #81A1C1">extension</span><span style="color: #D8DEE9FF"> CameraViewModel</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">AVCapturePhotoCaptureDelegate </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">photoOutput</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">output</span><span style="color: #D8DEE9FF">: AVCapturePhotoOutput,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                     </span><span style="color: #88C0D0">didFinishProcessingPhoto</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">photo</span><span style="color: #D8DEE9FF">: AVCapturePhoto,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                     </span><span style="color: #88C0D0">error</span><span style="color: #D8DEE9FF">: </span><span style="color: #8FBCBB">Error</span><span style="color: #81A1C1">?</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> error </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">CameraViewModel: Error capturing photo - </span><span style="color: #81A1C1">\(</span><span style="color: #A3BE8C">error</span><span style="color: #81A1C1">!)</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">guard</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> photoData </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> photo.</span><span style="color: #88C0D0">fileDataRepresentation</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">CameraViewModel: No photo data found.</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">capturedPhoto</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">UIImage</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">data</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> photoData</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// MARK: - SwiftUI Main View</span></span>
<span class="line"><span style="color: #81A1C1">struct</span><span style="color: #D8DEE9FF"> ContentView</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB; font-weight: bold">View </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">@</span><span style="color: #81A1C1">ObservedObject</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> viewModel </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">CameraViewModel</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> body: </span><span style="color: #81A1C1">some</span><span style="color: #D8DEE9FF"> View </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">GeometryReader</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> _ </span><span style="color: #81A1C1">in</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">ZStack</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">alignment</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">bottom</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">CameraLayerView</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">cameraSession</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> viewModel.</span><span style="color: #D8DEE9">session</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    .</span><span style="color: #88C0D0">onAppear</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        viewModel.</span><span style="color: #88C0D0">requestAccessIfNeeded</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    .</span><span style="color: #88C0D0">edgesIgnoringSafeArea</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">all</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    .</span><span style="color: #88C0D0">onCameraCaptureEvent</span><span style="color: #ECEFF4">()</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> event </span><span style="color: #81A1C1">in</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> event.phase </span><span style="color: #81A1C1">==</span><span style="color: #D8DEE9FF"> .began </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                            </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">viewModel</span><span style="color: #D8DEE9FF">.</span><span style="color: #88C0D0">capturePhoto</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span></span>
<span class="line"><span style="color: #ECEFF4">                </span><span style="color: #616E88">// Capture button</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">VStack</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #88C0D0">Spacer</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #88C0D0">Button</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        viewModel.</span><span style="color: #88C0D0">capturePhoto</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">label</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #88C0D0">Text</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Take Photo</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                            .</span><span style="color: #88C0D0">font</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">headline</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                            .</span><span style="color: #88C0D0">foregroundColor</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">white</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                            .</span><span style="color: #88C0D0">padding</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">                            .</span><span style="color: #88C0D0">background</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">Color.</span><span style="color: #D8DEE9">blue</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                            .</span><span style="color: #88C0D0">cornerRadius</span><span style="color: #ECEFF4">(</span><span style="color: #B48EAD">10</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    .</span><span style="color: #88C0D0">padding</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">bottom</span><span style="color: #D8DEE9FF">, </span><span style="color: #B48EAD">20</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span></span>
<span class="line"><span style="color: #ECEFF4">                </span><span style="color: #616E88">// Thumbnail of the captured photo</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> image </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> viewModel.capturedPhoto </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #88C0D0">Image</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">uiImage</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> image</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        .</span><span style="color: #88C0D0">resizable</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        .</span><span style="color: #88C0D0">aspectRatio</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">contentMode</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> .</span><span style="color: #D8DEE9">fit</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        .</span><span style="color: #88C0D0">frame</span><span style="color: #ECEFF4">(</span><span style="color: #88C0D0">width</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">120</span><span style="color: #D8DEE9FF">, </span><span style="color: #88C0D0">height</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">90</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        .</span><span style="color: #88C0D0">padding</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">bottom</span><span style="color: #D8DEE9FF">, </span><span style="color: #B48EAD">80</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        .</span><span style="color: #88C0D0">task</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> supportsCameraButton </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">self</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">viewModel</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">session</span><span style="color: #D8DEE9FF">.</span><span style="color: #D8DEE9">supportsControls</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #616E88">// MARK: - SwiftUI Preview</span></span>
<span class="line"><span style="color: #88C0D0">#Preview</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">ContentView</span><span style="color: #ECEFF4">()</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span></code></pre></div>



<p>Enjoy!</p><p>The post <a href="https://mszpro.com/ios-camera-control-button">Adapt to physical camera control button within your own iOS app (for SwiftUI, Zoom, Exposure, & Custom Controls)</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Detect, extract, Segment objects on given image in iOS (ImageAnalysisInteraction, VNGenerateForegroundInstanceMask)</title>
		<link>https://mszpro.com/vision-foreground-instance-mask-request</link>
		
		<dc:creator><![CDATA[msz]]></dc:creator>
		<pubDate>Mon, 16 Dec 2024 07:43:12 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[Machine Learning]]></category>
		<guid isPermaLink="false">https://mszpro.com/?p=377</guid>

					<description><![CDATA[<p>In Photos app, you can long press to extract objects (animals, people) from an image. In this article, we will talk about implementing this same feature using `ImageAnalysisInteraction`, and we will dig deeper to calling `VNGenerateForegroundInstanceMaskRequest` to archive this without using any image views. Who says cats cannot party? (a demo of extracting the foreground [&#8230;]</p>
<p>The post <a href="https://mszpro.com/vision-foreground-instance-mask-request">Detect, extract, Segment objects on given image in iOS (ImageAnalysisInteraction, VNGenerateForegroundInstanceMask)</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>In Photos app, you can long press to extract objects (animals, people) from an image. In this article, we will talk about implementing this same feature using `ImageAnalysisInteraction`, and we will dig deeper to calling `VNGenerateForegroundInstanceMaskRequest` to archive this without using any image views.</p>



<h2 class="wp-block-heading">Who says cats cannot party?</h2>



<h3 class="wp-block-heading">(a demo of extracting the foreground cats and replacing the image background)</h3>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="1600" height="2321" src="https://static-assets.mszpro.com/2024/12/1PE4kbADw0EBAea2QjFy2NQ.jpg" alt="" class="wp-image-384" srcset="https://static-assets.mszpro.com/2024/12/1PE4kbADw0EBAea2QjFy2NQ-207x300.jpg 207w, https://static-assets.mszpro.com/2024/12/1PE4kbADw0EBAea2QjFy2NQ-706x1024.jpg 706w, https://static-assets.mszpro.com/2024/12/1PE4kbADw0EBAea2QjFy2NQ-768x1114.jpg 768w, https://static-assets.mszpro.com/2024/12/1PE4kbADw0EBAea2QjFy2NQ-1059x1536.jpg 1059w, https://static-assets.mszpro.com/2024/12/1PE4kbADw0EBAea2QjFy2NQ-1412x2048.jpg 1412w, https://static-assets.mszpro.com/2024/12/1PE4kbADw0EBAea2QjFy2NQ.jpg 1600w" sizes="auto, (max-width: 1600px) 100vw, 1600px" /></figure>



<p>This article will apply to both UIKit and SwiftUI applications.</p>



<ul class="wp-block-list">
<li>Detect the objects within a given image</li>



<li>Highlight different objects in your code</li>



<li>Get the image of the object</li>
</ul>



<p>As a bonus of this article, I will also show you how to:</p>



<ul class="wp-block-list">
<li>Get the object at tapped position</li>



<li>Replace the image background behind the subjects</li>
</ul>



<p><strong>Notice: The code within this article will not run in simulator. You should use a physical device to test it.</strong></p>



<p><strong>Notice: SwiftUI code follows after the UIKit code</strong></p>



<p>Let’s get started!</p>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="1600" height="2321" src="https://static-assets.mszpro.com/2024/12/1cb3l9QS_2Z9zxYlUvAHzpQ.jpg" alt="" class="wp-image-381" srcset="https://static-assets.mszpro.com/2024/12/1cb3l9QS_2Z9zxYlUvAHzpQ-207x300.jpg 207w, https://static-assets.mszpro.com/2024/12/1cb3l9QS_2Z9zxYlUvAHzpQ-706x1024.jpg 706w, https://static-assets.mszpro.com/2024/12/1cb3l9QS_2Z9zxYlUvAHzpQ-768x1114.jpg 768w, https://static-assets.mszpro.com/2024/12/1cb3l9QS_2Z9zxYlUvAHzpQ-1059x1536.jpg 1059w, https://static-assets.mszpro.com/2024/12/1cb3l9QS_2Z9zxYlUvAHzpQ-1412x2048.jpg 1412w, https://static-assets.mszpro.com/2024/12/1cb3l9QS_2Z9zxYlUvAHzpQ.jpg 1600w" sizes="auto, (max-width: 1600px) 100vw, 1600px" /></figure>



<h1 class="wp-block-heading">Method 1: Attaching image analysis component to UIImageView image view</h1>



<h2 class="wp-block-heading">Detecting objects within an image</h2>



<p>To perform image analysis, you will need to add `ImageAnalysisInteraction` to&nbsp;<code>UIImageView</code></p>



<script src="https://gist.github.com/mszpro/b1b3d96b22e703d61f1b24c47815fca9.js"></script>



<p>Here, you can set the preferred interaction types. If you use the Apple’s Photos app, you will find that you can not only pick objects within the image, but also texts and QR codes. This is defined using the `preferredInteractionTypes` property. You can provide an array to this property to set which object the user can interact with in your app’s image view.</p>



<script src="https://gist.github.com/mszpro/c3e33a7fdf80d007e7b0b4673ab3b42f.js"></script>



<p><code>.dataDetectors</code>&nbsp;means URLs, email addresses, and physical addresses</p>



<p><code>.imageSubject</code>&nbsp;means objects within the image (the main focus of this article)</p>



<p><code>.textSelection</code>&nbsp;means selecting the text within the image</p>



<p><code>.visualLookUp</code>&nbsp;means objects that the iOS system can show more information about (for example, the breed of a cat or dog)</p>



<p>For this article, you can set it to be only&nbsp;<code>.imageSubject</code></p>



<h3 class="wp-block-heading">Running image analysis</h3>



<p>To run image analysis and check which objects are in the image, you run the below code:</p>



<p>You can also use the `interaction.highlightedSubjects` property to highlight each or all of the detected objects. In the above code, if we set this variable to `detectedSubjects`, it will highlight all the detected objects.</p>



<h2 class="wp-block-heading">Reading the data from image analyzer</h2>



<p>You can read the objects and set which subjects are highlighted in your code by using the `interaction.subjects` property and the `interaction.highlightedSubjects` property.</p>



<script src="https://gist.github.com/mszpro/b194c99b2f66d4d977b4ccd00e27b13b.js"></script>



<p>Within each of the subject (which conforms to `ImageAnalysisInteraction.Subject`, you can read the size, origin (bounding box), and extract the image</p>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="1600" height="527" src="https://static-assets.mszpro.com/2024/12/1R4cIBzJqfm6ZsN3-_R1ngg.png" alt="" class="wp-image-383" srcset="https://static-assets.mszpro.com/2024/12/1R4cIBzJqfm6ZsN3-_R1ngg-300x99.png 300w, https://static-assets.mszpro.com/2024/12/1R4cIBzJqfm6ZsN3-_R1ngg-1024x337.png 1024w, https://static-assets.mszpro.com/2024/12/1R4cIBzJqfm6ZsN3-_R1ngg-768x253.png 768w, https://static-assets.mszpro.com/2024/12/1R4cIBzJqfm6ZsN3-_R1ngg-1536x506.png 1536w, https://static-assets.mszpro.com/2024/12/1R4cIBzJqfm6ZsN3-_R1ngg.png 1600w" sizes="auto, (max-width: 1600px) 100vw, 1600px" /></figure>



<p>To access the size and bounding box:</p>



<script src="https://gist.github.com/mszpro/8786770e2c9c31789649fb1e5aca2929.js"></script>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="1600" height="1361" src="https://static-assets.mszpro.com/2024/12/1JhIlM0y8G6lsX0c1Sl8uPA.jpg" alt="" class="wp-image-380" srcset="https://static-assets.mszpro.com/2024/12/1JhIlM0y8G6lsX0c1Sl8uPA-300x255.jpg 300w, https://static-assets.mszpro.com/2024/12/1JhIlM0y8G6lsX0c1Sl8uPA-1024x871.jpg 1024w, https://static-assets.mszpro.com/2024/12/1JhIlM0y8G6lsX0c1Sl8uPA-768x653.jpg 768w, https://static-assets.mszpro.com/2024/12/1JhIlM0y8G6lsX0c1Sl8uPA-1536x1307.jpg 1536w, https://static-assets.mszpro.com/2024/12/1JhIlM0y8G6lsX0c1Sl8uPA.jpg 1600w" sizes="auto, (max-width: 1600px) 100vw, 1600px" /></figure>



<h3 class="wp-block-heading">Getting a single image for all highlighted (selected) object</h3>



<p>You can also get a single image for any objects combined. For example, I can get an image of the left-most cat and right-most cat:</p>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="1600" height="2321" src="https://static-assets.mszpro.com/2024/12/1HbltUA8F_ycummv6ccaVXg.jpg" alt="" class="wp-image-378" srcset="https://static-assets.mszpro.com/2024/12/1HbltUA8F_ycummv6ccaVXg-207x300.jpg 207w, https://static-assets.mszpro.com/2024/12/1HbltUA8F_ycummv6ccaVXg-706x1024.jpg 706w, https://static-assets.mszpro.com/2024/12/1HbltUA8F_ycummv6ccaVXg-768x1114.jpg 768w, https://static-assets.mszpro.com/2024/12/1HbltUA8F_ycummv6ccaVXg-1059x1536.jpg 1059w, https://static-assets.mszpro.com/2024/12/1HbltUA8F_ycummv6ccaVXg-1412x2048.jpg 1412w, https://static-assets.mszpro.com/2024/12/1HbltUA8F_ycummv6ccaVXg.jpg 1600w" sizes="auto, (max-width: 1600px) 100vw, 1600px" /></figure>



<script src="https://gist.github.com/mszpro/6e6670a8070cc9731361f0c59ffd89c5.js"></script>



<h1 class="wp-block-heading">SwiftUI compatible view</h1>



<p>If you want to use the above logic in SwiftUI, we can write 3 files.</p>



<p>Here is the&nbsp;<code>ObservableObject</code>&nbsp;which helps share the data between the SwiftUI view `ImageAnalysisViewModel` and the compatibility view `ObjectPickableImageView`</p>



<script src="https://gist.github.com/mszpro/68cb28027663d2ce10207574c0349f33.js"></script>



<p>Here is the compatibility view:</p>



<script src="https://gist.github.com/mszpro/cb50b68400cc2798a6fb3efc7b51e5c4.js"></script>



<p>Here is the SwiftUI view:</p>



<script src="https://gist.github.com/mszpro/04035ff46a92c9d72f0a9e6d7a09560c.js"></script>



<p>In the SwiftUI view, you can see that we will call the analyzer class directly. For example, to highlight an object, we use `self.viewModel.interaction.highlightedSubjects.insert(object)`.</p>



<p>Here, we are using&nbsp;<code>.environmentObject</code>&nbsp;view modifier to link the&nbsp;<code>ObservableObject</code>&nbsp;to the compatibility view: `.environmentObject(viewModel)`</p>



<h2 class="wp-block-heading">Find the subject at tapped position</h2>



<p>We can also add a feature to detect which object user tapped on.</p>



<p>First, we will attach a tap gesture recognizer to the image view:</p>



<pre class="wp-block-code"><code>let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))<br>imageView.addGestureRecognizer(tapGesture)</code></pre>



<p>In the&nbsp;<code>handleTap</code>&nbsp;function, we check if there is a subject at the tapped location. Then, we can either extract the image or highlight (or remove the highlight) for the (tapped) subject:</p>



<script src="https://gist.github.com/mszpro/ce9ffbac4c017374f15db2c83bb6a4bb.js"></script>



<p>In SwiftUI, we can use the&nbsp;<code>.onTapGesture</code>&nbsp;view modifier directly to read the tapped position:</p>



<script src="https://gist.github.com/mszpro/c291b274e79a322737ed095045f1bbec.js"></script>



<p>Now, you should be able to tap to highlight or remove the highlight of a subject within the image:</p>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="346" height="502" src="https://static-assets.mszpro.com/2024/12/1TKS9IJzwj8GcshRA8IcDEw.gif" alt="" class="wp-image-382"/></figure>



<h1 class="wp-block-heading">Method 2: Using Vision requests</h1>



<p>If you do not want to show the image view, and just want to analyze and extract the objects within an image, you can directly use the `VNGenerateForegroundInstanceMaskRequest`, which is the underlying API that the above function calls.</p>



<p>You can run the analysis like below:</p>



<script src="https://gist.github.com/mszpro/38cdccb2d943af235a0ffcac80e4ddde.js"></script>



<p>This function takes the user selected (or your application input)&nbsp;<code>UIImage</code>&nbsp;, convert it to&nbsp;<code>CIImage</code>&nbsp;and then runs the Vision foreground object recognition requests.</p>



<h2 class="wp-block-heading">Extract the masked image</h2>



<p>We can get the mask of the objects. As shown below, a mask indicates the pixels that has the foreground objects. Here, the white part indicates pixels that has been detected as the foreground object, and the black part indicates pixels that are the background.</p>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="1600" height="2321" src="https://static-assets.mszpro.com/2024/12/1kKTtYYEwcCC1LphASbeSXw.jpg" alt="" class="wp-image-385" srcset="https://static-assets.mszpro.com/2024/12/1kKTtYYEwcCC1LphASbeSXw-207x300.jpg 207w, https://static-assets.mszpro.com/2024/12/1kKTtYYEwcCC1LphASbeSXw-706x1024.jpg 706w, https://static-assets.mszpro.com/2024/12/1kKTtYYEwcCC1LphASbeSXw-768x1114.jpg 768w, https://static-assets.mszpro.com/2024/12/1kKTtYYEwcCC1LphASbeSXw-1059x1536.jpg 1059w, https://static-assets.mszpro.com/2024/12/1kKTtYYEwcCC1LphASbeSXw-1412x2048.jpg 1412w, https://static-assets.mszpro.com/2024/12/1kKTtYYEwcCC1LphASbeSXw.jpg 1600w" sizes="auto, (max-width: 1600px) 100vw, 1600px" /></figure>



<p>In the below code, `convertMonochromeToColoredImage` function will help generate a preview mask image. <code>apply</code>function will help us to apply the mask to the original input image (so we only get an image of cats without the background).</p>



<script src="https://gist.github.com/mszpro/aaa77629576f227f1226f37314d76e23.js"></script>



<p>In the&nbsp;<code>apply</code>&nbsp;function, we can also try to supply a background image. I first scale and crop that background image to fit the original image, then, I apply it as the background of the foreground image.</p>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="1600" height="2321" src="https://static-assets.mszpro.com/2024/12/1PE4kbADw0EBAea2QjFy2NQ.jpg" alt="" class="wp-image-384" srcset="https://static-assets.mszpro.com/2024/12/1PE4kbADw0EBAea2QjFy2NQ-207x300.jpg 207w, https://static-assets.mszpro.com/2024/12/1PE4kbADw0EBAea2QjFy2NQ-706x1024.jpg 706w, https://static-assets.mszpro.com/2024/12/1PE4kbADw0EBAea2QjFy2NQ-768x1114.jpg 768w, https://static-assets.mszpro.com/2024/12/1PE4kbADw0EBAea2QjFy2NQ-1059x1536.jpg 1059w, https://static-assets.mszpro.com/2024/12/1PE4kbADw0EBAea2QjFy2NQ-1412x2048.jpg 1412w, https://static-assets.mszpro.com/2024/12/1PE4kbADw0EBAea2QjFy2NQ.jpg 1600w" sizes="auto, (max-width: 1600px) 100vw, 1600px" /></figure>



<p>Yep! That’s how I made those cats party!</p>



<p>You can find the full project code (in SwiftUI) here:&nbsp;<a href="https://github.com/mszpro/LiftObjectFromImage" target="_blank" rel="noreferrer noopener">https://github.com/mszpro/LiftObjectFromImage</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>:relaxed: [Twitter&nbsp;<a href="http://twitter.com/MszPro" target="_blank" rel="noreferrer noopener">@MszPro</a>](<a href="https://twitter.com/MszPro" target="_blank" rel="noreferrer noopener">https://twitter.com/MszPro</a>)</p>



<p>:relaxed: 個人ウェブサイト&nbsp;<a href="https://mszpro.com/" target="_blank" rel="noreferrer noopener">https://MszPro.com</a></p>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="1200" height="900" src="https://static-assets.mszpro.com/2024/12/1WR41Ei1wO48T1HY7UnA61g.png" alt="" class="wp-image-379" srcset="https://static-assets.mszpro.com/2024/12/1WR41Ei1wO48T1HY7UnA61g-300x225.png 300w, https://static-assets.mszpro.com/2024/12/1WR41Ei1wO48T1HY7UnA61g-1024x768.png 1024w, https://static-assets.mszpro.com/2024/12/1WR41Ei1wO48T1HY7UnA61g-768x576.png 768w, https://static-assets.mszpro.com/2024/12/1WR41Ei1wO48T1HY7UnA61g.png 1200w" sizes="auto, (max-width: 1200px) 100vw, 1200px" /></figure><p>The post <a href="https://mszpro.com/vision-foreground-instance-mask-request">Detect, extract, Segment objects on given image in iOS (ImageAnalysisInteraction, VNGenerateForegroundInstanceMask)</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/

Page Caching using Disk: Enhanced 
Lazy Loading (feed)
Database Caching 13/75 queries in 0.023 seconds using Disk

Served from: mszpro.com @ 2025-07-06 04:55:23 by W3 Total Cache
-->