1. はじめに
こちらの記事の続きです![]()
本記事ではUIKitのクラス UIImagePickerController を使って、カメラロールから画像を選択する方法について解説します。
全部で3つの記事があり、本記事はその2番目の記事となります。
本文の内容は、ChatGPTに質問して得た回答を下敷きに、公式ドキュメント等で裏を取りながら執筆しました。
また、英文の和訳はDeepL翻訳を用いて訳したものを記載しています。
SwiftUIには、もっと手軽にカメラロールを扱うことができる PhotosPicker という構造体があります。
「別にUIKitを使わなくてもいいよ」という方はここで読むのをやめて、以下の解説サイトを閲覧されることをおすすめします![]()
2. 全体の構成
| タイトル | 概要 |
|---|---|
| UIImagePickerControllerを使って画像を選択しよう (その1) |
サンプルコードの全体像 UIViewControllerRepresentable |
| UIImagePickerControllerを使って画像を選択しよう (その2) |
Coordinatorの作成 |
| UIImagePickerControllerを使って画像を選択しよう (その3) |
デリゲートの設定 その他 |
3. 目次
- 1. はじめに
- 2. 全体の構成
- 3. 目次
- 4. サンプルコード
- 5. Coordinatorクラスのプロトコル
- 6. Coordinatorクラスの初期化
- 7. imagePickerControllerメソッド
- 8. imagePickerControllerDidCancelメソッド
- 9. 次の記事へ
4. サンプルコード
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()
}
}
本記事では、ContentView.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){}
}
5. Coordinatorクラスのプロトコル

前回の記事では、ビューコントローラー内での変更をSwiftUIに反映させるには、デリゲートを作成し、ビューコントローラーのインスタンスと紐づける必要があると説明しました。
Coordinator は、そのデリゲートを生成する元となるクラスです。
デリゲート
ここでは、ユーザーがビューコントローラー内で画像や動画を選択したときや、選択をキャンセルしたときに通知を受け取り、指定された処理を実行するオブジェクトのことをデリゲートと呼びます。
Coordinatorは、UIKitのビューコントローラーであるUIImagePickerControllerとやり取りするために、
- NSObjxectクラス
- UINavigationControllerDelegateプロトコル
- UIImagePickerControllerDelegateプロトコル
のすべてに準拠する必要があります。
以下のとおり、コードを記述してください。
import SwiftUI
import UIKit
struct ImagePickerView : UIViewControllerRepresentable {
@Binding var image : UIImage?
typealias UIViewControllerType = UIImagePickerController
+ class Coordinator : NSObject,
+ UINavigationControllerDelegate,
+ UIImagePickerControllerDelegate {
+ }
func makeUIViewController(context : Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate
return picker
}
func updateUIViewController(_ uiViewController : UIImagePickerController, context : Context){}
}
上記のとおりにコードを書いたら、
Type 'ImagePickerView' does not conform to protocol 'UIViewControllerRepresentable'(訳)
タイプ'ImagePickerView'はプロトコル'UIViewControllerRepresentable'`に準拠していません。
と、警告が表示されるかと思いますが、これについては後ほど説明します。まずはCoordinatorが準拠している3つのプロトコルについて解説します。
NSObjectクラス
NSObject クラスはプログラミング言語、Objective-Cのルートクラスのことです。UIKitは、Objective-Cでも動作するように作られているため、ほとんどのUIKitがNSObjectクラスを継承しています。したがって、CoordinatorがUIKitと情報のやり取りを行うためにはNSObjectプロトコルに準拠する必要があります。
【参考】 【Swift】NSObjectって何者?少しだけ詳しく調べてみた
UINavigationControllerDelegateプロトコル
UINavigationControllerDelefate は、UINavigationControllerの動作をカスタマイズするために使用されるプロトコルです。準拠することで、UINavigationControllerクラスの処理に任意の動作を追加できるようになります。
UINavigationController
ビューコントローラを配列の形で管理するクラスのこと。
配列の要素を追加したり削除したりすることで、画面に表示するビューコントローラを操作することができます。
【参考】公式ドキュメント
UIImagePickerControllerDelegate
UIImagePickerControllerDelegate は、UIImagePickerControllerクラスのインスタンスが画像や動画の選択を完了したときに、通知を受け取るためのプロトコルです。
準拠することで、ユーザーが画像を選択したり、選択をキャンセルした後、任意の処理を実行できるようになります。たとえば、今回のケースを例に挙げると、
- カメラロールで選択した画像の内容で、SwiftUIの構造体のプロパティを書き換える
といった処理ができるようになります。
ちなみにCoordinatorクラスのインスタンスは、後ほどUIImagePickerControllerのインスタンスの delegate プロパティとして位置付けられますが、このdelegateプロパティは
UINavigationControllerDelegateUIImagePickerControllerDelegate
の両方のプロトコルに準拠する必要があります。
そのため、Coordinatorクラスの実装では UINavigationControllerDelegate と UIImagePickerControllerDelegate の2つのプロトコルに準拠しなければならないのです。
6. Coordinatorクラスの初期化
このセクションでは、Coordinatorクラスを初期化する処理について説明します。
以下のコードを追記してください。
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 makeUIViewController(context : Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate
return picker
}
func updateUIViewController(_ uiViewController : UIImagePickerController, context : Context){}
}
初期化処理では、Coordinatorがどのインスタンンスに関連づけられるのかを設定します。
Coordinatorは、日本語だと調整役・交渉人という意味ですが、もしCoordinatorがどのインスタンスにも関連づけられないとしたら、それは「自分はどこの会社にも勤めていませんが、あなたと交渉したいです!」と言っているようなものです。
そんな人とは、怖くて交渉なんかできませんよね。
Coordinatorが役目を果たすためには、まず自分がどのインスタンスの交渉人なのかをはっきりさせなければなりません。それを行なっているのが
var parent : ImagePickerView
init(parent : ImagePickerView){
self.parent = parent
}
の箇所になります。ここではじめて Coordinatorは引数parentで指定したインスタンスの交渉人であるということが確定し、構造体ImagePickerViewを参照できるようになります。
そして、CoordinatorはImagePickerViewインスタンスを参照し、ImagePickerViewの@Bindingプロパティを更新することで、UIKitの情報をSwiftUIに渡すことができるのです。このことについては、また後で触れたいと思います。
7. imagePickerControllerメソッド
このセクションでは、Coordinatorクラスに imagePickerController(_:didFinishPickingMediaWithInfo:) メソッドを実装する方法について説明します。以下のコードを追記してください。
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 makeUIViewController(context : Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate
return picker
}
func updateUIViewController(_ uiViewController : UIImagePickerController, context : Context){}
imagePickerController(_:didFinishPickingMediaWithInfo:)メソッドは公式ドキュメントに以下のように定義されています。
Tells the delegate that the user picked a still image or movie.
(訳)
ユーザが静止画または動画を選択したことをデリゲートに伝えます。
ただ、定義の内容がいまいちピンと来ないので、私はこのメソッドをビューコントローラーの操作後に実行する処理を設定する関数と捉えております(認識が間違っているかもしれませんが...)。
imagePickerController(_:didFinishPickingMediaWithInfo:)は、実際に呼び出すコードを書く必要がないメソッドです。コードを書かなくても自動的に実行されるのは、その1の記事にて解説したmakeUIViewController(context:)メソッドと同じですね。
では、何がimagePickerController(_:didFinishPickingMediaWithInfo:)を呼び出すのか?それはビューコントローラーである UIImagePickerController クラスのインスタンスです。
UIImagePickerControllerクラスのインスタンスは、画像が選択されると次に行うべき処理を委任するために、CoordinatorのimagePickerController(_:didFinishPickingMediaWithInfo:)メソッドを呼び出すのです。
つまり、imagePickerController(_:didFinishPickingMediaWithInfo:)メソッドは、ユーザーがメディアの選択を完了したときに自動的に呼び出され、次に行うべき処理を委任されるメソッドと言えます。
imagePickerControllerメソッドの引数
imagePickerControllerの引数について、説明します。
公式ドキュメントより、型の定義を引用します。
optional func imagePickerController( _ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any] )引用元:公式ドキュメント imagePickerController(_:didFinishPickingMediaWithInfo:)
内部引数と外部引数について
引数名は関数の呼び出し時に使用する外部引数名と、関数内で使用される内部引数名の2つを持つことができます。外部引数名と内部引数名を分けるには、外部引数名 内部引数名: 型という形式で引数を定義します。
引用元:
[増補改訂第3版]Swift実践入門 直感的な文法と安全性を兼ね備えた言語
P.130,131
imagePickerControllerの場合は、第1引数の _ は外部引数名、 picker は内部引数名、 UIImagePickerController は型ということになります。
※ちなみに引数名が_となっているのは、外部引数を省略することを示しています。
picker
The controller object managing the image picker interface.
(訳)
イメージピッカーインタフェースを管理するコントローラオブジェクト。
UIImagePickerControllerクラスが型として指定されているとおり、内部引数 picker にはUIImagePickerControllerクラスのインスタンスが渡されることになります。
info
内部引数 info には、画像が選ばれた場合にはオリジナル画像と編集後の画像を含む辞書が、動画が選ばれた場合は動画のファイルURLを含む辞書が格納されます。
辞書とは[キー : 値]のように「キー」と「値」がペアになったコレクションのことを指し、辞書のキーは UIImagePickerController.InfoKey に格納されます。
UIImagePickerController.InfoKey
内部引数infoの型である[UIImagePickerController.InfoKey : Any]は、選択したメディアの情報を取得するために使用するキーのことです。以下にキーの値の例を抜粋します。
originalImage
static let originalImage: UIImagePickerController.InfoKey
The original, uncropped image selected by the user.(訳)
ユーザーが選択した、トリミングされていないオリジナルの画像。
editedImage
An image edited by the user.
(訳)
ユーザーが編集した画像。
mediaType
The media type selected by the user.
(訳)
ユーザーが選択したメディアタイプ。
引用元 公式ドキュメント UIImagePickerController.InfoKey
imagePickerControllerメソッドの実装
メソッドの処理の実装について説明します。以下のコードをご覧ください。
func imagePickerController(_ picker : UIImagePickerController,
didFinishPickingMediaWithInfo info : [UIImagePickerController.InfoKey : Any]){
if let selectedImage = info[.originalImage] as? UIImage {
parent.image = selectedImage
}
picker.dismiss(animated : true)
}
if let selectImage = info[.originalImage] as? UIImage について
info[.originalImage]では、originalImageをキーにして、辞書infoの中からユーザーが選択した元画像を取り出そうとしています。
as? UIImageでは、as演算子を使って、元画像のデータをUIImageへとダウンキャストしようとしています。ダウンキャストに成功した場合はselectedImageにUIImageが格納され、失敗した場合にはnilが格納されます。
parent.image = selectedImage について
parent.image = selectedImageでは、選択画像のUIImageデータをもって、parent.imageを書き換えています。
ここでCoordinatorを初期化するときの話を思い出してください。
parentとは、Coordinatorが所属するインスタンスを決める引数で、その型は UIImagePickerView となっていました。
そのため、parent.imageは、ImagePickerViewインスタンスのimageプロパティ、@Binding var image : UIImage?のことを指します。これをselectedImageで書き換えることはつまり、@Binding var image : UIImage?を選択画像のUIImageデータに書き換えることを意味します。
言い換えるならparent.image = selectedImageでは、UIKitの変更情報をもって、SwiftUIのプロパティを更新しているのです。
Coordinatorはこのようにして、UIKitとSwiftUIの交渉人の役目を果たしているんですね。
picker.dismiss(animated : true)
picker.dismiss(animated : true)では、表示中のカメラロールを閉じる処理を実行しています。
dismissとはUIKitのメソッドで、モーダルに表示されているビューコントローラを閉じるはたらきをします。
この場合、表示されているビューコントローラはUIImagePickerControllerクラスのインスタンスだから、カメラロールを閉じることになったのですね。
ちなみに、外部引数animatedでは、モーダルを閉じる動作をアニメーションにするか否かを設定することができます。アニメーションが無いと味気ないので、ここではtrueと設定しました。
8. imagePickerControllerDidCancelメソッド
以下の通り、コードを追記してください。
import SwiftUI
import UIKit
struct ImagePickerView : UIViewControllerRepresentable {
@Binding var image : UIImage?
typealias UIViewControllerType = UIImagePickerController
class Coordinator : NSObject,
UINavigationControllerDelegate,
UIImagePickerControllerDelegate {
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
return picker
}
func updateUIViewController(_ uiViewController : UIImagePickerController, context : Context){}
}
func imagePickerControllerDidCancel(_ picker : UIImagePickerController){
picker.dismiss(animated : true)
}
imagePickerControllerDidCancel(_:) メソッドについて、少しだけ解説します。
これは、ユーザーが画像選択をキャンセルしたことを伝達するメソッドです。
内部で行なっている処理は、ただ一つ。カメラロールを閉じることだけです。
picker.dismiss(animated : true)で閉じる動作を実行しているのですが、この内容についてはimagePickerController(_:didFinishPickingMediaWithInfo:)メソッドで述べたので、ここでは割愛させていただきます。
9. 次の記事へ
お疲れ様でした。今回はここまでとなります。
次の記事では、Coordinatorのインスタンスを作り、ビューコントローラーのデリゲートとして設定したいと思います。それでは![]()