[SwiftUI]VIPER実装を参考にしたい我
VIPER実装例を集めて、今後の知識とした蓄えようの会です。
目に付いたサイトから見ていこうと思いまする。
まずはこれを読む。
・画面遷移ロジックはUIKitのUINavigationControllerを使用する。
・SwiftUI.VIewの責務は、見た目の部分のみを担わせた方が良さそう。
・ObservableObjectを継承したクラスのインスタンス化をUIViewController側で行い、ライフサイクルをハンドリングしやすくする。
・SwiftUIのイベントをハンドリングする際は、処理を渡すこともできる。
struct ViewScreen: View {
var tapHandler: (() -> Void)?
var body: some View {
Button {
tapHandler?()
} label: {
Text("Tap Me !!")
}
}
}
これを読む。
・SwiftUI.ViewはあくまでもViewとする。
これを読む。
・上記同様で、SwiftUI.ViewをViewとして管理する。
extension UIViewController {
func setupUI(hostingController: UIHostingController<some View>) {
addChild(hostingController)
view.addSubview(hostingController.view)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(
[
hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor)
]
)
}
}
これを読む。
・画面は従来通りViewControllerを使用しそこにUIHostingControllerを介したSwiftUIで作成したViewを置くという方針にする。
・画面UIをSwiftUIで実装し、画面遷移処理はこれまで通りRouterに移譲する。
・SwiftUIからUIViewController経由でのデータ参照はObservableObjectを使用し、SwiftUIからViewControllerへのイベント受け渡しはdelegateを使用する。
SwiftUIにDataSourceというObservableObjectを継承させたクラスを作成し、そこに@Publishedを付与したプロパティを持たせます。さらにSwiftUIに@ObservedObjectを付与したdataSourceプロパティ定義することでDataSourceクラスを監視させます。
ViewController側ではpresenterから送られてきた値をそのままdataSource.textsにassignすることでデータが更新されます。すると、SwiftUI側で検知されViewが更新されるといった流れになります。
class ViewController: UIViewController {
var presenter: Presenter!
private var cancellables: Set<AnyCancellable> = []
private var dataSource: ContentView.DataSource = .init()
override func viewDidLoad() {
super.viewDidLoad()
addSwiftUIChild(ContentView(dataSource: dataSource))
// dataSource.textsを更新
presenter.$texts
.assign(to: \.dataSource.texts, on: self)
.store(in: &cancellables)
}
}
struct ContentView: View {
// ObservableObjectを継承する
class DataSource: ObservableObject {
@Published var texts: [String] = []
}
// DataSourceを監視
@ObservedObject var dataSource: DataSource
var body: some View {
// dataSource.textsの変更を検知してViewを更新
List(dataSource.texts, id: \.self) { text in
HStack {
Text(text)
Spacer()
}
.contentShape(Rectangle())
}
.listStyle(.plain)
}
}
SwiftUI.ViewからPresenterを触らせずにデータをバインドするには、このような方法が良さそう、?
新たにContentViewDelegateを作成し、SwiftUI側からdelegateでメソッドを呼びます。ViewControllerをContentViewDelegateに準拠させイベントが送られたらViewController側で処理をすることが出来るようにしています。
我の担当しているプロジェクトも、UIViewのパーツを一つずつSwiftUIに置き換える方法で移行していくので、SwiftUIを子Viewとして追加をして、必要なデータをViewごとにStoreとして持つのは良さそう!
PresenterにObservableObjectが継承されていてバインドができるといっても、すべてのパーツでPresenterを参照するわけにもいかないし。
そもそも必要のない情報までPresenterを持つことで実行できて、参照できる状態は何か良くない気がする。
VIPERで書かれた例
以下は公式
・https://cheesecakelabs.com/blog/ios-project-architecture-using-viper/
・https://github.com/pedrohperalta/Articles-iOS-VIPER/blob/master/Articles/Modules/Articles/Presenter/ArticlesPresenter.swift
以下はSwiftUI+VIPER
・https://blog.personal-factory.com/2020/05/31/viper-architecture-in-swiftui/
・https://github.com/SatoTakeshiX/SwiftUI-VIPER-Architecture-Pattern
以下はConbine+VIPER
・https://zenn.dev/hicka04/articles/viper-combine
・https://github.com/hicka04/viper-combine-sample