View in English

  • メニューを開く メニューを閉じる
  • Apple Developer
検索
検索を終了
  • Apple Developer
  • ニュース
  • 見つける
  • デザイン
  • 開発
  • 配信
  • サポート
  • アカウント
次の内容に検索結果を絞り込む

クイックリンク

5 クイックリンク

ビデオ

メニューを開く メニューを閉じる
  • コレクション
  • トピック
  • すべてのビデオ
  • 利用方法

WWDC25に戻る

  • 概要
  • トランスクリプト
  • コード
  • アプリでのシネマティックビデオの撮影

    Cinematic Video APIを使って、シネマスタイルのビデオをアプリで簡単に撮影する方法を紹介します。シネマティック撮影セッションの設定方法や、ビデオ撮影UIの構築の基本について説明します。被写界深度のエフェクトを適用してトラッキングとRack Focus(ピン送り)を実現するなど、高度なシネマティック機能についても解説します。

    関連する章

    • 0:00 - イントロダクション
    • 0:33 - シネマティックビデオ
    • 3:44 - 優れたシネマティック撮影体験の構築

    リソース

    • AVCam: Building a camera app
    • AVFoundation
    • Capturing cinematic video
    • Cinematic
      • HDビデオ
      • SDビデオ

    関連ビデオ

    WWDC25

    • キャプチャコントロールによるカメラ体験の向上

    WWDC24

    • ロック画面での優れたカメラキャプチャ体験の構築

    WWDC23

    • より応答性の高いカメラ体験の実現
    • iPadOSアプリで外部カメラをサポート
    • tvOS向けの連携カメラ機能
  • このビデオを検索

    こんにちは Royです カメラソフトウェアチームの エンジニアです 今日は みなさんのアプリで Cinematic Video APIを使って簡単に プロレベルのシネマスタイルビデオを 撮影する方法についてお話しします

    iPhone 13と13 Proでは シネマティックモードが導入されました

    直感的なユーザーインターフェイスと パワフルなアルゴリズムで iPhoneはビデオ撮影における 最強のツールに変身しました

    このセッションでは シネマティックビデオが 魅力的な理由を探ります

    そして いくつかのコードを見て 優れたシネマティックキャプチャ体験を 構築する方法について学びます

    シネマティックビデオとは何でしょうか

    その本質は よく知られた ストーリーテリング用のツールです 矩形フォーカスや トラッキングフォーカスなどです

    被写界深度が浅い場合 監督は シーンの主な被写体に 見る人の注意を引きつけ 物語のインパクトを高めます 映画でよくあるように 被写体が動くときは トラッキングフォーカスで 鮮明に捉えておくことができます

    効果は抜群ですが 現実世界では このようなフォーカステクニックには 多くの専門知識が必要です

    そのため 映画のセットには フォーカスプラーがいて このパワフルながら難しい撮影を 主な仕事にしています

    シネマティックビデオは インテリジェントに フォーカス決定を行うことで これを劇的に簡素化します

    例えば 被写体がフレームインすると アルゴリズムは自動的にフォーカスを合わせ トラッキングを開始します 被写体が目をそらすと フォーカスは自動的に 別のポイントに移動し 必要に応じて被写体に戻ります

    今年 この素晴らしい機能が Cinematic Video APIとして 利用可能になったことで みなさんのアプリで 素晴らしい シネマスタイルのビデオを簡単に撮影できます 新しいAPIを使って シネマティックビデオの 優れたキャプチャ体験を構築する 方法を見ていきましょう ビデオアプリの一般的な キャプチャセッションから見ていきましょう

    まず ムービーを撮影する デバイスを選択します

    それをデバイス入力に追加します

    ユースケースに応じて 複数の出力を セッションに追加できます

    この出力がキャプチャセッションに 追加されると 接続オブジェクトが作成されます

    単純な設定ではありません でも シネマティックビデオキャプチャは とても簡単に有効化できます

    iOS 26では AVCaptureDeviceInputクラスに 新しいプロパティ isCinematicVideoCaptureEnabledが追加されています これをtrueに設定すると キャプチャセッション全体が シネマティックビデオを出力する設定になり シネマティック処理が それぞれの出力に 適用されるようになります

    ムービーファイル出力で 制作されたムービーファイルは シネマティックになります

    視差データ メタデータと 非破壊編集用の オリジナルの ビデオが含まれています レンダリングされたボケで再生したり ボケ効果を編集したりするには 2023年に導入されたCinematic フレームワークを使用できます

    このフレームワークについて詳しくは WWDC23セッション 「Support Cinematic mode videos in your app」をご覧ください ビデオデータ出力は 浅い被写界深度エフェクトが ベイクされたフレームを生成します これは フレームに直接アクセスする 必要があるとき便利です

    例えば リモートデバイスに 送信する場合です

    同じように プレビューレイヤには ボケがリアルタイムでレンダリングされます この方法でファインダーを 簡単に構築できます

    このアーキテクチャの概要を 念頭に置きつつ 以下の領域のコードを いくつか見ていきましょう

    シネマティックキャプチャに必要な すべてのコンポーネントで AVCaptureSessionを構成します

    次に SwiftUIを使ってビデオキャプチャ用の インターフェイスを構築します

    顔検出などのメタデータを 取得したり 画面に描画したりする 方法について説明します

    さまざまな方法で フォーカス駆動を手動で行って シネマティックビデオの パワーをフル活用します

    最後は さらに洗練された アプリにするための いくつかの 高度な機能で締めくくります

    キャプチャセッションから始めましょう

    まず ムービーをキャプチャする ビデオデバイスを見つけます デバイスを見つけるには AVCaptureDevice.DiscoverySession オブジェクトを作成します

    シネマティックビデオは 背面のデュアル 広角カメラと 前面のTrueDepthカメラの 両方でサポートされています

    この場合は デバイスタイプの配列で .builtInDualWideCameraを指定します ムービーを撮影しているので mediaTypeは.videoを使用します

    デバイスの背面にあるカメラを リクエストします

    今回は単一のデバイスタイプを リクエストしているため 検出セッションのデバイス配列の 最初の要素のみを取得できます

    シネマティックビデオキャプチャを 有効にするには この機能をサポートする形式が必要です

    このような形式を見つけるには すべてのデバイスの形式を 反復処理して isCinematicVideoCaptureSupported プロパティがtrueを返すものを使用します

    次の形式がサポートされています

    デュアル広角カメラとTrueDepthカメラ どちらも 毎秒30フレームで1080pと4Kの 両方をサポートしています

    SDRやEDRコンテンツの録画に 興味があれば 420ビデオレンジまたはフルレンジの いずれかを使用できます

    10ビットHDRビデオコンテンツがよければ 代わりにx420を使用します

    無声映画を作っているわけではないので 音声もほしいですね 同じDiscoverySession APIを使用して マイクを検索します

    デバイスが手元にある状態で それぞれに入力を作成します 次にこれらの入力を キャプチャセッションに追加します

    この時点で ビデオ入力で シネマティック ビデオキャプチャをオンにできます

    シネマティック体験を向上させるために 空間オーディオをキャプチャできます 一次アンビソニックスを multichannelAudioModeとして 設定するだけです

    空間オーディオについて詳しくは 今年のセッション「Enhance your app's audio content creation capabilities」をご覧ください 出力に移りましょう AVCaptureMovieFileOutputオブジェクトを 作成し セッションに追加します

    手だと三脚ほど安定性がないので 手ぶれ補正を有効にすることをお勧めします

    そのためには まず movieFileOutputの ビデオ接続を見つけて そのpreferredVideoStabilizationModeを 設定します この場合は cinematicExtendedEnhancedを使用します

    最後に プレビューレイヤを キャプチャセッションに関連付けます

    キャプチャセッションは ひとまず完了です ユーザーインターフェイスに 移りましょう

    AVCaptureVideoPreviewLayerは CALayerのサブクラスです SwiftUIの一部ではありません やり取りするためには UIViewRepresentable プロトコルに準拠する構造体に プレビューレイヤを ラップする必要があります

    この構造体内に UIViewサブクラスの CameraPreviewUIViewを作成します

    そのlayerClassプロパティを オーバーライドして previewLayerをビューの バッキングレイヤにします

    previewLayerプロパティを作成して AVCaptureVideoPreviewLayerタイプとして 簡単にアクセスできるようにします

    次に プレビュービューを ZStackに配置できます

    ここでカメラコントロールなどの 他のUI要素と 簡単に構成できます

    冒頭で述べたように 浅い被写界深度は ストーリーテリングのための重要なツールです デバイス入力のsimulatedAperture プロパティを変更して ボケ効果の全体的な強さを調整できます

    右側に表示されているように スライダでプロパティを動かして ぼかしの全体的な強さを変更します

    この値は業界標準の絞り値で表されます

    焦点距離と 絞りの直径の比です

    入れ替えると 絞りは 焦点距離をF値で 割ったものになります

    そのため F値が小さいほど絞りが大きくなり ボケが強くなります

    形式から 絞り値の最小値 最大値 デフォルトのシミュレーション値を 見つけられます

    これを使用して スライダなどの適切なUI要素を 設定します

    次に ユーザーがシネマティックビデオを 手動で操作する仕組みを いくつか構築しましょう

    ユーザーが手動でフォーカス駆動するには 顔などのフォーカス候補に視覚的 インジケータを表示する必要があります そのためには 検出メタデータが必要です

    AVCaptureMetadataOutputを使用して これらの検出を取得し 画面に境界を描画して ユーザーが操作できるようにします シネマティックビデオアルゴリズムが 最適に機能するには 特定の metadataObjectTypesが必要です

    これは 新しいプロパティ requiredMetadataObjectTypesForCinematicVideoCaptureで 伝達されます シネマティックビデオが有効になったとき 指定されたmetadataObjectTypesが リストと異なる場合は例外が発生します

    最後に デリゲートを提供して メタデータと デリゲートが呼び出される キューを受け取る必要があります

    メタデータオブジェクトはメタデータ出力 デリゲートコールバックで受け取ります

    このメタデータをSwiftUIの ビューレイヤに簡単に伝達するために Observableクラスを使用します

    プロパティを更新すると Observingビューが 自動的に更新されます

    ビューレイヤでは Observableオブジェクトが更新されるたびに ビューが自動的に再描画されます

    各metadataObjectに対して 矩形を描画します

    矩形を作成する際には メタデータの境界をプレビューレイヤの 座標空間に変換することが重要です

    layerRectConverted fromMetadataOutputRectメソッドを使用します

    なお 位置メソッドのXとYは AVFoundationで使用される左上隅ではなく ビューの中心を指していることに 注意してください それで 矩形のmidXとmidYを使用して 適切に調整する必要があります

    メタデータの矩形が画面に描画されると その矩形で 手動で フォーカス駆動できます

    Cinematic Video APIには フォーカスを手動設定する方法が3つあります 1つずつ見ていきましょう

    setCinematicVideoTrackingFocus detectedObjectID focusModeメソッドを使用して メタデータ出力から取得する AVMetadataオブジェクトで利用可能な detectedObjectIDが識別した 特定の被写体に ラックフォーカスできます

    focusModeはシネマティックビデオの トラッキング動作を設定します

    CinematicVideoFocusMode列挙型には 次の3つのケースがあります none strong weakです

    strongはシネマティックビデオに 被写体を トラッキングし続けるよう指示します 自動的に選ばれそうな ほかの フォーカス候補があったとしてもです

    この場合 猫はフレーム内で より目立っていますが 黄色の実線の矩形が示すように 強いフォーカスは 後ろの被写体に固定されたままです

    一方 弱いフォーカスはアルゴリズムに フォーカスコントロールを任せます アルゴリズムは適切だと判断すると 自動的にラックフォーカスします

    この場合 猫が振り向くと より重要だとみなされ 弱いフォーカスは自動的に猫に移ります それが破線の矩形で表示されています

    noneの場合は 現時点で メタデータオブジェクトに フォーカスが合っているかどうかを 判断する時にのみ役立ちます フォーカスを設定する際には 使用しないでください

    2番目のフォーカスメソッドは 別の最初のパラメータを受け取ります 検出されたオブジェクトIDの代わりに ビュー内のポイントを取得します

    指定されたポイントで 興味深い オブジェクトを探すように指示します 見つかると タイプが際立つ オブジェクトで新しい メタデータオブジェクトを作成します それで 画面上でその周りに 矩形を描画できます

    3番目のフォーカスメソッドは setCinematicVideoFixedFocusです ポイントとフォーカスモードを取得します フォーカスを 深度などのシグナルを使って 内部的に計算される固定距離に設定します このメソッドは 強いフォーカスモードと 組み合わせることで シーン内の特定の平面に フォーカスを効果的に固定します フォアグラウンドでの他の動きも無視します

    それぞれのユースケースで意味のある フォーカスロジックを どんなアプリにも実装できます このアプリでは次のことを行います

    フォーカスされていない 検出矩形をタップすると 弱いフォーカスでラックフォーカスします これで 被写体の間でフォーカスを あちこち切り替えて フォーカスイン アウトできます

    すでに弱いフォーカスが合っている メタデータオブジェクトをタップすると 強いフォーカスに変わります

    黄色の実線の矩形で表示されます

    既存の検出がないポイントでタップすると シネマティックビデオは 目立つオブジェクトを見つけて 弱いフォーカスを合わせます

    長押しで 強い固定フォーカスを設定します このロジックは 次の方法で コードに実装できます まず 2つのジェスチャを 作成する必要があります

    通常のタップジェスチャは SpatialTapGestureで簡単に行えます これは フォーカス設定に必要な タップの位置を提供します

    タップすると カメラモデルオブジェクトで focusTapメソッドを呼び出します これは 基盤となるAVCaptureDeviceに アクセスできる場所です

    一方 長押しはもう少し複雑です 組み込みのlongPressGestureからは DragGestureを使用して 長押しを シミュレートするためのタップ位置が 提供されないからです

    押すと 0.3秒タイマーで開始します

    タイマーが作動すると カメラモデルで focusLongPressメソッドを 呼び出します

    次に ジェスチャを受け取る 矩形ビューを作成します これはZStackの最後に挿入されて ユーザーのジェスチャ入力が ブロックされないように すべての検出矩形の一番上に配置されます

    以前のビデオですでに見たように フォーカスされた矩形を視覚的に 区別することが重要です 弱いフォーカス 強いフォーカス フォーカスなしを区別して ユーザーが正しいアクションを 実行できるようにします

    これを行うには AVMetadataObjectを 受け取り フォーカスされた 矩形ビューを返すメソッドを実装します

    また メタデータの境界を メタデータ出力の座標空間から プレビューレイヤの座標空間に 変換する必要もあります

    線のスタイルと色を違うものに設定することで フォーカスモードごとに 視覚的に異なる矩形を簡単に作成できます

    ビューレイヤから渡されたポイントを使って どのフォーカスメソッドを使用するか 決定できます

    まず 知りたいのは ユーザーがメタデータの矩形を タップしたかどうかです これはヘルパメソッド findTappedMetadataObjectで行います

    ここでは フレームごとにキャッシュする すべてのメタデータを反復処理して 指定されたポイントが境界のどれか1つに 収まるかどうかをチェックします

    ここでも ポイントと矩形が 同じ座標空間にあることを確認します

    focusTapメソッドに戻って メタデータオブジェクトが見つかり すでに弱いフォーカスが合っている場合 強いフォーカスに変わります

    まだフォーカスが合っていない場合 弱いフォーカスを適用します

    ユーザーがメタデータの矩形を タップしていない場合 フレームワークに この時点で際立つ オブジェクトを見つけるように指示します

    長押しで 指定したポイントに 強い固定フォーカスを設定します

    これで シネマティックビデオが キャプチャできる 完全に機能するアプリが完成しました 細かいところに磨きをかけていきましょう

    今 ビデオキャプチャのグラフは こんな感じです ムービーをキャプチャして メタデータ プレビューを受け取る入力が 3つあります

    録画中の静止画キャプチャを サポートする場合は AVCapturePhotoOutputを セッションに追加するだけです

    グラフはすでにシネマティックになるように 構成されているため 写真出力には自動的に シネマティック処理が適用されます

    写真出力によって返される画像には ボケ効果が焼き付けられています

    最後に シネマティックビデオアルゴリズムが 正しく機能するためには 十分な光量が必要です 暗すぎる部屋やカメラが覆われている場合 その問題をUIでユーザーに通知します

    この状態が発生したときに 通知を受け取るためには 新しいプロパティを使用して キー値監視できます AVCaptureDeviceクラスの cinematicVideoCaptureSceneMonitoringStatusesです

    現在 シネマティックビデオは 光量不足のステータスのみサポートしています

    光量不足が発生した場合 KVOハンドラで UIを適切に更新できます

    空集合はすべてが 正常に戻ったことを意味します

    今日のセッションでは シネマティック ビデオで プロレベルの魅力的な ムービーをキャプチャする 方法についてまとめました ペットと過ごすような日常の瞬間も キャプチャできます

    Cinematic Video APIを使用して 優れたシネマティックキャプチャ体験を 構築する方法についても詳しく説明しました

    皆さんがこの機能をどのように アプリで活用して より豊かで シネマティックなコンテンツを提供するのか とても楽しみです

    ご視聴ありがとうございました

    • 4:26 - Select a video device

      // Select a video device
      
      let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualWideCamera], mediaType: .video, position: .back)
              
      guard let camera = deviceDiscoverySession.devices.first else {
          print("Failed to find the capture device")
          return
      }
    • 5:07 - Select a format that supports Cinematic Video capture

      // Select a format that supports Cinematic Video capture
      
      for format in camera.formats {
      
          if format.isCinematicVideoCaptureSupported {
      
             try! camera.lockForConfiguration()
             camera.activeFormat = format
             camera.unlockForConfiguration()
      
             break
          }
      
      }
    • 5:51 - Select a microphone

      // Select a microphone
      
      let audioDeviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes [.microphone], mediaType: .audio, position: .unspecified)
      
      guard let microphone = audioDeviceDiscoverySession.devices.first else {
          print("Failed to find a microphone")
          return
      }
    • 6:00 - Add devices to input & add inputs to the capture session & enable Cinematic Video capture

      // Add devices to inputs
      
      let videoInput = try! AVCaptureDeviceInput(device: camera)
      guard captureSession.canAddInput(videoInput) else {
          print("Can't add the video input to the session")
          return
      }
      
      let audioInput = try! AVCaptureDeviceInput(device: microphone)
      guard captureSession.canAddInput(audioInput) else {
          print("Can't add the audio input to the session")
          return
      }
      
      // Add inputs to the capture session
      
      captureSession.addInput(videoInput)
      captureSession.addInput(audioInput)
      
      // Enable Cinematic Video capture
      
      if (videoInput.isCinematicVideoCaptureSupported) {
        videoInput.isCinematicVideoCaptureEnabled = true
      }
    • 6:17 - Capture spatial audio

      // Configure spatial audio
      
      if audioInput.isMultichannelAudioModeSupported(.firstOrderAmbisonics) {
          audioInput.multichannelAudioMode = .firstOrderAmbisonics
      }
    • 6:33 - Add outputs to the session & configure video stabilization & associate the preview layer with the capture session

      // Add outputs to the session
      
      let movieFileOutput = AVCaptureMovieFileOutput()
      guard captureSession.canAddOutput(movieFileOutput) else {
          print("Can't add the movie file output to the session")
          return
      }
      captureSession.addOutput(movieFileOutput)
              
      
      // Configure video stabilization
      
      if let connection = movieFileOutput.connection(with: .video), 
          connection.isVideoStabilizationSupported {
          connection.preferredVideoStabilizationMode = .cinematicExtendedEnhanced
      }
      
      // Add a preview layer as the view finder
      
      let previewLayer = AVCaptureVideoPreviewLayer()
      previewLayer.session = captureSession
    • 7:11 - Display the preview layer with SwiftUI

      // Display the preview layer with SwiftUI
      
      struct CameraPreviewView: UIViewRepresentable {
      
          func makeUIView(context: Context) -> PreviewView {
              return PreviewView()
          }
      
          class CameraPreviewUIView: UIView {
      	
      			override class var layerClass: AnyClass {
          		AVCaptureVideoPreviewLayer.self
      			}
      
      			var previewLayer: AVCaptureVideoPreviewLayer {
        	  	layer as! AVCaptureVideoPreviewLayer
      			}
      
      			...
      		}
      
      ...
      }
    • 7:54 - Display the preview layer with SwiftUI

      // Display the preview layer with SwiftUI
      
      @MainActor
      struct CameraView: View {       
      
          var body: some View {
              ZStack {
                  CameraPreviewView()  
                	CameraControlsView()
              }
          }
      }
    • 8:05 - Adjust bokeh strength with simulated aperture

      // Adjust bokeh strength with simulated aperture
      
      
      open class AVCaptureDeviceInput : AVCaptureInput {
      
      	open var simulatedAperture: Float
      
      	...
      
      }
    • 8:40 - Find min, max, and default simulated aperture

      // Adjust bokeh strength with simulated aperture
      
      
      extension AVCaptureDeviceFormat {
      
      	open var minSimulatedAperture: Float { get }
      
      	open var maxSimulatedAperture: Float { get }
      
      	open var defaultSimulatedAperture: Float { get }
      
      	...
      
      }
    • 9:12 - Add a metadata output

      // Add a metadata output
      
      let metadataOutput = AVCaptureMetadataOutput()
      
      guard captureSession.canAddOutput(metadataOutput) else {
          print("Can't add the metadata output to the session")
          return
      }
      captureSession.addOutput(metadataOutput)
      
      metadataOutput.metadataObjectTypes = metadataOutput.requiredMetadataObjectTypesForCinematicVideoCapture
      
      metadataOutput.setMetadataObjectsDelegate(self, queue: sessionQueue)
    • 9:50 - Update the observed manager object

      // Update the observed manager object
      
      func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
      
         self.metadataManager.metadataObjects = metadataObjects
      
      }
      
      // Pass metadata to SwiftUI
      
      @Observable
      class CinematicMetadataManager {
          
          var metadataObjects: [AVMetadataObject] = []
          
      }
    • 10:12 - Observe changes and update the view

      // Observe changes and update the view
      
      struct FocusOverlayView : View {
      
          var body: some View {
      
      	        ForEach(
      	      metadataManager.metadataObjects, id:\.objectID)
      		  	{ metadataObject in
      
          		  rectangle(for: metadataObject)
      
      			  }
      		}
      }
    • 10:18 - Make a rectangle for a metadata

      // Make a rectangle for a metadata
      
      private func rectangle(for metadata: AVMetadataObjects) -> some View {
          
          let transformedRect = previewLayer.layerRectConverted(fromMetadataOutputRect: metadata.bounds)
          
          return Rectangle()
              .frame(width:transformedRect.width,
                     height:transformedRect.height)
              .position(
                  x:transformedRect.midX,
                  y:transformedRect.midY)
      }
    • 10:53 - Focus methods

      open func setCinematicVideoTrackingFocus(detectedObjectID: Int, focusMode: AVCaptureDevice.CinematicVideoFocusMode)
      
      open func setCinematicVideoTrackingFocus(at point: CGPoint, focusMode: AVCaptureDevice.CinematicVideoFocusMode)
      
      open func setCinematicVideoFixedFocus(at point: CGPoint, focusMode: AVCaptureDevice.CinematicVideoFocusMode)
    • 10:59 - Focus method 1 & CinematicVideoFocusMode

      // Focus methods
      
      open func setCinematicVideoTrackingFocus(detectedObjectID: Int, focusMode: AVCaptureDevice.CinematicVideoFocusMode)
      
      
      public enum CinematicVideoFocusMode : Int, @unchecked Sendable {
      
          case none = 0
      
          case strong = 1
      
          case weak = 2
      }
      
      extension AVMetadataObject {
      
         open var cinematicVideoFocusMode: Int32 { get }
      
      }
    • 12:19 - Focus method no.2

      // Focus method no.2
      
      open func setCinematicVideoTrackingFocus(at point: CGPoint, focusMode: AVCaptureDevice.CinematicVideoFocusMode)
    • 12:41 - Focus method no.3

      // Focus method no.3
      
      open func setCinematicVideoFixedFocus(at point: CGPoint, focusMode: AVCaptureDevice.CinematicVideoFocusMode)
    • 13:54 - Create the spatial tap gesture

      var body: some View {
      
      let spatialTapGesture = SpatialTapGesture()
          .onEnded { event in
              Task {
                  await camera.focusTap(at: event.location)
              }
           }
      
      ...
      }
    • 14:15 - Simulate a long press gesture with a drag gesture

      @State private var pressLocation: CGPoint = .zero
      @State private var isPressing = false
      private let longPressDuration: TimeInterval = 0.3
      
      var body: some View {
        
        ...
        
      	let longPressGesture = DragGesture(minimumDistance: 0).onChanged { value in
      		if !isPressing {
      			isPressing = true
      			pressLocation = value.location
      			startLoopPressTimer()
      		}
      	}.onEnded { _ in
      		isPressing = false
      	}
        
      	...
        
      }
      
      private func startLoopPressTimer() {
      	DispatchQueue.main.asyncAfter(deadline: .now() + longPressDuration) {
      		if isPressing {
      			Task {
      				await camera.focusLongPress(at: pressLocation)
      			}
      		}
      	}
      }
    • 14:36 - Create a rectangle view to receive gestures.

      var body: some View {
      
      let spatialTapGesture = ...
      let longPressGesture = ...
      
      ZStack {
        ForEach(
          metadataManager.metadataObjects,
          id:\.objectID)
        { metadataObject in
      
          rectangle(for: metadataObject)
      
        }
        Rectangle()
            .fill(Color.clear)
            .contentShape(Rectangle())
            .gesture(spatialTapGesture)
        .gesture(longPressGesture)}
      
        }
      }
    • 15:03 - Create the rectangle view

      private func rectangle(for metadata: AVMetadataObject) -> some View {
          
          let transformedRect = previewLayer.layerRectConverted(fromMetadataOutputRect: metadata.bounds)
          var color: Color
          var strokeStyle: StrokeStyle
          
          switch metadata.focusMode {
          case .weak:
              color = .yellow
              strokeStyle = StrokeStyle(lineWidth: 2, dash: [5,4])
          case .strong:
              color = .yellow
              strokeStyle = StrokeStyle(lineWidth: 2)
          case .none:
              color = .white
              strokeStyle = StrokeStyle(lineWidth: 2)
          }
          
          return Rectangle()
              .stroke(color, style: strokeStyle)
              .contentShape(Rectangle())
              .frame(width: transformedRect.width, height: transformedRect.height)
              .position(x: transformedRect.midX, 
                        y: transformedRect.midY)
      }
    • 15:30 - Implement focusTap

      func focusTap(at point:CGPoint) {
          
         try! camera.lockForConfiguration()
              
          if let metadataObject = findTappedMetadataObject(at: point) {
              if metadataObject.cinematicVideoFocusMode == .weak {
                  camera.setCinematicVideoTrackingFocus(detectedObjectID: metadataObject.objectID, focusMode: .strong)
                  
              }
              else {
                  camera.setCinematicVideoTrackingFocus(detectedObjectID: metadataObject.objectID, focusMode: .weak)
              }
          }
          else {
              let transformedPoint = previewLayer.metadataOutputRectConverted(fromLayerRect: CGRect(origin:point, size:.zero)).origin
              camera.setCinematicVideoTrackingFocus(at: transformedPoint, focusMode: .weak)
          }
          
          camera.unlockForConfiguration()
      }
    • 15:42 - Implement findTappedMetadataObject

      private func findTappedMetadataObject(at point: CGPoint) -> AVMetadataObject? {
          
          var metadataObjectToReturn: AVMetadataObject?
          
          for metadataObject in metadataObjectsArray {
              let layerRect = previewLayer.layerRectConverted(fromMetadataOutputRect: metadataObject.bounds)
              if layerRect.contains(point) {
                  metadataObjectToReturn = metadataObject
                  break
              }
          }
          
          return metadataObjectToReturn
      }
    • 16:01 - focusTap implementation continued

      func focusTap(at point:CGPoint) {
          
         try! camera.lockForConfiguration()
              
          if let metadataObject = findTappedMetadataObject(at: point) {
              if metadataObject.cinematicVideoFocusMode == .weak {
                  camera.setCinematicVideoTrackingFocus(detectedObjectID: metadataObject.objectID, focusMode: .strong)
                  
              }
              else {
                  camera.setCinematicVideoTrackingFocus(detectedObjectID: metadataObject.objectID, focusMode: .weak)
              }
          }
          else {
              let transformedPoint = previewLayer.metadataOutputRectConverted(fromLayerRect: CGRect(origin:point, size:.zero)).origin
              camera.setCinematicVideoTrackingFocus(at: transformedPoint, focusMode: .weak)
          }
          
          camera.unlockForConfiguration()
      }
    • 16:23 - Implement focusLongPress

      func focusLongPress(at point:CGPoint) {
          
         try! camera.lockForConfiguration()
      
         let transformedPoint = previewLayer.metadataOutputRectConverted(fromLayerRect:CGRect(origin: point, size: CGSizeZero)).origin
             camera.setCinematicVideoFixedFocus(at: pointInMetadataOutputSpace, focusMode: .strong)
         
          camera.unlockForConfiguration()
      }
    • 17:10 - Introduce cinematicVideoCaptureSceneMonitoringStatuses

      extension AVCaptureDevice {
      
         open var cinematicVideoCaptureSceneMonitoringStatuses: Set<AVCaptureSceneMonitoringStatus> { get }
      
      }
      
      extension AVCaptureSceneMonitoringStatus {
      
         public static let notEnoughLight: AVCaptureSceneMonitoringStatus
      
      }
    • 17:42 - KVO handler for cinematicVideoCaptureSceneMonitoringStatuses

      private var observation: NSKeyValueObservation?
      
      observation = camera.observe(\.cinematicVideoCaptureSceneMonitoringStatuses, options: [.new, .old]) { _, value in
          
          if let newStatuses = value.newValue {
              if newStatuses.contains(.notEnoughLight) {
                  // Update UI (e.g., "Not enough light")
              }
              else if newStatuses.count == 0 {
                  // Back to normal.
              }
          }
      }

Developer Footer

  • ビデオ
  • WWDC25
  • アプリでのシネマティックビデオの撮影
  • メニューを開く メニューを閉じる
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    メニューを開く メニューを閉じる
    • アクセシビリティ
    • アクセサリ
    • App Extension
    • App Store
    • オーディオとビデオ(英語)
    • 拡張現実
    • デザイン
    • 配信
    • 教育
    • フォント(英語)
    • ゲーム
    • ヘルスケアとフィットネス
    • アプリ内課金
    • ローカリゼーション
    • マップと位置情報
    • 機械学習とAI
    • オープンソース(英語)
    • セキュリティ
    • SafariとWeb(英語)
    メニューを開く メニューを閉じる
    • 英語ドキュメント(完全版)
    • 日本語ドキュメント(一部トピック)
    • チュートリアル
    • ダウンロード(英語)
    • フォーラム(英語)
    • ビデオ
    Open Menu Close Menu
    • サポートドキュメント
    • お問い合わせ
    • バグ報告
    • システム状況(英語)
    メニューを開く メニューを閉じる
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles(英語)
    • フィードバックアシスタント
    メニューを開く メニューを閉じる
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program(英語)
    • News Partner Program(英語)
    • Video Partner Program(英語)
    • セキュリティ報奨金プログラム(英語)
    • Security Research Device Program(英語)
    Open Menu Close Menu
    • Appleに相談
    • Apple Developer Center
    • App Store Awards(英語)
    • Apple Design Awards
    • Apple Developer Academy(英語)
    • WWDC
    Apple Developerアプリを入手する
    Copyright © 2025 Apple Inc. All rights reserved.
    利用規約 プライバシーポリシー 契約とガイドライン