Well, if the actual loading is as simple as getting a counter, we can use that fact to provide extremely simple load method in ViewModel:
func load() async {
clicked = await model.count
}
Since it won't be heavy, there is no need to worry about state and usage by several views.
Also, a ViewModel in SwiftUI is kinda redundant, SwiftUI views are already can be treated as view models, so I would eliminate it at all and moved its logic to the view itself:
@MainActor
struct ContentView: View {
// I haven't worked with new Observable macro,
// so I'm using observed object property wrapper
@ObservedObject
private var model: Model
@State
private var clicksCount = 0
var body: some View {
VStack {
Text("Clicked: \(clicksCount)")
Button {
Task {
await model.click()
}
} label: {
Text("Click Me")
}
}
.padding()
.task {
clicksCount = await model.count
for await count in model.channel {
clicksCount = count
}
}
}
}
I prefer to use a coordinator pattern, where all the routing is incapsulated with coordinator object. NavigationStack available from iOS 16 can be considered as one, but in large apps with complex routing I have found it hard to use. Anyway, the pattern itself decouples initialisation from view, so it becomes something like that roughly:
@MainActor
struct OriginView: View {
var body: some View {
Button(
action: {
Task { await onClipboardHistory() }
},
label: { Text("Clipboard History") }
)
ProgressView()
.progressViewStyle(.circular)
.hidden(!showActivityIndicator)
}
private func onClipboardHistory() async {
// this could be also delayed for better UX
// if opening doesn't take a lot of time
showActivityIndicator = true
await openClipboard()
showActivityIndicator = false
}
@State
var showActivityIndicator = false
let openClipboard: () async -> Void
}
final class ViewsCoordinator {
func openOrigin() {
let view = OriginView(openClipboard: { [weak self] in
await self?.openClipboard()
})
navigationController.viewControllers = [UIHostingController(rootView: view)]
}
func openClipboard() async {
let viewModel = await ViewModel(model: Model())
let view = ClipboardView(viewModel: viewModel)
navigationController.pushViewController(UIHostingController(rootView: view), animated: true)
}
}
In more simple apps (straight navigation logic, few screens) it would be easier of course to remove coordinator and perform this inside a view, to not introduce extra abstraction level in form of a coordinator.
EDIT: StateObject was incorrect, replaced with ObservedObject, since model will be passed from the outside.