【Swift】UIImagePickerControllerを使って画像を選択しよう（その３）

# 1. はじめに
こちらの記事の続きです

https://qiita.com/Hiroki01100001/items/c183a67c962cbbdf3340

本記事ではUIKitの **`UIImagePickerController`** クラスを使って、カメラロールから画像を選択する方法について解説します。
全部で3つの記事があり、本記事はその**3番目**の記事となります。

:::note warn
本文の内容は、ChatGPTに質問して得た回答を下敷きに、公式ドキュメント等で裏を取りながら執筆しました。
また、英文の和訳はDeepL翻訳を用いて訳したものを記載しています。
:::
:::note info
SwiftUIには、もっと手軽にカメラロールを扱うことができる **`PhotosPicker`** という構造体があります。

「別にUIKitを使わなくてもいいよ」という方はここで読むのをやめて、以下の解説サイトを閲覧されることをおすすめします:thumbsup:

 [【SwiftUI】iOS16+のPhotosPicker｜フルSwiftUIでのフォトピッカー実装方法](https://blog.code-candy.com/swiftui_photospicker/)
:::

# 2. 全体の構成
|タイトル  |概要  |
|---|---|
|[UIImagePickerControllerを使って画像を選択しよう<br>（その１）](https://qiita.com/Hiroki01100001/items/e98c76ad0406263e198b)|サンプルコードの全体像<br>UIViewControllerRepresentable|
|[UIImagePickerControllerを使って画像を選択しよう<br>（その２）](https://qiita.com/Hiroki01100001/items/c183a67c962cbbdf3340)|Coordinatorの作成|
|UIImagePickerControllerを使って画像を選択しよう<br>（その３）|デリゲートの設定<br>その他|

# 3. 目次
- [1. はじめに](#1-はじめに)
- [2. 全体の構成](#2-全体の構成)
- [3. 目次](#3-目次)
- [4. サンプルコード](#4-サンプルコード)
- [5. Coordinatorクラスのインスタンス化](#5-coordinatorクラスのインスタンス化)
- [6. makeCoordinatorメソッドの性質](#6-makecoordinatorメソッドの性質)
- [7. makeCoordinatorメソッドの実装](#7-makecoordinatorメソッドの実装)
- [8. ビューコントローラーにデリゲートを設定する](#8-ビューコントローラーにデリゲートを設定する)
- [9. サンプルコードのライフサイクルについて](#9-サンプルコードのライフサイクルについて)
- [10. おまけ Coordinatorクラスのインスタンス化の失敗例](#10-おまけ-coordinatorクラスのインスタンス化の失敗例)
- [11. おわりに](#11-おわりに)

<br>

# 4. サンプルコード
```swift:ContentView.swift
import SwiftUI

struct ContentView: View {
    @State private var image : UIImage?
    @State var isShowing = false

    var body : some View {
        VStack {
            Button(action : {
                isShowing = true
            }){
                Text("Select Image")
            }
            .sheet(isPresented : $isShowing){
                ImagePickerView(image : $image)
            }
            if let image = image {
                Image(uiImage : image)
                    .resizable()
                    .frame(width : 350, height: 350)
            }   
        }
    }
}

struct ContentView_Previews : PreviewProvider {
    static var previews : some View {
        ContentView()
    }
}
```
:::note warn
本記事では、**ContentView.swift** に関する説明を割愛させていただきます。
ご了承ください。
:::


↓ 以下、 **ImagePickerView.swift** について説明します
``` swift:ImagePickerView.swift
import SwiftUI
import UIKit

struct ImagePickerView : UIViewControllerRepresentable {
    @Binding var image : UIImage?

    typealias UIViewControllerType = UIImagePickerController

    class Coordinator : NSObject, 
                        UINavigationControllerDelegate,
                        UIImagePickerControllerDelegate {
        var parent : ImagePickerView

        init(parent : ImagePickerView){
            self.parent = parent
        }

        func imagePickerController(_ picker : UIImagePickerController, 
         didFinishPickingMediaWithInfo info : [UIImagePickerController.InfoKey : Any]){
            if let selectedImage = info[.originalImage] as? UIImage {
                parent.image = selectedImage
            }
            picker.dismiss(animated : true)
        }

        func imagePickerControllerDidCancel(_ picker : UIImagePickerController){
            picker.dismiss(animated : true)
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(parent : self)
    }

    func makeUIViewController(context : Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.delegate = context.coordinator
        return picker
    }

    func updateUIViewController(_ uiViewController : UIImagePickerController, context : Context){}
}
```
<br>

# 5. Coordinatorクラスのインスタンス化


前回の記事では、`Coordinator`のクラスを実装しました。
今回の記事では、`Coordinator`のインスタンスを作り、ビューコントローラーのデリゲートとして設定したいと思います。

:::note info
本記事の「ビューコントローラー」とは`UIImagePickerController()`クラスのインスタンスを意味します。
:::

ここで注意点が一つ。
クラスのインスタンスを作る時は`var 変数名 = クラス名()`と書くのが定番ですが、`Coordinator`のインスタンスを作る時には、**この方式で書いてはいけません**。
なぜなら`delegate`プロパティは**弱参照**だから。以下のコード（失敗例）をご覧ください。

```diff_swift: ※失敗例(マネしないでください)
func makeUIViewController(context : Context) -> UIImagePickerController {
     let picker = UIImagePickerController()
+    picker.delegate = Coordinator()
     return picker
}
```
`Coordinator`インスタンスは、ビューコントローラー`picker`のプロパティ、`delegate`の参照先として生成されています。通常であれば、何の問題もないコードですが、`delegate`プロパティは**弱参照**のプロパティとなっています。
弱参照の参照先として生成されたインスタンスは、生成された直後に**消失**してしまう運命にあります。よって、`picker.delegate = Coordinator()`で生成されたインスタンスは、実際にはまったく使い物になりません。

それでは、どうしたら`Coordinator`インスタンスをビューコントローラーのデリゲートとして設定することができるのか？
この問題を解決するのが、 **`makeCoordinator()`** メソッドです。

# 6. makeCoordinatorメソッドの性質
**`makeCoordinator()`** メソッドは、その名のとおり、`Coordinator`のインスタンスを生成するメソッドです。
ただ、それだけでは、普通にインスタンス化するのとなんら変わらないですよね。
もちろん、`makeCoordinator()`はインスタンスを生成するだけのメソッドではありません。
`makeCoordinator()`ならではの性質があるからこそ、わざわざメソッドとして用意されているわけです。では、その性質とは何なのか？

公式ドキュメントで説明されているので引用します。
>SwiftUI calls this method before calling the makeUIViewController(context:) method. The system provides your coordinator either directly or as part of a context structure when calling the other methods of your representable instance.
>
>(訳)
>SwiftUIはmakeUIViewController(context:)メソッドを呼び出す前にこのメソッドを呼び出します。システムはあなたのコーディネータを直接、もしくはあなたの表現可能なインスタンスの他のメソッドを呼び出すときにコンテキスト構造の一部として提供します。
>
>引用元：[公式ドキュメント](https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable/makecoordinator()-32trb)

ここで注目すべきは以下の２箇所です。
- SwiftUIはmakeUIViewController(context:)メソッドを呼び出す前にこのメソッドを呼び出します
- (コーディネーターを)コンテキスト構造の一部として提供します

この記述から、`makeCoordinator()`は以下の3つの性質を持っていることがわかります。

 (1) **SwiftUIによって自動的に実行されるメソッドである**
 (2) **`makeUIViewController(context:)`メソッドより先に実行される**
 (3) **コンテクスト構造体のプロパティとしてコーディネーターを生成する**

この３つの性質について、順番に見ていきましょう。

### (1) SwiftUIによって自動的に実行されるメソッドである
これは`makeUIViewController(context:)`メソッドにも見られた性質ですね。
SwiftUIによって自動的に実行されるので、メソッドを定義しさえすれば、呼び出すコードを書かなくても処理が実行されるようになるというわけです。

### (2) `makeUIViewController(context:)`メソッドより先に実行される
これは実質、`ImagePickerView`構造体の中で一番最初に実行される関数は`makeCoordinator()`メソッドだということです。`ImagePickerView()`のインスタンスが画面に表示されるとき、SwiftUIはまず`makeCoordinator()`を実行し、`Coordinator`のインスタンスを生成します。

### (3) コンテクスト構造体のプロパティとしてコーディネーターを生成する
ここでいう「コンテクスト構造体」とは、構造体　**`UIViewControllerRepresentableContext`**　のことです。また。「コーディネーター」とは **`Coordinator`クラスのインスタンス**を意味します。

:::note info
**UIViewControllerRepresentableContext**

システムの現在の状態に関する情報を格納し、UIViewControllerの作成と更新のため使用される構造体のこと。
ビューコントローラーを作成したり更新したりする際に、システムは`UIViewControllerRepresentableContext`のインスタンスを作成し、それを適切なメソッドに渡します。
`UIViewControllerRepresentableContext`はコーディネーターを格納する **`coordinator`** プロパティのほか、ビューのアニメーションを可能にする`transaction`プロパティや、システムの現在の状態を表すプロパティ`environment`などを所持しています。
:::

`makeCoordinator()`メソッドは、`Coordinator`インスタンス（=**コーディネーター**）を生成すると、`UIViewControllerRepresentableContext`の`coordinator`プロパティとしてコーディネーターを設定します。
これにより、私たちは、`makeUIViewController(context:)`メソッドや`updateUIViewController(_:context:)`メソッド内で、`context.coordinator`を使用してコーディネーターにアクセスすることができるようになります。

# 7. makeCoordinatorメソッドの実装

`makeCoordinator()`メソッドについて概ね理解できたところで、実装に進んでみましょう。以下のとおり、コードを追加してください。

```diff_swift:ImagePickerView.swift
import SwiftUI
import UIKit

struct ImagePickerView : UIViewControllerRepresentable {
    @Binding var image : UIImage?
    
    typealias UIViewControllerType = UIImagePickerController
    
    class Coordinator : NSObject,
                        UINavigationControllerDelegate,
                        UIImagePickerControllerDelegate {
                        
        var parent : ImagePickerView
        init(parent : ImagePickerView){
            self.parent = parent
        }
        
        func imagePickerController(_ picker : UIImagePickerController,
                                   didFinishPickingMediaWithInfo info : [UIImagePickerController.InfoKey : Any]){
            if let selectedImage = info[.originalImage] as? UIImage {
                parent.image = selectedImage
            }
            picker.dismiss(animated : true)
        }
        
        func imagePickerControllerDidCancel(_ picker : UIImagePickerController){
            picker.dismiss(animated : true)
        }
    }
    
+    func makeCoordinator() -> Coordinator {
+        Coordinator(parent : self)
+    }
    
    func makeUIViewController(context : Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.delegate
        return picker
    }
    
    func updateUIViewController(_ uiViewController : UIImagePickerController, context : Context){}
}
```
ここでは、さきほど触れたとおり、`Coordinator`クラスのインスタンス(コーディネーター)の生成を行っております。`Coordinator(parent : self)`の箇所の **`self`** とは、構造体`ImagePickerView`を指します。
生成されたコーディネーターは`UIViewControllerRepresentabelContext`の`coorinator`プロパティに格納されます。

# 8. ビューコントローラーにデリゲートを設定する
ここでは、`makeUIViewController(context:)`で作成したビューコントローラーに、デリゲートを設定する方法について解説します。
[その１](https://qiita.com/Hiroki01100001/items/e98c76ad0406263e198b)で、`makeUIViewController(context:)`メソッドの中に`picker.delegate`を参照するコードを書いたことを覚えているでしょうか？

```diff_swift: ImagePickerView.swift（※その１より抜粋）
import SwiftUI
import UIKit

struct ImagePickerView : UIViewControllerRepresentable {
    @Binding var image : UIImage?

    typealias UIViewControllerType = UIImagePickerController

    func makeUIViewController(context : Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
+       picker.delegate
        return picker
    }

    func updateUIViewController(_ uiViewController : UIImagePickerController, context : Context){}
}
```

この長らく宙ぶらりんの状態になっていた`picker`の`delegate`プロパティに対して、　コーディネーターを設定します。以下のとおり、コードを追記してください。

``` diff_swift:ImagePickerView.swift
import SwiftUI
import UIKit

struct ImagePickerView : UIViewControllerRepresentable {
    @Binding var image : UIImage?

    typealias UIViewControllerType = UIImagePickerController

    class Coordinator : NSObject, 
                        UINavigationControllerDelegate,
                        UIImagePickerControllerDelegate {
        var parent : ImagePickerView

        init(parent : ImagePickerView){
            self.parent = parent
        }

        func imagePickerController(_ picker : UIImagePickerController, 
         didFinishPickingMediaWithInfo info : [UIImagePickerController.InfoKey : Any]){
            if let selectedImage = info[.originalImage] as? UIImage {
                parent.image = selectedImage
            }
            picker.dismiss(animated : true)
        }

        func imagePickerControllerDidCancel(_ picker : UIImagePickerController){
            picker.dismiss(animated : true)
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(parent : self)
    }

    func makeUIViewController(context : Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
+        picker.delegate = context.coordinator
        return picker
    }

    func updateUIViewController(_ uiViewController : UIImagePickerController, context : Context){}
}
```

`picker.delegate = context.coordinator`となっているのは、コーディネーターのインスタンスが、`UIViewRepresentabelContext`の`coorinator`プロパティに保存されているからです。

これで、ビューコントローラーのインスタンスとデリゲートの紐付けが完了しました。
コードとしてはこれで完成となります:sparkles:

カメラロールで画像を選ぶと、画面に表示されることをお確かめください:bow:
<br>

# 9. サンプルコードのライフサイクルについて

記事の最後に、より理解を深めるため、サンプルコードのライフサイクルがどうなっているのか順番に見ていきたいと思います。

## 1. インスタンスの生成
SwiftUIで`ImagepickerView`が表示されると、`ImagePickerView`のインスタンスが生成されます。この時点で`@Binding var image : UIImage?`が初期化されます。

## 2. コーディネーターの生成
```swift
func makeCoordinator() -> Coordinator {
    Coordinator(parent : self)
}
```
SwiftUIは`makeCoordinator()`メソッドを呼び出し、コーディネーター(=`Coordinator`クラスのインスタンス)を生成します。生成されたコーディネーターは構造体`UIViewControllerRepresentableContext`のプロパティとして格納されます。

### 3.ビューコントローラーの生成
```swift
 func makeUIViewController(context : Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.delegate = context.coordinator
        return picker
    }
```
SwiftUIは`makeUIViewController(context:)`メソッドを呼び出し、ビューコントローラー（＝`UIImagePickerController`クラスのインスタンス）を生成します。
ビューコントローラーには、デリゲートとしてコーディネーターが紐づけられます。
生成されたビューコントローラーは、SwiftUIのビューの中に組み込まれます。

## 4.ビューコントローラーの更新
```swift
func updateUIViewController(_ uiViewController : UIImagePickerController, context : Context){}
```
ビューコントローラーが更新された場合には、`updateUIViewController(_:context:)`メソッドが呼び出され、`UIImagePickerController`を更新します。...とは書いていますが、今回はビューコントローラーの更新を行う必要がないため、サンプルコードを動かしても、`updateUIViewController(_:context:)`の処理が実行されることはありません。

## 5.ユーザーの操作結果を反映する
```swift
func imagePickerController(_ picker : UIImagePickerController,
    didFinishPickingMediaWithInfo info : [UIImagePickerController.InfoKey : Any]){
    if let selectedImage = info[.originalImage] as? UIImage {
        parent.image = selectedImage
    }
    picker.dismiss(animated : true)
}
```
ユーザーが画像を選択すると、コーディネーターの`imagePickerController()`メソッドが呼び出されます。このメソッドによって、選択された画像が`@Binding`変数である`image`に格納され、ビューコントローラーが閉じられます。
<br>

# 10. おまけ Coordinatorクラスのインスタンス化の失敗例
[5. Coordinatorクラスのインスタンス化](#5-coordinatorクラスのインスタンス化)では、`Coordinator`クラスのインスタンスを作る時に`var 変数名 = クラス名()`と書いてはいけないと述べていましたが、なぜ`var 変数名 = クラス名（）`の書き方ではいけないのかをまとめましたので、おまけとして掲載します。

以下は失敗例としてのサンプルコードです。
```swift: ImagePickerView.swift　※失敗例（マネしないでください）
import SwiftUI
import UIKit

struct ImagePickerView : UIViewControllerRepresentable {
    @Binding var image : UIImage?

    typealias UIViewControllerType = UIImagePickerController

    class Coordinator : NSObject, 
                        UINavigationControllerDelegate,
                        UIImagePickerControllerDelegate {
        var parent : ImagePickerView

        init(parent : ImagePickerView){
            self.parent = parent
        }

        func imagePickerController(_ picker : UIImagePickerController, 
         didFinishPickingMediaWithInfo info : [UIImagePickerController.InfoKey : Any]){
            if let selectedImage = info[.originalImage] as? UIImage {
                parent.image = selectedImage
            }
            picker.dismiss(animated : true)
        }

        func imagePickerControllerDidCancel(_ picker : UIImagePickerController){
            picker.dismiss(animated : true)
        }
    }

    func makeUIViewController(context : Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.delegate = Coordinator()
        return picker
    }

    func updateUIViewController(_ uiViewController : UIImagePickerController, context : Context){}
}
```

実際、XCodeで書いてみた画像がこちらです。
![スクリーンショット 2024-08-09 1.02.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589104/99c14dfd-a4d2-9b62-a7d2-e982549d183f.png)

警告が出ているので、コードに問題があるのは一目瞭然ですね。
なぜ警告が表示されるのかというと、以下の２つの問題があるからです。

1. `UIViewControllerRepresentable`プロトコルに準拠していない
1. `UIImagePickerController`のプロパティ`delegate`は`Coordinator`インスタンスを保持できない

それぞれ簡単に説明します。
#### 問題1　`UIViewControllerRepresentable`プロトコルの条件を満たしていない

問題１は以下のコンパイルエラーの原因となっています。
>`Type 'ImagePickerView' does not conform to protocol 'UIViewControllerRepresentable'`
>
>(訳)
>タイプ 'ImagePickerView' はプロトコル  'UIViewControllerRepresentable' に準拠していません。

`UIViewControllerRepresentable`に準拠するためには、以下の３つのメソッドを実装しなくてはなりません。

**1. makeUIViewController(context:)**
**2. updateUIViewController(_:context:)**
**3. makeCoordinator()**

公式ドキュメントの`UIVireControllerRepresentable`の項目には、`makeCoordinator()`メソッドについて以下のとおり記載されています。

>func makeCoordinator() -> Self.Coordinator
Creates the custom instance that you use to communicate changes from your view controller to other parts of your SwiftUI interface.
Required Default implementation provided.
>
>（訳）ビューコントローラーから SwiftUI インターフェイスの他の部分に変更を伝えるために使うカスタムインスタンスを作成します。
必須 デフォルトの実装が提供されます。

公式ドキュメントに書かれている内容は、つまりこういうことです。
- `makeCoordinator()`メソッドは必ず実装しなければならない
- `makeCoordinator()`メソッドはデフォルトで実装されるようになっているが、カスタムの`Coordinator`が存在する場合には、オーバーライドしないとコンパイルエラーが起きてしまう

サンプルコードでカスタムの`Coordinator`を作ってしまった以上、`makeCoordinator()`をオーバーライドしないと`UViewControllerRepresentable`プロトコルには準拠できないというわけですね。
<br>

#### 問題点2. `UIImagePickerController`のプロパティ`delegate`は`Coordinator`インスタンスを保持できない

問題２は以下の警告メッセージが表示される原因となっています。
>Instance will be immediately deallocated because property 'delegate' is 'weak'
>
>(訳)プロパティ「delegate」が「weak」であるため、インスタンスは即座にデアロケートされる。

この警告メッセージは
- `UIImagePickerController`の`delegate`プロパティが**弱参照**である
- したがって`delegate`プロパティに設定された`Coordinator`インスタンスは、作ったそばからメモリが解放されてしまうため、まったく使い物にならない

ということを意味します。では、**弱参照**とはどういうものでしょうか？

:::note info
**弱参照**とは
クラスのインスタンスを参照しても、参照カウントをカウントアップしない参照方法のことです。
> クラスのインスタンスへの参照には、強参照と弱参照の２種類があります。強参照は参照カウントを１つカウントアップし、弱参照はカウントアップしません。
>
>引用元：
>[増補改訂第３版]Swift実践入門　直感的な文法と安全性を兼ね備えた言語
>P.292
:::

Swiftでは、クラスのインスタンスを生成するとき、インスタンスの情報を記憶させておくために、必要範囲のメモリ領域を確保しますが、このインスタンスに割り当てられたメモリは、以下のように管理されます。

> 使用中のインスタンスのメモリが解放されてしまうことを防ぐために、プロパティ、変数、定数からそれぞれのクラスのインスタンスへの参照がいくつあるかをカウントしています。
このカウントが０になったとき、そのインスタンスはどこからも参照されてないとみなされ、メモリが解放されます。
>
>引用元：
>[増補改訂第３版]Swift実践入門　直感的な文法と安全性を兼ね備えた言語
>P.205　

インスタンスへの参照(**参照カウント**)が０になったとき、メモリが解放される。

これは裏を返すと、「参照カウントが０にならない限り、そのインスタンスのメモリは解放されない」ということです。そのため、お互いに相手を参照し合うような状態（**循環参照**）のインスタンスは永久に残り続けるということになります。

循環参照の状態になったインスタンスは、メモリを圧迫し、アプリのパフォーマンス低下を招きます。なので私たちは循環参照が起きるのを避けなくてはなりません。
**弱参照**は、以下のように参照カウントをカウントアップしないことで、この循環参照を回避することができます。

- 通常、クラスのインスタンスを参照するときは参照カウントを＋１するようになっている
↓<br>
- ２つのインスタンスがお互いに参照しあうと、参照カウントが無限に増えていく循環参照の状態に陥ってしまう
↓<br>
- クラスの参照方法を弱参照に変える
↓<br>
- いくら参照がループしたとしても、弱参照は参照カウントをカウントアップしないので、参照カウントを０のままに留めておくことができるようになる



:::note info
弱参照は、インスタンスを参照するプロパティに`weak`キーワードをつけることで、設定することができます。
:::

もう一度、問題２の警告メッセージを見てみましょう。
>Instance will be immediately deallocated because property 'delegate' is 'weak'
>
>(訳)
>プロパティ「delegate」が「weak」であるため、インスタンスは即座にデアロケートされる。

（`UIImagePickerController()`の）`delegate`プロパティは**弱参照**であると、はっきり書かれてありますね。このことを踏まえて間違った書き方の例を見てみましょう。

```swift: ※失敗例(マネしないでください)
let picker = UIImagePickerController()
picker.delegate = Coordinator()
return picker
```
間違った書き方では、`delegate`プロパティは`picker.delegate = Coordinator()`というコードで設定されています。
`delegate`は弱参照のプロパティです。よって、`delegate`の値として`Coordinator`のインスタンスを生成したとしても、参照カウントは0のまま増えることはありません。
`Coordinator`のインスタンスは参照カウントが０のため、どこからも参照されていないとみなされます。
その結果、メモリが解放され、`Coordinator`インスタンスは生成された直後に消去されてしまうことになるのです。

以上の理由から、`var 変数名 = クラス名（）`という書き方で`Coordinator`クラスのインスタンスを生成してはいけないということになります。奥が深いですね。

# 11. おわりに
最後までお読みいただき、ありがとうございました:bow:
SwiftUI上でUIKitを使うコードは、コード自体は短い割に意外と内容が難しく、ChatGPT先生がいなかったら完全にお手上げな状態でした:sweat_smile:　公式ドキュメントを読み解く良い練習にもなったので、これからも公式を参照することを習慣にしたいと思います。
お疲れ様でした:tada: