概要
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を追加することで差し替えが可能です。
これで、アプリとしての動作は通信を行い、プレビュー時は通信を行わない実装になりました。
@main
struct ProtocolObservableApp: App {
var body: some Scene {
WindowGroup {
ContentView(viewModel: TopContentViewModel())
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(viewModel: PreviewContentViewModel())
}
}
EnvironmentObjectの場合
EnvironmentObjectの場合も同じように差し替えることが可能です。
struct ContentView<ViewModel: ContentViewModel>: View {
@EnvironmentObject var viewModel: ViewModel
var body: some View {
Button {
viewModel.update()
} label: {
Text(viewModel.name)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView<PreviewContentViewModel>()
.environmentObject(PreviewContentViewModel())
}
}