100 Days of SwiftUI(DAY 64(Project13 part 3))

コーディネーターを使用してSwiftUIビューコントローラーを管理する
Using coordinators to manage SwiftUI view controllers

SwiftUIのコーディネーターは、UIKitビューコントローラーのデリゲートとして機能するように設計されています。「デリゲート」は、他の場所で発生するイベントに応答するオブジェクトであることを忘れないでください。たとえば、UIKitを使用すると、デリゲートオブジェクトをテキストフィールドビューにアタッチできます。そのデリゲートは、ユーザーが何かを入力したとき、ユーザーがReturnキーを押したときなどに通知されます。これは、UIKit開発者が独自のカスタムテキストフィールドタイプを作成しなくても、テキストフィールドの動作を変更できることを意味しました。

ここで必要なのは、SwiftUIの@Bindingプロパティラッパーです。これを使用して、作成したものImagePickerまでバインドを作成できます。これは、画像ピッカーでバインディング値を設定し、実際に他の場所(ContentViewたとえば)に格納されている値を更新できることを意味します。

ImagePickerために、画像が選択されたときにレポートを返すことができるようにビューをアップグレードします。

//
//  ImagePicker.swift
//  pro13-part2
//
//  Created by Naoki Abe on 2020/08/22.
//  Copyright © 2020 Naoki Abe. All rights reserved.
//

import Foundation
import SwiftUI

//struct ImagePicker: UIViewControllerRepresentable {
//    typealias UIViewControllerType = UIImagePickerController
//}
////Type 'ImagePicker' does not conform to protocol 'UIViewControllerRepresentable'
////Do you want to add protocol stubs?
//// Fix ↓


struct ImagePicker: UIViewControllerRepresentable {
    //ここで必要なのは、SwiftUIの@Bindingプロパティラッパーです。これを使用して、作成したものImagePickerまでバインドを作成できます。これは、画像ピッカーでバインディング値を設定し、実際に他の場所(ContentViewたとえば)に格納されている値を更新できることを意味します。
    @Binding var image: UIImage?
    //プロパティをに追加してImagePicker、プログラムでビューを閉じることができるようにします。
    @Environment(\.presentationMode) var presentationMode
    
    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 uiImage = info[.originalImage] as? UIImage {
                parent.image = uiImage
            }
            parent.presentationMode.wrappedValue.dismiss()
        }
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIViewController(context: Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.delegate = context.coordinator
        return picker
    }
    
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
    }
    
    
    typealias UIViewControllerType = UIImagePickerController
}



import SwiftUI

struct ContentView: View {
    @State private var image: Image?
    @State private var showingImagePicker = false
    @State private var inputImage: UIImage?
    
    func loadImage() {
        guard let inputImage = inputImage else { return }
        image = Image(uiImage: inputImage)
    }

    var body: some View {
        VStack {
            image?
                .resizable()
                .scaledToFit()

            Button("Select Image") {
               self.showingImagePicker = true
            }
        }
        .sheet(isPresented: $showingImagePicker, onDismiss: loadImage) {
            ImagePicker(image: self.$inputImage)
        }
    }
}


ユーザーの写真ライブラリに画像を保存する方法
How to save images to the user’s photo library

ユーザーの画像を処理すると、UIImage戻ってきますが、処理した画像を保存する方法が必要です。

UIImageWriteToSavedPhotosAlbum()

コードを記述する前に、プロジェクトのInfo.plistファイルに1つの小さな変更を加える必要があります。ご覧のとおり、フォトライブラリへの書き込みは保護された操作です。つまり、ユーザーからの明示的な許可なしにこれを行うことはできません。

Info.plist
select “Privacy – Photo Library Additions Usage Description”
文字列:We want to save the filtered photo.

import SwiftUI

struct ContentView: View {
    
    @State private var image: Image?
    @State private var showingImagePicker = false
    @State private var inputImage: UIImage?
    
    class ImageSaver: NSObject {
        func writeToPhotoAlbum(image: UIImage) {
            UIImageWriteToSavedPhotosAlbum(image, self, #selector(saveError), nil)
        }

        @objc func saveError(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
            print("Save finished!")
        }
    }
    
    func loadImage() {
        guard let inputImage = inputImage else { return }
        image = Image(uiImage: inputImage)
        // for print!!
        //UIImageWriteToSavedPhotosAlbum(inputImage, nil, nil, nil)
        let imageSaver = ImageSaver()
        imageSaver.writeToPhotoAlbum(image: inputImage)
    }

    var body: some View {
        VStack {
            image?
                .resizable()
                .scaledToFit()

            Button("Select Image") {
               self.showingImagePicker = true
            }
        }
        .sheet(isPresented: $showingImagePicker, onDismiss: loadImage) {
            ImagePicker(image: self.$inputImage)
        }
    }
}

保存するイメージを最初のパラメーターとしてにUIImageWriteToSavedPhotosAlbum()提供し、次にnil他の3つとして提供しました。
少なくとも最初の2つは重要です。
保存が完了したときに呼び出すメソッドをSwiftに指示します。これにより、保存操作が成功したか失敗したかがわかります。