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

# 1. はじめに
本記事では、UIKitの **`UIImagePickerController`** クラスを使って、カメラロールから画像を選択する方法について解説します。

なぜ`UIImagePickerController`クラスを使うのかというと、そうしないとカメラロールから画像を選択する機能を実装することができないからです。
SwiftUIはまだ完全に成熟しきっていないため、このようにUIKitから部品を借りてこないと実装できない機能があるのです。

:::note info
現在は、SwiftUIにもっと手軽にカメラロールを扱うことができる **`PhotosPicker`** という構造体が用意されています。

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

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

全部で3つの記事があり、本記事はその**１番目**の記事となります。

- UIKitをSwiftUIで使ってみたい

という方には、お役に立てる内容となっているかと思います。
よろしくお願いします。

:::note warn
本文の内容は、ChatGPTに質問して得た回答を下敷きに、公式ドキュメント等で裏を取りながら執筆しました。
また、英文の和訳はDeepL翻訳を用いて訳したものを記載しています。
:::


:::note info
**UIImagePickerController**
 以下の操作を行うためのインターフェースを提供するクラスのこと。
- 写真を撮影する
- ビデオを録画する
- **カメラロールから画像や動画を選択する**
:::
<br>

# 2. 開発環境
- OS : Sonoma 14.5
- Xcode : Version 15.4
- Simulator : Version 15.4
<br>

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

# 4. 目次
[1. はじめに](#1-はじめに)
[2. 開発環境](#2-開発環境)
[3. 全体の構成](#3-全体の構成)
[4. 目次](#4-目次)
[5. 完成イメージ](#5-完成イメージ)
[6. サンプルコード](#6-サンプルコード)
[7. なぜこの記事を書いたのか](#7-なぜこの記事を書いたのか)
[8. サンプルコードの全体像について](#8-サンプルコードの全体像について)
[9. UIViewControllerRepresentable](#9-uiviewcontrollerrepresentable)
[10. ビューコントローラーの作成](#10-ビューコントローラーの作成)
[11. 次の記事へ](#11-次の記事へ)
<br>

# 5. 完成イメージ
![ImagePickerGIF.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589104/defc75e4-9eec-542b-eba4-a9bd6c61b7da.gif)
<br>

# 6. サンプルコード
```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>

# 7. なぜこの記事を書いたのか
今回、「UIImagePickerControllerを使って画像を選択しよう」というタイトルで3つの記事を書いたのは、書くことを通してUIKitに関する技術を理解したかったからです。

SwiftUIとUIKitは、お互いに異なるライフサイクルを持ったフレームワークです。
そのため、SwiftUIの中でUIKitのクラスを使用するには、両者が情報のやり取りができるよう、必要となるコードを**抜かりなく**記述する必要があります。

この過程が非常にややこしい…！  

[サンプルコード](#サンプルコード(完成版))は、初見の私にはまったく意味がわからない代物でした。
これを１から説明できるようになれば、きっとUIKitを扱う技術が身につくはず！
そういう思いから執筆いたしました。

:::note info
今では`PhotosPicker`があるので、UIKitなしでも「カメラロールから画像を選択する機能」を実装することは可能です。
:::
<br>

# 8. サンプルコードの全体像について
[サンプルコード](#サンプルコード(完成版))の **ContentView.swift** と **ImagePickerView.swift** について、超ざっくりと解説します。

**ContentView.swift**
画面表示の見た目を担当する構造体
 
**ImagePickerView.swift**
SwiftUIに組み込むため、`UIImagePickerController`クラスのインスタンスを`UIViewControllerRepresentable`という専用のプロトコルでラッピングした構造体
<br>

サンプルコードの構成図
<img width="700" alt="サンプルコードの構成図" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589104/c526dc8f-1af3-e32a-288f-29f27c490dfd.png">

上の画像は[サンプルコード](#サンプルコード(完成版))の**ImagePickerView.swift**を図で表したものです。

「SwiftUIの世界」の中に、`UIViewControllerRepresentable`プロトコルでラップされた「UIKitの世界」がありますね。文字通りSwiftUIに「組み込まれて」います。

`UIViewControllerRepresentable`プロトコルの囲みの中には、画像選択のインターフェースを提供する`UIImagePickerController`クラスのインスタンスが配置されています。

隣には `Coordinator`という名前の人影がありますね。これはコーディネーターといって、「*coordinator* = 交渉人」の名前のとおり、UIKit側の**交渉人**として振る舞い、SwiftUIとUIKitが情報をやり取りできるよう、間に入って調整してくれます。
<br>

# 9. UIViewControllerRepresentable
<img width="700" alt="サンプルコードの構成図(UIViewControllerRepresentableにフォーカス)" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589104/e0982417-9f69-fc23-1341-4d90193d0f03.png">

`UIImagePickerController`はUIKitのクラスなので、SwiftUIの世界では生きられません。
淡水魚を海水に放つようなものです。
<img width="250" alt="fish" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589104/37e81693-9e2d-32e1-4ed0-3298bd87ad34.png">
<br>

`UIImagePickerController`クラスをSwiftUIで扱うためには、SwiftUIの中にUIKitが生きられる環境を作ってあげる必要があり、その役目を担うのが **`UIViewControllerRepresentable`** プロトコルです。　

`UIViewControllerRepresentable`は、**UIKitのビューコントローラーをSwiftUIの一部として使用できるようにするプロトコル**です。

ちなみに`UIViewControllerRepresentable`の名称には「*represent* = 代表する・表現する」という単語が含まれていますが、これは`UIViewControllerRepresentable`プロトコルが、UIKitのビューコントローラーをSwiftUIのビューとして表現（*represent*）するからです。

:::note info
**ビューコントローラーとは**
画面に表示されるビューを管理したり操作したりする役割を持つクラスのこと。
たとえばユーザーインターフェースを構築したり、コンポーネントの表示・非表示を切り替えたり、ビューの表示にアニメーションをかけたりする処理を行います。

参考：[SwiftでViewControllerを使う](https://qiita.com/h_nagami/items/66dc637463f98716bfa5)
:::

それでは、さっそく`ImagePickerView`という名前で構造体を宣言して、`UIViewControllerRepresentable`プロトコルに準拠させてみましょう。**ImagePickerView.swift**に以下のコードを記述してください。
```Swift: ImagePickerView.swift
import SwiftUI
import UIKit

struct ImagePickerView : UIViewControllerRepresentable {
    @Binding var image : UIImage?
}
```
:::note info
**`@Binding var image : UIImage?`**

`@Binding`アノテーションを付けて変数`image`を定義することで、`image`が変更された際に、**ContentView.swift** のプロパティ`image`も同期して更新されるようにしています。
:::
:::note info
**`struct ImagePickerView : UIViewControllerRepresentable`**

構造体`ImagePickerView`が準拠しているのは`View`ではなく`UIViewControllerRepresentable`プロトコル。ビューコントローラそのものとなっています。

参考：[Wrapping a UIViewController in a SwiftUI view](https://www.hackingwithswift.com/books/ios-swiftui/wrapping-a-uiviewcontroller-in-a-swiftui-view)
:::

上記のコードを記述すると、XCode上に以下の警告メッセージが表示されます。

>Type 'ImagePickerView' does not conform to protocol 'UIViewControllerRepresentable'
Add stubs for conformance
>
>(訳)
>タイプ'ImagePickerView'がプロトコル'UIViewControllerRepresentable'に適合していない。スタブを追加する。


![スクリーンショット 2024-08-01 0.30.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589104/33d92241-da10-a79a-57ef-442e5ef678d7.png)
隣にある`Fix`ボタンを押してみましょう。

![スクリーンショット 2024-08-01 0.36.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589104/b66c36de-5a35-caf1-2993-ef82a392ef1b.png)
すると、今度は`typealias UIViewControllerType = type`という文が出現し、以下の警告が表示されます。
> Cannot find type '<#type#>' in scope
>
>(訳)
>スコープ内で型 '<#type#>' が見つからない。

なぜ警告が表示されるのでしょうか。
それは`UIViewControllerRepresentable`プロトコルに準拠するため、「**何のビューコントローラーをSwiftUIのビューとして表現するのか**」をはっきりさせないといけないからです。
 
今回は`UIImagePickerController`のインスタンスをSwiftUIのビューとして表現するので、以下のようにコードを追記します。
```diff_swift: ImagePickerView.swift
import SwiftUI
import UIKit

struct ImagePickerView : UIViewControllerRepresentable {
+   typealias UIViewControllerType = UIImagePickerController

    @Binding var image : UIImage?
}
```
さて、警告は消えたでしょうか？
![スクリーンショット 2024-08-01 1.10.14 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589104/582b5038-104b-c846-4162-30ea4925891d.png)
消えていませんね...。もう１回`Fix`ボタンを押してみましょう。

![スクリーンショット 2024-08-01 1.16.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589104/fbdd17f3-b507-cf87-e903-d9a3e9b637d3.png)
すると、新たに２つのメソッドが構造体の中に出現しました。
- **`makeUIViewController(context:)`**
- **`updateUIViewController(_:context:)`**

`UIViewControllerRepresentable`プロトコルに準拠するためには、さらにこの２つのメソッドを実装しなくてはなりません。

https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable/

## makeUIViewController(context:)
 **`makeUIViewController(context:)`** は、`UIViewControllerRepresentable`プロトコルのメソッドの一つで、**UIKitのビューコントローラーを作って返してくれます**。
このメソッドを定義しないと`UIViewControllerRepresentable`プロトコルに準拠できず、コンパイルエラーになってしまいます。

`UIViewControllerRepresentable`は、SwiftUIでビューコントローラーを使用可能にするためのプロトコルです。`makeUIViewController(context:)`メソッドを実装しなければ、

「は？なんでビューコントローラーを作るメソッドも無いのに、`UIViewControllerRepresentable`を名乗ってんの？」

ということになってしまいます。だから、実装は必須なのでしょうね。
<br>

また`makeUIViewController(context:)`メソッドは**自分を呼び出すコードを書く必要がない**という謎の属性をもっています。
ふつう関数を作ったら、どこかでその関数を呼び出すコードを書きますよね？
しかし`makeUIViewController(context:)`メソッドは、`context`という引数まで持っているくせに、**宣言するだけで勝手に実行されてしまう**のです。
では、誰が`makeUIViewController(context:)`メソッドを実行しているのか？

**SwiftUIです。**

SwiftUIはビューコントローラーを作る際に一度だけ`makeUIView(context:)`メソッドを実行します。SwiftUIが自動でやってくれるので、`makeUIViewController(context:)`メソッドを呼び出す必要はありません。引数の`context`もSwiftUIが埋めてくれます。便利ですね。

参考：[Using coordinators to manage SwiftUI view controllers](https://www.hackingwithswift.com/books/ios-swiftui/using-coordinators-to-manage-swiftui-view-controllers)

https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable/makeuiviewcontroller(context:)

<br>

## updateUIViewController(_:context:)
`updateUIViewController(_:context:)`メソッドは、**ビューコントローラーの状態を更新する**メソッドです。1つ目の引数の`_`で対象となるビューコントローラーを指定し、２つ目の引数の`context`でSwiftUI側の最新の状態の情報をメソッドへと渡します。
指定したビューコントローラーに影響を与えるような変更が起こった場合に限り、自動的にこのメソッドが呼ばれます。

今回は以下のように定義すればOKです。
```swift
func updateUIViewController(_ uiViewController : UIImagePickerController,
  context : Context){}
```
「えっ？`{}`の中、空っぽじゃん...。」
そうです。この `updateUIViewController(_:context:)`メソッドは、`{}`の中に何も処理を書かなくても実装することができます。なぜでしょうか？

ビューコントローラーは`makeUIViewController(context:)`メソッドで生成されます。
しかし、生成されたビューコントローラーがその後いっさい変更されないのなら、更新処理を記述する必要はありません。だから`{}`の中が空っぽでも問題ないというわけです。

https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable/updateuiviewcontroller(_:context:)

<br>

# 10. ビューコントローラーの作成
`makeUIViewController(context:)`メソッドと`updateUIViewController(_:context:)`メソッドの概要が理解できたところで、次の３つの処理をコードに書いてみましょう。
1. `makeUIViewController(context:)`メソッドの実装
1. **ビューコントローラの作成**
1. `updateUIViewController(_:context:)`メソッドの実装

以下のとおり、コードを記述してください。
```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()
+        return picker
+    }
    
+    func updateUIViewController(_ uiViewController:UIImagePickerController,
+      context: Context) {}
}
```
`makeUIViewController(context:)`メソッドを実装するためには、戻り値として何らかのビューコントローラーを返さなければなりません。

ここでは`UIImagePickerController()`クラスのインスタンスを、ビューコントローラーとして返すことで、「1.  **`makeUIViewController(context:)`メソッドの実装**」と「2. **ビューコントローラの作成**」の2つの処理が実行されるようになっています。 　

「3.**`updateUIViewController(_:context:)`の実装**」については、先ほどのセクションで記載したコードをそのまま転記すればOKです。
<br>

これで、カメラロールから画像を選択するビューコントローラーを作ることができました！さっそく、動作をシミュレーターで確かめてみましょう。

<img width="360" alt="UIImagePickerController_step1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589104/1b548b00-b0ef-238c-7c81-805392d44544.gif" />

カメラロールの選択画面は表示されましたが、**選択した画像が画面に表示されていません**。これはいけませんね。しかしなぜ、カメラロールの選択画像が画面に表示されなかったのでしょうか？
それは「**UIKitのビューコントローラー内での変更は、自動的にはSwiftUIの他の部分へ伝達されないようになっている**」からです。

SwiftUIとUIKitは、お互いに異なるライフサイクルを持っているので、直接情報をやり取りすることができません。そのためUIKitのビューコントローラー内で起こった変更は、そのままではSwiftUIの他の部分に反映されません。UIKitの代わりにSwiftUIと情報のやり取りを行なってくれるオブジェクト、**デリゲート**の存在が必要になります。

:::note info
**デリゲート**
*deligate*  = (権限などを)委譲・委任する

あるオブジェクトが実行できないような処理を、別のオブジェクトに実行してもらう仕組みのことを、プログラミングの分野では**デリゲート**と読んでいます。
:::
今回の場合、デリゲートは「**ユーザーが画像や動画を選択したときや、選択をキャンセルしたときに通知を受け取り、指定された処理を実行するオブジェクト**」と解釈していただけばOKです。

https://developer.apple.com/documentation/uikit/uiimagepickercontroller/1619145-delegate

ビューコントローラー内での変更をSwiftUIの他の部分に反映させるには、デリゲートを作成し、ビューコントローラーのインスタンスと紐づける必要があります。`UIImagePickerController`クラスにはそのためのプロパティ　**`delegate`** が用意されています。

デリゲートをどうやって作成するのかについては、次回以降の記事で説明するとして、ここではプロパティの`delgete`を参照するコードを書き、デリゲートと紐づけするための準備をしておきましょう。以下のとおり、コードを追記してください。

```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`になんらかの値を代入することで、`UIImagePickerController`クラスのインスタンスとデリゲートを紐づけることができます。
<br>

# 11. 次の記事へ
お疲れ様でした。今回はここまでとなります。
次の記事では、デリゲートを生成する元となるクラス、**`Coordinatore`** について解説していきます。それでは:wave:

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