Take this simple view. It has a @StateObject
that is used within the view to automatically load and parse some data. I have many of these views with different loaders and parsers.
struct SomeView {
@StateObject var loader: Loader<SomeParser> = Loader<SomeParser>()
var body: some View {
// Some body that uses the above loader
VStack {
// ...
}
}
}
The loaders are set to use @MainActor
and since the swift 5.6
update I get the new warning about initiating these with a default value and that it will be an error in swift 6
Expression requiring global actor 'MainActor' cannot appear in default-value expression of property '_loader'; this is an error in Swift 6
There's a simple fix, as explained here. We simply set it in the init
struct SomeView {
@StateObject var loader: Loader<SomeParser>
init() {
self._loader = StateObject(wrappedValue: Loader<SomeParser>())
}
var body: some View {
// Some body that uses the above loader
VStack {
// ...
}
}
}
Now the issue I have, is that I have 20 of these views, with different loaders and parsers and I have to go through each and add this init
.
I thought, let's simply create a class that does it and subclass it. But it's a View
struct
so that's not possible to subclass.
Then I had a go at using a protocol
, but I couldn't figure out a way to make it work as overriding the init()
in the protocol doesn't let you set self.loader = ...
Is there a better way to do this, or is adding an init
to every view the only way?
CodePudding user response:
Well, actually it is possible (I don't know all your 20 views, but still) to try using generics to separate common parts and generalise them via protocols and dependent views.
Here is a simplified demo of generalisation based on your provided snapshot. Tested with Xcode 13.2 / iOS 15.2
Note: as you will see the result is more generic, but it seems you will need more changes to adapt it than you would just change inits
- Separate model into protocol with associated type and required members
protocol LoaderInterface: ObservableObject { // observable
associatedtype Parser // associated parser
init() // needed to be creatable
var isLoading: Bool { get } // just for demo
}
- Generalize a view with dependent model and builder based on that model
struct LoadingView<Loader, Content>: View where Loader: LoaderInterface, Content: View {
@StateObject private var loader: Loader
private var content: (Loader) -> Content
init(@ViewBuilder content: @escaping (Loader) -> Content) {
self._loader = StateObject(wrappedValue: Loader())
self.content = content
}
var body: some View {
content(loader) // build content with loader inline
// so observing got worked
}
}
- Now try to use above to create concrete view based on concrete model
protocol Creatable { // just helper
init()
}
// another generic loader (as you would probably already has)
class MyLoader<T>: LoaderInterface where T: Creatable {
typealias Parser = T // confirm to LoaderInterface
var isLoading = false
private var parser: T
required init() { // confirm to LoaderInterface
parser = T()
}
}
class MyParser: Creatable {
required init() {} // confirm to Creatable
func parse() {}
}
// demo for specified `LoadingView<MyLoader<MyParser>>`
struct LoaderDemoView: View {
var body: some View {
LoadingView { (loader: MyLoader<MyParser>) in
Text(loader.isLoading ? "Loading..." : "Completed")
}
}
}