【SwiftUI】UIViewRepresentableの使い方!Coordinatorクラスとは?
この記事からわかること
- SwiftUIでUIKitを使えるようにするには?
- UIViewRepresentableプロトコルの使い方
- makeUIViewメソッドとupdateUIViewメソッドの違い
- UIKitのイベントをSwiftUIに伝える
- UILabelとUIButtonをSwiftUIで扱う方法
- Coordinator(コーディネータ)クラスとは?
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
iOSアプリを開発するためのプログラミング言語である「Swift」の代表的なフレームワークが「UIKit」と「SwiftUI」です。
今回は「SwiftUI」から「UIKit」を使えるようにできるUIViewRepresentableプロトコルの使い方と概要についてまとめていきたいと思います。
おすすめ記事:Swiftとは?Swift UIとの違いと特徴やメリット
UIViewRepresentableプロトコルとは?
protocol UIViewRepresentable : View where Self.Body == Never
「UIViewRepresentableプロトコル」とはUIKitのビューや機能をSwiftUIでも使えるようにするラッパーの役割も持つプロトコルです。そもそもUIKitのビューを管理するUIViewController
とSwiftUIのView
はベースが異なるので、両者をそのままつなぎ合わせることはできません。
また「UIKit」は古くから使用されてきたフレームワークなので豊富な機能やビューが提供されています。一方比較的新しいフレームワークである「SwiftUI」はまだまだ実装できる機能には制限がありできることは限られてきます。
「互換性のないビュー」と「機能の豊富さの差異」を解決するために両フレームワーク間の機能の橋渡しするためのラッパーが用意されています。
両フレームワーク間の機能の橋渡しするためのラッパー
- UIKitでSwiftUIを使用:UIHostingControllerクラス
- SwiftUIでUIKitを使用:UIViewRepresentableプロトコル
UIViewRepresentableプロトコルの使い方
SwiftUIでUIKitを使用するためのUIViewRepresentable
プロトコルは構造体に準拠させて使用します。定義を見てみると以下の2つのメソッドの実装が必須になっています。
定義
public protocol UIViewRepresentable : View where Self.Body == Never {
func makeUIView(context: Self.Context) -> Self.UIViewType
func updateUIView(_ uiView: Self.UIViewType, context: Self.Context)
}
ちなみにUIViewRepresentable
プロトコルをクラスに準拠させる場合はfinal class
にしないと以下のようなエラーを吐きます。
Protocol 'View' requirement '_makeView(view:inputs:)' cannot be satisfied by a non-final class ('LabelView') because it uses 'Self' in a non-parameter, non-result type position
// プロトコル 'ビュー' 要件 '_makeView(view:inputs:)' は非最終クラス ('LabelView') では満たすことができません
とはいえUIKitは主にクラスで定義されているの対し、SwiftUiは主に構造体で定義されているので準拠させていくのは構造体になります。
makeUIViewメソッド
func makeUIView(context: Self.Context) -> Self.UIViewType
makeUIView
メソッドは表示するViewの初期状態のインスタンスを生成するメソッドです。SwiftUIの中で使用したいUIKitのViewを戻り値として返却します。
このメソッドは最初の一回しか呼び出されず、ビューを更新する場合はupdateUIView
メソッドに処理を定義します。
引数context
にはUIKitビューの作成と更新に使用するシステムの状態に関するコンテキスト(文脈)情報が渡されます。つまりビューを構築するために必要な情報のことです。
戻り値には表示させたいUIKitのViewの型(UIViewType)を指定します。ここで戻す型が実際にSwiftUIで表示させたいUIKitの型になります。最初のコード補完では上記のように-> Self.UIViewType
となります。この部分を直接指定の型に書き換えるもしくはUIViewType
を指定の型のタイプエイリアスとして宣言しておく必要があります。
直接指定するパターン
func makeUIView(context: Self.Context) -> UILabel
タイプエイリアスとして宣言するパターン
typealias UIViewType = UILabel
func makeUIView(context: Self.Context) -> Self.UIViewType
updateUIViewメソッド
func updateUIView(
_ uiView: Self.UIViewType,
context: Self.Context
)
updateUIView
メソッドは表示するビューの状態が更新されるたびに呼び出され更新を反映させるためのメソッドです。
例えばUIViewRepresentable
プロトコルに準拠させた構造体のプロパティなどが変更された時などに呼び出されます。
UIViewRepresentable〜まとめ〜
- UIKitをSwiftUIで使える役割を持たせるためのプロトコル
- 構造体に準拠させて使用
- makeUIViewとupdateUIViewの2つのメソッドの実装が必須
- makeUIView:初期表示用のメソッド
- updateUIView:更新用のメソッド
使用方法1:SwiftUIの変更をUIKitに伝える
実際にUILabel(UIKitのビュー)をSwiftUIで表示させてみたいと思います。ラベルを表示させるだけではなくSwiftUIのButton
構造体で定義したボタンをクリック時にラベルのテキストを変更させます。
- UIViewRepresentableに準拠させた構造体を作成
- isClickプロパティをバインディングで定義
- makeUIViewメソッド内でUILabelを構築しreturn
- updateUIViewメソッドではバインディングしたプロパティの値の変化をキャッチできるので値に応じて処理を分岐
- LabelView()としてインスタンス化してビューを表示
import SwiftUI
struct LabelView: UIViewRepresentable {
@Binding var isClick:Bool
func makeUIView(context: Context) -> UILabel {
let labelView:UILabel = UILabel() // UIKitのビュー
labelView.text = "UIKitから作成したView" // テキストを格納
labelView.textAlignment = NSTextAlignment.center
return labelView // ビューを返す
}
func updateUIView(_ uiView: UILabel, context: Context) {
if isClick {
uiView.text = "変更しました。"
}else{
uiView.text = "UIKitから作成したView"
}
}
}
struct ContentView: View {
@State var isClick:Bool = false
var body: some View {
VStack {
LabelView(isClick: $isClick).padding()
Button("ボタン"){
isClick.toggle()
}
}
}
}
これでボタン(SwiftUI)をクリックした時にUILabel(UIKit)のテキストを変更する機能が完了しました。
使用方法2:UIKitのイベントをSwiftUIに伝える
ではUIKitのUIButton
をクリックされた時にUILabel
を変更する方法を考えてみます。まずはUIButton
をSwiftUIで使えるように先ほどと同じ手順で定義していきます。今回は特にボタンは変更しないのでupdateUIView
は空にしておきます
struct ButtonView: UIViewRepresentable {
@Binding var isClick:Bool
func makeUIView(context: Context) -> UIButton {
let control = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 40))
control.setTitle("ボタン", for: .normal)
control.setTitleColor(.red, for: .normal)
return control
}
func updateUIView(_ uiView: UIButton, context: Context) {
// 今回は定義しない
}
}
これでUIButton
をSwiftUIで表示できるようになりましたが、クリック時(UIKit側のイベント)の処理はまだ記述できていません。
UIKit側のイベントを取得するにはCoordinator(コーディネータ)クラスを構造体の中に定義して実装していきます。
Coordinator(コーディネータ)クラス
Coordinator(コーディネータ)クラスとはUIKitでのイベントをSwiftUIで管理するために作成するクラスのことです。このクラスはデフォルトで存在するわけではなく、自分で実装します。そのためクラス名に決まりはないですが構造体の中で定義するため名前の衝突を気にする必要がないので分かりやすくCoordinator
としておきます。
struct ButtonView: UIViewRepresentable {
@Binding var isClick:Bool
func makeUIView(context: Context) -> UIButton {
let control = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 40))
control.setTitle("ボタン", for: .normal)
control.setTitleColor(.red, for: .normal)
control.addTarget(context.coordinator,action: #selector(Coordinator.clickButton(sender:)),for: .touchUpInside)
return control
}
func updateUIView(_ uiView: UIButton, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator {
var control: ButtonView
init(_ control: ButtonView){
self.control = control
}
@objc func clickButton(sender : Any){
control.isClick.toggle()
}
}
}
Coordinator
クラスの中には「観測対象プロパティの宣言」と「イベント発生時の処理」を実装していきます。
- Coordinatorクラスを構造体の中に作成
- プロパティに定義したビューをセット
- イニシャライザで引数を自身にセット
- イベント発生時の処理(clickButton)を記述する
- インスタンス化用の専用メソッド(makeCoordinator)を構造体内に定義
- makeUIViewメソッドにアクション部分を定義
まずはクラスを定義します。@objc
はSwift前身のObjective-Cのコードとして扱うためのコードです。これは後述する#selector
と関係しています。その中にアクションで行う処理を記述しておきます。
class Coordinator {
var control: ButtonView
init(_ control: ButtonView){
self.control = control
}
@objc func clickButton(sender : Any){
control.isClick.toggle()
}
}
続いてCoordinator
クラスをインスタンス化するためのmakeCoordinator
メソッドを準備します。これはUIViewRepresentable
にあらかじめ用意されているメソッドでmakeUIView
メソッドが呼び出される前に一度だけ呼びだされインスタンス化されます。
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
インスタンス化されるとmakeUIView
メソッドの引数context
の中から参照できるようになります。UIButton
のアクション部分を実装するaddTarget
メソッドにCoordinator
を渡していきます。#selector
を使ってメソッドを指定します。
func makeUIView(context: Context) -> UIButton {
let control = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 40))
control.setTitle("ボタン", for: .normal)
control.setTitleColor(.red, for: .normal)
control.addTarget(context.coordinator,action: #selector(Coordinator.clickButton(sender:)),for: .touchUpInside)
return control
}
私がSwift UI学習に使用した参考書
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。