My question is a conceptual question.
I have the following code:
struct CategoriesList : View {
@State pri
You could take a look at my answer here.
Basically you create a model object conforming to BindableObject
:
class LoginModel : BindableObject {
var didChange = PassthroughSubject<LoginModel, Never>()
private(set) var username: String? {
didSet {
didChange.send(self)
}
}
func load() {
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
self.username = "Sorin"
}
}
}
This example simulates an async server call by using the plain ol' asyncAfter
.
Then, the View links with it and it's automatically updated when the model changes.
public struct LoginScreen: View {
@ObjectBinding var loginObject = LoginModel()
public var body: some View {
Group {
if login.username == nil {
Text("Trying to login, please wait...")
} else {
Text("Successful login, the username is \(loginObject.username!)")
}
}.onAppear {
self.loginObject.load()
}
}
}
The key here is to avoid trying to make the View
perform anything related to the Model
, except displaying it. SwiftUI
will resist you all the way :-)
iOS developers don't seem to be as familiar to this idea as Mac OS developers, in Mac apps we split the controller layer into ViewController and ModelControllers, ViewControllers are responsible for synchronise between the views and the model, ModelControllers are responsible for managing archiving of the model etc, so SwiftUI does away with ViewControllers, but if you have network handling then thats where ModelControllers would be useful, they could handle the synchronising between you remote source and you model, this is how I am currently doing it in a sample application I am working, though I have been wondering if Combine could be used to replace that as well, that will be my next thing to experiment with.
The SwiftUI community hasn't really established any best-practices yet because the technology is so new. My answer is based off of what I've seen from different WWDC19 sessions.
First, create a BindableObject
with a categories
property. Then write your network request code and set self.categories
to your newly downloaded categories.
import SwiftUI
import Combine
final class CategoryStore: BindableObject {
var didChange = PassthroughSubject<Void, Never>()
var categories = [String]()
init() {
// TODO: Fetch categories from API
self.categories = ["A", "B", "C"]
}
}
Then, add CategoryStore
to View
and use it with List
to iterate over the categories.
import SwiftUI
struct ContentView : View {
@ObjectBinding private var store = CategoryStore()
var body: some View {
List(store.categories.identified(by: \.self)) { category in
Text(category)
}
}
}
Whenever the categories
property updates, your UI will update with the new categories (tysm Combine)