パート 2: SwiftUI 2.0 と UIKit
記事は3つの異なる部分に分かれている:
- パート 1: SwiftUI 2.0 と ストーリーボード
- パート 2: SwiftUI 2.0 と UIKit (English)
- パート 3: ストーリーボード と SwiftUI 2.0
パート 2は、SwiftUI2.0アプリケーションでのUIKit要素の使用についてです。
SwiftUIビューからUIKitビューの使用
SwiftUIでUIKitビューとビューコントローラーを使用できるようにするには、UIViewRepresentableおよびUIViewControllerRepresentableプロトコルに準拠するタイプでラップする必要があります。
XCodeプロジェクト
新しい__SwiftUI__アプリプロジェクトを作成する
プロジェクトに名前を付ける
新しい__Swift__ファイルを作成する
ファイルに名前を付ける
次に、UIKitコンポーネントをラップするためにUIViewControllerRepresentable
プロトコルに準拠するタイプを作成する必要があります...
ImagePicker: UIViewControllerRepresentable
に準拠するタイプ
まず、UIImagePickerController
をラップするImagePicker
タイプを作成しましょう。
UIViewControllerRepresentable
プロトコルには、次の2つの要件があります:
-
UIViewController
を作成および設定するmakeUIViewController(context:)
メソッド - 指定されたビューコントローラーの状態をSwiftUIからの新しい情報で更新する
updateUIViewController(_:context:)
メソッド。この例では使用しないため、空白のままにします。
import Foundation
import SwiftUI
import AVKit
struct ImagePicker: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
// ...
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
}
次に、makeUIViewController(context:)
メソッドでUIImagePickerController
を作成しましょう:
let imagePicker = UIImagePickerController()
次に、それを設定しましょう:
- 保存した写真や動画から選択するためのユーザーインターフェイスを設定します_(新しい写真や動画を撮影するためのユーザーインターフェイスを設定するには、代わりに
.camera
を使用します)_:
imagePicker.sourceType = .photoLibrary
- メディアピッカーコントローラーで、アクセスするメディアタイプを写真と動画に指定します:
imagePicker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary) ?? ["public.image", "public.movie"]
- 選択した静止画像と動画の編集を無効にします:
imagePicker.allowsEditing = false
- ビデオ録画とトランスコーディングの品質を設定します:
imagePicker.videoQuality = .typeHigh
- イメージピッカーのイベント通知を処理するイメージピッカーのデリゲートオブジェクトを設定します:
imagePicker.delegate = context.coordinator
※ 次のセクションでデリゲートオブジェクトについて説明します...
- そして最後に、
UIImagePickerController
を戻します:
return imagePicker
最後に、ImagePicker
の作成時にソースタイプを変更できるようにするには、makeUIViewController(context:)
メソッドでの設定中に使用されるデフォルトの.photoLibrary
ソースタイプを保持する変数を定義します:
struct ImagePicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType = .photoLibrary
// ...
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
// ...
imagePicker.sourceType = sourceType
imagePicker.mediaTypes = UIImagePickerController.availableMediaTypes(for: sourceType) ?? ["public.image", "public.movie"]
// ...
}
// ...
}
UIImagePickerController
のソースタイプは、作成時に次のように変更できます:
ImagePicker(sourceType: .camera, /* ... */)
Coordinator:UIImagePickerController
のデリゲート
ビューコントローラ内で発生する変更をSwiftUIインターフェイスの他の部分に伝達するには、カスタムコーディネータオブジェクトをSwiftUIビューに提供する必要があります。
UIKitコンポーネントの変更を聞くために、デリゲートを使用する。デリゲートは、イベント自体がシステムによって処理される前または後にイベント通知を処理するために使用されます。
また、UIImagePickerController
の変更を聞くには、Coordinator
がUIImagePickerControllerDelegate
プロトコルとUINavigationControllerDelegate
プロトコルに準拠する必要があります。また、NSObject
から継承して、Swiftで宣言できないNSObjectProtocol
プロトコルに準拠する必要があります。
変更が観察されるImagePicker
インスタンスへの参照を設定するコンストラクターを使用してCoordinator
タイプを宣言しましょう:
struct ImagePicker: UIViewControllerRepresentable {
// ...
final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
// ...
}
// ...
}
imagePickerController(_:didFinishPickingMediaWithInfo:)
「ユーザーが画像または動画を選択しました」というイベント通知をキャッチするには、imagePickerController(_:didFinishPickingMediaWithInfo:)
メソッドを実装して、ユーザーが静止画像または動画を選択したことをデリゲートに通知する必要があります。
そのメソッドの中で、ユーザーが画像または動画を選んだかどうかを確認します:
- ユーザーが画像を選択した場合、
info
パラメーターのUIImagePickerController.InfoKey.originalImage
にはUIImage
オブジェクトが含まれます。 - ユーザーが動画を選んだ場合、
info
パラメーターの.mediaURL
にはURL
オブジェクトが含まれます。
struct ImagePicker: UIViewControllerRepresentable {
// ...
final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
// ...
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
// ...
}
if let videoURL = info[.mediaURL] as? URL {
// ...
}
// ...
}
}
// ...
}
ユーザーが選択したファイルをSwiftUIインターフェースに伝達するために、作成時に引数としてImagePicker
に渡されるURL
の外部Array
へのバインディングを使用することを選択しました。
struct ImagePicker: UIViewControllerRepresentable {
// ...
@Binding var selectedFiles: Array<URL>
final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
// ...
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
// ...
parent.selectedFiles.append(imagePath.absoluteURL)
}
if let videoURL = info[.mediaURL] as? URL {
parent.selectedFiles.append(videoURL)
}
// ...
}
}
// ...
}
ユーザーが動画を選ぶと、URL
が表示されます。これは、URL
の配列に直接追加できます。
ただし、ユーザーが画像を選択するときは、パスの URL
表現をURL
の配列に追加する前に、まず一時ファイルをディスクに書き込む必要があります:
// ...
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
let imageName = UUID().uuidString
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let imagePath = paths[0].appendingPathComponent(imageName)
if let jpegData = image.jpegData(compressionQuality: 0.8) {
try? jpegData.write(to: imagePath)
}
parent.selectedFiles.append(imagePath.absoluteURL)
}
// ...
}
imagePickerController(_:didFinishPickingMediaWithInfo:)
メソッドの最後のステップは、環境プロパティpresentationMode
を使用してピッカーを閉じることです:
struct ImagePicker: UIViewControllerRepresentable {
// ...
@Environment(\.presentationMode) private var presentationMode
final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
// ...
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
// ...
parent.presentationMode.wrappedValue.dismiss()
}
}
// ...
}
カスタムCoordinatorオブジェクト
提供されているデフォルトの実装の代わりに独自のコーディネーターオブジェクトを提供するには、ImagePicker
でmakeCoordinator()
メソッドを実装する必要があります:
struct ImagePicker: UIViewControllerRepresentable {
// ...
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
// ...
}
ImagePickerのソースコード
import Foundation
import SwiftUI
import AVKit
struct ImagePicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType = .photoLibrary
@Binding var selectedFiles: Array<URL>
@Environment(\.presentationMode) private var presentationMode
final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
let imageName = UUID().uuidString
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let imagePath = paths[0].appendingPathComponent(imageName)
if let jpegData = image.jpegData(compressionQuality: 0.8) {
try? jpegData.write(to: imagePath)
}
parent.selectedFiles.append(imagePath.absoluteURL)
}
if let videoURL = info[.mediaURL] as? URL {
parent.selectedFiles.append(videoURL)
}
parent.presentationMode.wrappedValue.dismiss()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let imagePicker = UIImagePickerController()
imagePicker.sourceType = sourceType
imagePicker.mediaTypes = UIImagePickerController.availableMediaTypes(for: sourceType) ?? ["public.image", "public.movie"]
imagePicker.allowsEditing = false
imagePicker.videoQuality = .typeHigh
imagePicker.delegate = context.coordinator
return imagePicker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
}
ContentViewのソースコード
それでは、 ImagePicker
をアプリ ContentView
に追加しましょう。
Button
を使用し、sheet(isPresented:onDismiss:content:)
メソッドを使用してシートでImagePicker
を表示します:
import SwiftUI
struct ContentView: View {
@State var imageFiles = Array<URL>()
@State private var isShowingPhotoLibrary = false
var body: some View {
VStack {
Text("Pick an Image!")
.padding()
Button(action: {
self.isShowingPhotoLibrary = true
}) {
Image(systemName: "photo.fill")
.font(.system(size: 40))
}.sheet(isPresented: self.$isShowingPhotoLibrary) {
ImagePicker(sourceType: .photoLibrary, selectedFiles: $imageFiles)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
アプリ
アプリを起動し、コンテンツビューで、ボタンをクリックして画像ピッカーを開きます。
ピッカーで、画像を選択します:
imagePickerController(_:didFinishPickingMediaWithInfo:)
の最後にprint
ステートメントを追加して、URL
の配列の内容を表示しました。
最初の画像を選択した後:
selectedFiles= [
file:///Users/loic/Library/Developer/CoreSimulator/Devices/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/data/Containers/Data/Application/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/Documents/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
]
2番目の画像を選択した後:
selectedFiles= [
file:///Users/loic/Library/Developer/CoreSimulator/Devices/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/data/Containers/Data/Application/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/Documents/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX,
file:///Users/loic/Library/Developer/CoreSimulator/Devices/YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY/data/Containers/Data/Application/YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY/Documents/YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY
]
やった!
これで、SwiftUIでのUIKit要素の使用に関するこの記事は終わりです。