SwiftUIとUIKit間での値の受け渡し
こんにちは、けんご(@N30nnnn)です。
SwiftUI内でUIKitを使うには, UIViewRepresentable
または UIViewControllerRepresentable
をかませることで呼び出せることができます。
それはSwiftUI tutorialや多くの参考記事がある一方で、その間で変数をやり取りするのに詰まったのでメモに残します。
結論
データの受け渡しは以下の構成で行います。
Coordinator
+ObservedObject
Coordinator
+Binding
UIViewControllerRepresentable (SwiftUI)
内で Coordinator
を実装し、 UIViewController (UIKit)
にデリゲートすることで変数を受け取ります。
また、 Binding
または ObservedObject
を UIViewControllerRepresentable
で受け取り、 Coordinator
内でそれらに値を代入することで、親Viewに値を返すことを実現します。
同じことなのでこの記事では ObservedObject
で残します。
また、"デリゲート"とはデザインパターンの一種で、この記事が分かりやすいです。
[Swift]"デリゲートデザインパターン"ってなに? https://qiita.com/nitaking/items/d441c5aa2aceaf4fc089
ケース
次のような protocol
を持つ Controller
があった時に、 result
を親の UIViewControllerRepresentable
に送りたいとします。
public protocol SampleDelegate: class { func success(_ controller: UIViewController, result: String) func fail(_ controller: UIViewController, error: String) func cancel(_ controller: UIViewController) } public class SampleController: UIViewController { public weak var delegate: SampleDelegate? ... }
参考実装
SwiftUI側
値をやり取りする ObservableObject
の定義
final class ResultModel: ObservableObject { @Published var result = "" }
UIViewControllerRepresentable
の定義と Coordinator
の実装
値の代入を行う Coordinator
を protocol
に則って実装し、 makeUIViewController
内で生成した SampleViewController
のインスタンスに実装した Coordinator
をデリゲートします。
struct SampleViewControllerRepresentable: UIViewControllerRepresentable { // 親 View から初期化されたObservableObjectを受け取る @ObservedObject var model: ResultModel func makeCoordinator() -> Coordinator { // Coordinator を定義した場合、 makeCoordinator も実装する必要有り Coordinator(self) } class Coordinator: NSObject, SampleDelegate { var parent: SampleViewControllerRepresentable init(_ sampleViewControllerRepresentable: SampleViewControllerRepresentable){ self.parent = sampleViewControllerRepresentable } func success(_ controller: UIViewController, scanDidComplete result: String){ print("result:\(result)") // ObservedObject に変数を代入。ここで値を受け取る。 self.parent.model.result = result } func fail(_ controller: UIViewController, error: String) { print("error:\(error)") } func cancel(_ controller: UIViewController) { print("Sample did cancel") } } func makeUIViewController(context: Context) -> SampleViewController { // SampleViewControllerの実装は後述 let ctrl = SampleViewController() ctrl.delegate = context.coordinator return ctrl } func updateUIViewController(_ uiViewController: SampleViewController, context: Context) { } }
UIKit側
Controllerのインスタンスを生成とデリゲート
デリゲートを受け取る UIViewController
を実装し、ケースで示した protocol
を持つ Controller
のインスタンスにデリゲートを伝播させます。
class SampleViewController: UIViewController { public weak var delegate: SampleDelegate? override func viewDidLoad() { super.viewDidLoad() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) let ctrl = SampleController() ctrl.delegate = delegate self.present(ctrl, animated: true, completion: nil) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } }
最終的には、デリゲートしたメソッドが呼び出されて、 ObservableObject
に代入されて親Viewで参照できます。