0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SwiftUI 2.0と UIKit と ストーリーボード (パート 2)

Last updated at Posted at 2021-05-19

パート 2: SwiftUI 2.0 と UIKit

記事は3つの異なる部分に分かれている:

パート 2は、SwiftUI2.0アプリケーションでのUIKit要素の使用についてです。

SwiftUIビューからUIKitビューの使用

SwiftUIでUIKitビューとビューコントローラーを使用できるようにするには、UIViewRepresentableおよびUIViewControllerRepresentableプロトコルに準拠するタイプでラップする必要があります。

XCodeプロジェクト

新しい__SwiftUI__アプリプロジェクトを作成する
image.png
プロジェクトに名前を付ける
image.png
新しい__Swift__ファイルを作成する
image.png
ファイルに名前を付ける
image.png
次に、UIKitコンポーネントをラップするためにUIViewControllerRepresentableプロトコルに準拠するタイプを作成する必要があります...

ImagePicker: UIViewControllerRepresentableに準拠するタイプ

まず、UIImagePickerControllerをラップするImagePickerタイプを作成しましょう。

UIViewControllerRepresentableプロトコルには、次の2つの要件があります:

ImagePicker.swift
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 を作成しましょう:

Swift
    let imagePicker = UIImagePickerController()

次に、それを設定しましょう:

  • 保存した写真や動画から選択するためのユーザーインターフェイスを設定します_(新しい写真や動画を撮影するためのユーザーインターフェイスを設定するには、代わりに.cameraを使用します)_:
Swift
    imagePicker.sourceType = .photoLibrary
  • メディアピッカーコントローラーで、アクセスするメディアタイプを写真と動画に指定します:
Swift
    imagePicker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary) ?? ["public.image", "public.movie"]
  • 選択した静止画像と動画の編集を無効にします:
Swift
    imagePicker.allowsEditing = false
  • ビデオ録画とトランスコーディングの品質を設定します:
Swift
    imagePicker.videoQuality = .typeHigh
  • イメージピッカーのイベント通知を処理するイメージピッカーのデリゲートオブジェクトを設定します:
Swift
    imagePicker.delegate = context.coordinator

※ 次のセクションでデリゲートオブジェクトについて説明します...

Swift
    return imagePicker

最後に、ImagePickerの作成時にソースタイプを変更できるようにするには、makeUIViewController(context:)メソッドでの設定中に使用されるデフォルトの.photoLibraryソースタイプを保持する変数を定義します:

Swift
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のソースタイプは、作成時に次のように変更できます:

SwiftUI
ImagePicker(sourceType: .camera, /* ... */)

Coordinator:UIImagePickerControllerのデリゲート

ビューコントローラ内で発生する変更をSwiftUIインターフェイスの他の部分に伝達するには、カスタムコーディネータオブジェクトをSwiftUIビューに提供する必要があります。

UIKitコンポーネントの変更を聞くために、デリゲートを使用する。デリゲートは、イベント自体がシステムによって処理される前または後にイベント通知を処理するために使用されます。

また、UIImagePickerControllerの変更を聞くには、CoordinatorUIImagePickerControllerDelegateプロトコルとUINavigationControllerDelegateプロトコルに準拠する必要があります。また、NSObjectから継承して、Swiftで宣言できないNSObjectProtocolプロトコルに準拠する必要があります。

変更が観察されるImagePickerインスタンスへの参照を設定するコンストラクターを使用してCoordinatorタイプを宣言しましょう:

Swift
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オブジェクトが含まれます。
Swift
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へのバインディングを使用することを選択しました。

Swift
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の配列に追加する前に、まず一時ファイルをディスクに書き込む必要があります:

Swift
    // ...
    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を使用してピッカーを閉じることです:

Swift
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オブジェクト

提供されているデフォルトの実装の代わりに独自のコーディネーターオブジェクトを提供するには、ImagePickermakeCoordinator()メソッドを実装する必要があります:

Swift
struct ImagePicker: UIViewControllerRepresentable {
    // ...
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    // ...
}

ImagePickerのソースコード

ImagePicker.swift
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を表示します:

ContentView.swift
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()
    }
}

アプリ

アプリを起動し、コンテンツビューで、ボタンをクリックして画像ピッカーを開きます。
test-uikit-00.png
ピッカーで、画像を選択します:
test-uikit-01.png
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要素の使用に関するこの記事は終わりです。

参照

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?