
【SwiftUI】UIImagePickerControllerとPHPickerViewControllerを使ってカメラとフォトライブラリから動画を取得する
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
SwiftUIで端末のカメラで撮影した動画、またはフォトライブラリにある動画を取得したかった為、調べました。
以前からUIImagePickerControllerにはお世話になっていましたが、UIImagePickerController.SourceType.photoLibraryはiOS 14より新しいOSバージョンでは非推奨になっている為、フォトライブラリからソースを取り出す場合はPHPickerViewControllerの使用が推奨されております。
なので今回はカメラから動画を取得する場合は、UIImagePickerControllerを使用して、フォトアルバムから動画を取得する場合は、PHPickerViewControllerを使用する方法を調べてみました。
環境
- Xcode 13.3
- iOS 15.4.1
作ったもの
カメラで撮影した動画、またはフォトライブラリに保存してある動画を取得してVideoPlayerで再生するアプリです。
カメラ撮影した動画を取得する
カメラで撮影した動画を取得する為に、UIImagePickerControllerを使用します。SwiftUIで使用する為にUIViewControllerRepresentableに準拠させたstructを作成します。
import UniformTypeIdentifiers
import SwiftUI
struct CameraMoviePickerView: UIViewControllerRepresentable {
@Environment(\.dismiss) private var dismiss
@Binding var movieUrl: URL?
func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.sourceType = .camera
picker.delegate = context.coordinator
picker.mediaTypes = [UTType.movie.identifier]
picker.videoQuality = .typeHigh
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let parent: CameraMoviePickerView
init(_ parent: CameraMoviePickerView) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
guard let movieURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL else {
return
}
parent.movieUrl = movieURL
parent.dismiss()
}
func imagePickerControllerDidCancel(_: UIImagePickerController) {
parent.dismiss()
}
}
}
内容については説明していきます。
プロパティ
ImagePickerで動画を取得、または取得をキャンセルした場合にViewを破棄する為に環境変数dismissを用意します。
@Environment(\.dismiss) private var dismiss
動画のURLを取得した時にバインディングしたいのでバインディング変数を定義します。
@Binding var movieUrl: URL?
makeUIViewController
makeUIViewControllerで今回生成するUIImagePickerControllerの設定を行い、返り値として渡します。
func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.sourceType = .camera
picker.mediaTypes = [UTType.movie.identifier]
picker.videoQuality = .typeHigh
picker.delegate = context.coordinator
return picker
}
- sourceType
- 今回はカメラからの動画を取得するので
.cameraを指定しています。
- 今回はカメラからの動画を取得するので
- mediaTypes
- 今回はメディアは動画を取得したいので、
[UTType.movie.identifier]を指定します。
- 今回はメディアは動画を取得したいので、
- videoQuality
- 特に指定はないですが、今回は高品質動画
.typeHighにしています。
- 特に指定はないですが、今回は高品質動画
- delegate
UIViewControllerRepresentableでdelegateメソッドを呼ぶ為に、context.coordinatorを代入しています。
makeCoordinator
ViewControllerのイベントをSwiftUIに伝達する役割を果たすCoordinatorを生成します。
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
Coordinator
UIImagePickerControllerDelegateを使用する為にUINavigationControllerDelegateとUIImagePickerControllerDelegateに準拠しています。
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let parent: CameraMoviePickerView
init(_ parent: CameraMoviePickerView) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
guard let movieURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL else {
return
}
parent.movieUrl = movieURL
parent.dismiss()
}
func imagePickerControllerDidCancel(_: UIImagePickerController) {
parent.dismiss()
}
}
parent
UIImagePickerControllerのイベント伝達を受け取る変数になります。
let parent: CameraMoviePickerView
imagePickerController(_:, didFinishPickingMediaWithInfo:)
UIImagePickerControllerでメディア(今回は動画)が選択された時に呼ばれるメソッドです。
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
guard let movieURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL else {
return
}
parent.movieUrl = movieURL
parent.dismiss()
}
選択された動画のURLを取得して、CameraMoviePickerのmovieUrlに代入しています。その後、dismissを行い、画面を閉じるようにしています。
imagePickerControllerDidCancel(_:)
UIImagePickerController のキャンセルボタンが押された時に呼ばれるメソッドなので、dismissを行い、画面を閉じています。
func imagePickerControllerDidCancel(_: UIImagePickerController) {
parent.dismiss()
}
これでUIImagePickerControllerを使用してカメラで撮影した動画を取得出来ました。
フォトライブラリから動画を取得する
冒頭で説明した通り、iOS 14.0より新しいバージョンでは、UIImagePickerController.SourceType.photoLibraryは非推奨になっている為、フォトアルバムの動画を取得するのにPHPickerViewControllerを使用します。
PHPickerViewControllerはUIImagePickerControllerの代替えクラスで、安定性と信頼性が向上しています。開発者とユーザーは複数の恩恵を受けることが出来るそうです。詳細はPHPickerViewController
をご覧下さい。
import SwiftUI
import PhotosUI
struct PhotoLibraryMoviePickerView: UIViewControllerRepresentable {
@Environment(\.dismiss) private var dismiss
@Binding var movieUrl: URL?
func makeUIViewController(context: Context) -> PHPickerViewController {
var configuration = PHPickerConfiguration()
configuration.filter = .videos
configuration.preferredAssetRepresentationMode = .current
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, PHPickerViewControllerDelegate {
let parent: PhotoLibraryMoviePickerView
init(_ parent: PhotoLibraryMoviePickerView) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
parent.dismiss()
guard let provider = results.first?.itemProvider else {
return
}
let typeIdentifier = UTType.movie.identifier
if provider.hasItemConformingToTypeIdentifier(typeIdentifier) {
provider.loadFileRepresentation(forTypeIdentifier: typeIdentifier) { url, error in
if let error = error {
print("error: \(error)")
return
}
if let url = url {
let fileName = "\(Int(Date().timeIntervalSince1970)).\(url.pathExtension)"
let newUrl = URL(fileURLWithPath: NSTemporaryDirectory() + fileName)
try? FileManager.default.copyItem(at: url, to: newUrl)
self.parent.movieUrl = newUrl
}
}
}
}
}
}
内容について説明していきます。
プロパティ
PHPickerViewControllerで動画を取得、または取得をキャンセルした場合にViewを破棄する為に環境変数dismissを用意します。
@Environment(\.dismiss) private var dismiss
動画のURLを取得した時にバインディングしたいのでバインディング変数を定義します。
@Binding var movieUrl: URL?
makeUIViewController
makeUIViewControllerで今回生成するPHPickerViewControllerの設定を行い、返り値として渡します。
PHPickerViewController のインスタンス生成にはPHPickerConfigurationが必要なので、まずはPHPickerConfigurationの設定を行い、その構成と共にPHPickerViewControllerを生成します。
func makeUIViewController(context: Context) -> PHPickerViewController {
var configuration = PHPickerConfiguration()
configuration.filter = .videos
configuration.preferredAssetRepresentationMode = .current
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = context.coordinator
return picker
}
- configuration.filter
- ピッカーがどのアセットタイプを表示させるかのフィルタリングです。今回は動画なので
.videoを指定しています。
- ピッカーがどのアセットタイプを表示させるかのフィルタリングです。今回は動画なので
- configuration.preferredAssetRepresentationMode
- アセットに複数の表現が含まれている場合に使用する表現を決定するモードです。preferredAssetRepresentationModeのドキュメントに記載があるのですが、システムが追加のトランスコーディングを実行して、要求したアセットを互換性のある表現に変換する場合がある為、可能であれば、トランスコーディングを回避するために
.currentを使用します。
- アセットに複数の表現が含まれている場合に使用する表現を決定するモードです。preferredAssetRepresentationModeのドキュメントに記載があるのですが、システムが追加のトランスコーディングを実行して、要求したアセットを互換性のある表現に変換する場合がある為、可能であれば、トランスコーディングを回避するために
- configuration.selectionLimit
- 何個選択することが出来るかを指定することが出来ます。デフォルト値は
1で、今回は1つしか取得しない為、デフォルトを使用するので特に指定していません。
- 何個選択することが出来るかを指定することが出来ます。デフォルト値は
Coordinator
makeCoordinatorの箇所はUIImagePickerControllerの時と変わりがない為、割愛します。
Coordinatorクラスについて説明していきます。
class Coordinator: NSObject, PHPickerViewControllerDelegate {
let parent: PhotoLibraryMoviePickerView
init(_ parent: PhotoLibraryMoviePickerView) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
parent.dismiss()
guard let provider = results.first?.itemProvider else {
return
}
let typeIdentifier = UTType.movie.identifier
if provider.hasItemConformingToTypeIdentifier(typeIdentifier) {
provider.loadFileRepresentation(forTypeIdentifier: typeIdentifier) { url, error in
if let error = error {
print("error: \(error)")
return
}
if let url = url {
let fileName = "\(Int(Date().timeIntervalSince1970)).\(url.pathExtension)"
let newUrl = URL(fileURLWithPath: NSTemporaryDirectory() + fileName)
try? FileManager.default.copyItem(at: url, to: newUrl)
self.parent.movieUrl = newUrl
}
}
}
}
}
parent
PHPickerViewControllerのイベント伝達を受ける変数になります。
let parent: PhotoLibraryMoviePickerView
picker(_:, didFinishPicking:)
PHPickerViewControllerでメディアが選択された時に呼ばれるメソッドです。PHPickerViewControllerでは、didCancelのようなメソッドはなく、キャンセルが押された際もこのメソッドが呼ばれます。
なので、メソッドが呼ばれた場合にまずdismissを行い、メディアのURLが受け取れた場合は、値をバインディングするようにしています。
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
parent.dismiss()
guard let provider = results.first?.itemProvider else {
return
}
let typeIdentifier = UTType.movie.identifier
if provider.hasItemConformingToTypeIdentifier(typeIdentifier) {
provider.loadFileRepresentation(forTypeIdentifier: typeIdentifier) { url, error in
if let error = error {
print("error: \(error)")
return
}
if let url = url {
let fileName = "\(Int(Date().timeIntervalSince1970)).\(url.pathExtension)"
let newUrl = URL(fileURLWithPath: NSTemporaryDirectory() + fileName)
try? FileManager.default.copyItem(at: url, to: newUrl)
self.parent.movieUrl = newUrl
}
}
}
}
今回は動画を取得したいのでtypeIdentifierには、オーディオとビデオを含むメディアを表すUTType.movie.identifierを代入しています。
provider.hasItemConformingToTypeIdentifier(typeIdentifier)でオーディオとビデオを含むメディアであるかを判定して、該当メディアである場合はURL取得処理を進めていきます。
provider.loadFileRepresentation(forTypeIdentifier:)で取得したファイルのデータのコピーを一時ファイルに書き込みます。一時ファイルは、完了ハンドラーが戻ったときにシステムが削除する為、ファイルのデータをTemporaryDirectoryにコピーしています。
let fileName = "\(Int(Date().timeIntervalSince1970)).\(url.pathExtension)" let newUrl = URL(fileURLWithPath: NSTemporaryDirectory() + fileName) try? FileManager.default.copyItem(at: url, to: newUrl) self.parent.movieUrl = newUrl
これでPHPickerViewControllerを使用してフォトライブラリから動画を取得出来ました。
MoviePlayerView
動画の再生には、AVKitのVideoPlayerを使用します。引数にAVPlayerを渡すことでコンテンツの再生を制御出来ます。またVideoPlayerには閉じるボタンが無いため、Viewの上部に閉じるボタンを追加しました。
import SwiftUI
import AVKit
struct MoviePlayerView: View {
@Environment(\.dismiss) private var dismiss
private let movieUrl: URL?
init(with movieUrl: URL?) {
self.movieUrl = movieUrl
}
var body: some View {
VStack {
HStack {
Spacer()
Button {
dismiss()
} label: {
Text("Close")
}
Spacer()
.frame(width: 16)
}
VideoPlayer(player: AVPlayer(url: movieUrl!))
}
}
}
ContentView
各Pickerからの動画URLを保持するState変数movieUrlと、各PickerとMoviePlayerViewを表示するかどうかのフラグのBool値を用意しています。
各Picker用のButtonを押すと、それに紐づくPickerが表示されます。movieUrlがnilでない場合は、再生ボタンが活性化し、押すと動画が再生されます。
import SwiftUI
struct ContentView: View {
@State private var movieUrl: URL?
@State private var showCameraMoviePickerView = false
@State private var showPhotoLibraryMoviePickerView = false
@State private var showMoviePlayerView = false
private var canPlayVideo: Bool {
movieUrl != nil
}
var body: some View {
VStack(spacing: 32) {
Spacer()
Button {
showCameraMoviePickerView = true
} label: {
Text("Camera Movie Picker")
}
Button {
showPhotoLibraryMoviePickerView = true
} label: {
Text("Photo Library Movie Picker")
}
Button {
showMoviePlayerView = true
guard let url = movieUrl else {
return
}
print(url)
} label: {
Image(systemName: "play")
.resizable()
.frame(width: 50,
height:50)
.foregroundColor(canPlayVideo ? .accentColor : .gray)
}
.disabled(!canPlayVideo)
Spacer()
}
.fullScreenCover(isPresented: $showCameraMoviePickerView) {
CameraMoviePickerView(movieUrl: $movieUrl)
}
.fullScreenCover(isPresented: $showPhotoLibraryMoviePickerView) {
PhotoLibraryMoviePickerView(movieUrl: $movieUrl)
}
.fullScreenCover(isPresented: $showMoviePlayerView) {
MoviePlayerView(with: movieUrl)
}
}
}
これで完成です!
おわりに
これまではUIImagePickerControllerを使っていましたが、これからは積極的にPHPickerViewControllerを使っていきたいですね!

参考
- UIImagePickerController.SourceType.photoLibrary
- UIImagePickerController
- PHPickerViewController
- preferredAssetRepresentationMode
- PHPickerViewControllerを使った動画の読み込み方法
- SwiftUI - How to load a video from Photos
- [SwiftUI][iOS]UIKitの使い方(2)〜Coordinator(コーディネータ)編〜
- movie
- loadFileRepresentation(forTypeIdentifier:completionHandler:)










