Closed7

[SwiftUI]VIPER実装を参考にしたい我

ほへとほへと

VIPER実装例を集めて、今後の知識とした蓄えようの会です。
目に付いたサイトから見ていこうと思いまする。

ほへとほへと

まずはこれを読む。

https://tech.stmn.co.jp/entry/2023/09/01/132141


・画面遷移ロジックはUIKitのUINavigationControllerを使用する。
・SwiftUI.VIewの責務は、見た目の部分のみを担わせた方が良さそう。
・ObservableObjectを継承したクラスのインスタンス化をUIViewController側で行い、ライフサイクルをハンドリングしやすくする。
・SwiftUIのイベントをハンドリングする際は、処理を渡すこともできる。

struct ViewScreen: View {
    var tapHandler: (() -> Void)?

    var body: some View {
        Button {
            tapHandler?()
        } label: {
            Text("Tap Me !!")
        }
    } 
}
ほへとほへと

これを読む。

https://zenn.dev/voicy/articles/be329b697fb5ff


・上記同様で、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)
            ]
        )
    }
}
ほへとほへと

これを読む。

https://tech.niftylifestyle.co.jp/entry/706


・画面は従来通り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を持つことで実行できて、参照できる状態は何か良くない気がする。

このスクラップは5ヶ月前にクローズされました