36
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ObservableObjectをProtocolで差し替える

Posted at

概要

SwiftUIでのアプリ開発では、多くの場合はObservableObjectプロトコルを組み合わせるかと思います。
ObservableObjectプロトコル対応させたclassを作成し、ObservedObjectに設定して使います。

以下はTopContentViewModelをObservableObjectとして作成しています。

struct ContentView: View {
    @ObservedObject var viewModel: TopContentViewModel

    var body: some View {
        Button {
            viewModel.update()
        } label: {
            Text(viewModel.name)
        }
    }
}

@MainActor
class TopContentViewModel: ObservableObject {
    @Published var name: String = Date.now.description

    func update() {
        Task {
            let (data, _) = try await URLSession.shared.data(
                from: .init(string: "https://httpbin.org/get")!)
            name = String(data: data, encoding: .utf8) ?? "nil"
        }
    }
}

しかし、現在のTopContentViewModelは通信を伴うものになっていますが、プレビューではわざわざ通信させたくない、ダミーのViewModelに差し替えて通信をさせないようにしたい、という考えもあるかと思います。

WWDCの動画にStructure your app for SwiftUI previewsという動画がありますので、そこの内容を参考にObservedObjectを差し替えられるようにします。

ObservableObjectを継承したprotocolを作成する

ObservableObjectを継承したprotocolを作成します。ここではContentViewModelとします。
@Publishedとして設定しているプロパティは{ get set }としておきます。

@MainActor
protocol ContentViewModel: ObservableObject {
    var name: String { get set }

    func update()
}

ObservableObjectを継承したprotocolを使ってclassを作成する

そして、各種ObservableObjectはContentViewModelに変更します。
アプリ用に使うTopContentViewModelとは別にプレビュー用のPreviewContentViewModelを作成しました。

@MainActor
class TopContentViewModel: ContentViewModel {
    @Published var name: String = Date.now.description

    func update() {
        Task {
            let (data, _) = try await URLSession.shared.data(
                from: .init(string: "https://httpbin.org/get")!)
            name = String(data: data, encoding: .utf8) ?? "nil"
        }
    }
}

private class PreviewContentViewModel: ContentViewModel {
    @Published var name: String = Date.now.description

    func update() {
        name = Date.now.description
    }
}

protocolを使ったViewを定義する

先ほど作ったprotocolを使うようにViewを変更します。
ジェネリクスを設定し、ObservedObjectも変更します。

struct ContentView<ViewModel: ContentViewModel>: View {
    @ObservedObject var viewModel: ViewModel

    var body: some View {
        Button {
            viewModel.update()
        } label: {
            Text(viewModel.name)
        }
    }
}

Viewの呼び出し

ObservedObjectでは、Viewの引数にContentViewModelプロトコルを継承したViewModelを追加することで差し替えが可能です。
これで、アプリとしての動作は通信を行い、プレビュー時は通信を行わない実装になりました。

app
@main
struct ProtocolObservableApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView(viewModel: TopContentViewModel())
        }
    }
}
preview
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(viewModel: PreviewContentViewModel())
    }
}

EnvironmentObjectの場合

EnvironmentObjectの場合も同じように差し替えることが可能です。

view
struct ContentView<ViewModel: ContentViewModel>: View {
    @EnvironmentObject var viewModel: ViewModel

    var body: some View {
        Button {
            viewModel.update()
        } label: {
            Text(viewModel.name)
        }
    }
}
preview
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView<PreviewContentViewModel>()
            .environmentObject(PreviewContentViewModel())
    }
}
36
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?