背景
業務でTCAを始めて導入しました。業務アプリは全てSwiftで書かれており、合わせて新しい画面を作る必要もあったため、SwiftUI+TCAでの導入となりました。
そこで課題だったのが、UIKit×SwiftUIの連携をTCAでどう行うかでした。
具体的には、子View(SwiftUIのSheet)上でタップされたボタンに応じて、親(UIKit)でactionを受けて分岐し、親に定義されている既存メソッドを使用するということを実装しました。
備忘録 && 同じようにTCAを既存のUIKitプロジェクトに導入したいという方の一助になればと思いました。
バージョン
Swift: 5.7.2
Xcode: 14.2
TCA: 0.52.0
デモアプリ
- 親(UIKit)のボタンを押すと3つのボタンがある子(SwiftUI)がSheetで表示される
- 任意のボタンをタップ
- 子がdismissし、タップしたボタンに応じて親でAlertが表示される
子
まずは、子(SwiftUI×TCA)を作成します。
今回のサンプル実装部分では特に何も行わないのですっからかんです。
親にどのボタンがタップされたかを渡すためにButtonTypeというenumを作成し、タップされた時にボタンに応じたcaseを渡しています。
import SwiftUI
import ComposableArchitecture
struct ChildView: View {
let store: StoreOf<Child>
var body: some View {
WithViewStore(self.store) { viewStore in
HStack {
Button("a") {
viewStore.send(.buttonTapped(.a))
}
Button("b") {
viewStore.send(.buttonTapped(.b))
}
Button("c") {
viewStore.send(.buttonTapped(.c))
}
}
}
}
}
Store
import ComposableArchitecture
struct Child: ReducerProtocol {
struct State: Equatable {}
enum Action: Equatable {
case buttonTapped(ButtonType)
}
func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
switch action {
case .buttonTapped:
return .none
}
}
}
enum ButtonType: String {
case a, b, c
}
親
では、肝心の親(UIKit)の方です。
子Viewのインスタンスを作る際にreducerをReduce
で作成し、そこで子のstateとactionを受け取ることができます。(今回はstateは使用しないので、_
にしてます)
子のactionに応じて分岐処理を書きます。
import SwiftUI
import ComposableArchitecture
final class ParentViewController: UIViewController {
@IBAction private func showChildView(_ sender: UIButton) {
let childView = ChildView(
store: .init(initialState: .init(),
reducer: Reduce { [weak self] _, action in
.fireAndForget {
switch action {
case let .buttonTapped(type):
self?.presentedViewController?.dismiss(animated: true) {
self?.showAlert(type: type)
}
}
}
}))
let hostingController = UIHostingController(rootView: childView)
present(hostingController, animated: true)
}
private func showAlert(type: ButtonType) {
let alert = UIAlertController(title: type.rawValue, message: nil, preferredStyle: .alert)
alert.addAction(.init(title: "OK", style: .default))
present(alert, animated: true)
}
}
ネストが深くなってしまうのがちょっと・・ですが、これが子のactionを受けて、親のViewController側のメソッドを実行するシンプルな例でした。
動作確認
まとめ
これで既存のメソッドを使いつつ、UIKitとSwiftUIをTCAを使って一旦は連携できました。
親側でもTCA化すればいいのですが、時間的な制約もあり、TCAなしで書いてます。
こちらも少しずつSwiftUI×TCA化を進めていきます。
この記事が誰かの助けになれば幸いです!
もし間違いやもっといい方法があるよという方、ぜひコメントなどで教えてください!