Coordinatorとは?
-
UIKitでのイベントをSwiftUIで管理する(取得するなど)ために作成するクラスのこと。
- 先述した通り、主な役割としては、
UIKitのイベントやデータの受け渡しをSwiftUIに伝える。
- (ラップされた)UIKitのViewの
delegate
やtarget action
をハンドリングしたい場合、structではハンドリングできないため、Coordinatorクラスという仕組みが採用されている。
- (ラップされた)UIKitのViewの
- 先述した通り、主な役割としては、
-
Coordinatorクラスはデフォルトで存在しないため、独自で実装する必要がある。一般的にはCoordinatorという命名である。
主なメソッド
makeCoordinatorメソッド
-
makeCoordinatorメソッドは
makeUIViewメソッドよりも先に呼ばれる
ので、そこ(makeCoordinatorメソッド内)でCoordinatorを作成する。 -
その後、makeUIViewメソッドやupdateUIViewメソッドなどで渡されるcontextにcoordinatorが含まれる。そのcoordinatorを用いればUIKitで作成したViewとSwiftUI側でやり取り(イベント管理など)ができる。
ソースコード(いくつかのパターンを添えて)
イベントの受け渡し
Swift
// (SwiftUIで使えるようにラップされた)UIKitのUIButtonがタップされたときに、そのイベントをSwiftUIのViewに受け渡し(通知し)、SwiftUI側でラベルにメッセージを表示する
struct ButtonView: UIViewRepresentable {
// UIViewRepresentable使えば、Bindingも可能になる
@Binding var message: String
func makeUIView(context: Context) -> UIButton {
let button = UIButton(type: .system)
button.setTitle("ボタン", for: .normal)
// coordinatorがSwiftUIとUIKitの仲介役(イベント管理的役割)となってイベントを受け渡すことができる
button.addTarget(context.coordinator, action: #selector(Coordinator.tappedButton), for: .touchUpInside)
return button
}
func updateUIView(_ uiView: UIButton, context: Context) {
}
func makeCoordinator() -> Coordinator {
// 仲介対象はこのButtonView自身
Coordinator(self)
}
}
// SwiftUIとUIKitの仲介役
// classなのでイベント管理やdelegateが可能
class Coordinator: NSObject {
var parent: ButtonView
init(_ parent: ButtonView) {
self.parent = parent
}
@objc func tappedButton() {
// UIKitのボタンがタップされたときに呼び出される
// メッセージをSwiftUIに伝える
parent.message = "「ボタンが押された」というメッセージに変わりました"
}
}
// messageプロパティのStringをボタンタップによって変化させ、SwiftUI側に表示させる
struct ContentView: View {
@State var message = "ボタンはまだ押されていません"
var body: some View {
VStack {
Text(message)
ButtonView(message: $message)
}
}
}
-
delegateを噛ませてイベントの受け渡し
- ただし、わざわざdelegateを生やして処理を委譲する(データを渡す)のではなく、構造体に処理委譲を任せるクロージャーを引数として生やして、インスタンス化先で処理を任せるみたいな対応が好ましいかもしれない。
Swift
protocol ButtonDelegate {
func TappedButton()
}
struct ButtonView: UIViewRepresentable {
var delegate: ButtonDelegate
// UIKitの初期状態のインスタンスを作成
func makeUIView(context: Context) -> UIButton {
let button = UIButton(type: .system)
button.setTitle("ボタン", for: .normal)
button.addTarget(context.coordinator, action: #selector(Coordinator.tappedButton), for: .touchUpInside)
return button
}
func updateUIView(_ uiView: UIButton, context: Context) {
}
// SwiftUIとUIKitの仲介(やりとり)
// UIKitのdelegate通知をSwiftUIで受け取れるようにしている
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
// delegateを準拠
// この構造体に処理を委譲する
struct ButtonDelegateImpl: ButtonDelegate {
@Binding var message: String
func TappedButton() {
message = "ボタンがタップされました"
}
}
// SwiftUIとUIKitの仲介(やりとり)
// UIKitのdelegate通知をSwiftUIで受け取れるようにする
class Coordinator: NSObject {
let parent: ButtonView
init(_ parent: ButtonView) {
self.parent = parent
}
@objc func tappedButton() {
// UIKitのボタンが押されるとdelegate経由で通知
parent.delegate.TappedButton()
}
}
struct ContentView: View {
@State var message = ""
var body: some View {
VStack {
Text(message)
// ButtonViewの引数に(処理をお願いする)ButtonDelegateImplを持たせる
ButtonView(delegate: ButtonDelegateImpl(message: $message))
}
}
}
おわりに
間違い等ありましたらコメント欄にてご指摘ください!
参考記事
開発環境
- Xcode-14.3
- Swift version 5.8