SwiftUI - 简化在不同视图中相同的视图 init() 的最佳模式
SwiftUI - Best pattern to simplify a view init() that's the same across different views
采取这种简单的观点。它有一个 @StateObject
用于在视图中自动加载和解析一些数据。我有很多这样的视图,使用不同的加载器和解析器。
struct SomeView {
@StateObject var loader: Loader<SomeParser> = Loader<SomeParser>()
var body: some View {
// Some body that uses the above loader
VStack {
// ...
}
}
}
加载程序设置为使用 @MainActor
,自从 swift 5.6
更新后,我收到有关使用默认值启动这些程序的新警告,这将是 swift 6
中的错误
Expression requiring global actor 'MainActor' cannot appear in
default-value expression of property '_loader'; this is an error in
Swift 6
如 here 所述,有一个简单的修复方法。我们只需将它设置在 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 {
// ...
}
}
}
现在我遇到的问题是,我有 20 多个这样的视图,具有不同的加载器和解析器,我必须检查每个视图并添加这个 init
。
我想,让我们简单地创建一个 class 来执行它并子class 它。但它是 View
struct
所以不可能 subclass.
然后我尝试使用 protocol
,但我想不出一种方法让它工作,因为重写协议中的 init()
不允许您设置 self.loader = ...
是否有更好的方法来做到这一点,或者向每个视图添加 init
是唯一的方法吗?
嗯,实际上有可能(我不知道你所有的 20 多个观点,但仍然)尝试使用泛型来分离公共部分并通过协议和依赖视图概括它们。
这是一个基于您提供的快照的简化泛化演示。使用 Xcode 13.2 / iOS 15.2
测试
注意:正如您将看到的结果更通用,但看起来您需要进行更多更改来适应它,而不是仅更改 inits
- 将模型分离到具有关联类型和必需成员的协议中
protocol LoaderInterface: ObservableObject { // observable
associatedtype Parser // associated parser
init() // needed to be creatable
var isLoading: Bool { get } // just for demo
}
- 使用依赖模型和基于该模型的构建器概括视图
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
}
}
- 现在尝试使用上面的方法创建基于具体模型的具体视图
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")
}
}
}
采取这种简单的观点。它有一个 @StateObject
用于在视图中自动加载和解析一些数据。我有很多这样的视图,使用不同的加载器和解析器。
struct SomeView {
@StateObject var loader: Loader<SomeParser> = Loader<SomeParser>()
var body: some View {
// Some body that uses the above loader
VStack {
// ...
}
}
}
加载程序设置为使用 @MainActor
,自从 swift 5.6
更新后,我收到有关使用默认值启动这些程序的新警告,这将是 swift 6
中的错误
Expression requiring global actor 'MainActor' cannot appear in default-value expression of property '_loader'; this is an error in Swift 6
如 here 所述,有一个简单的修复方法。我们只需将它设置在 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 {
// ...
}
}
}
现在我遇到的问题是,我有 20 多个这样的视图,具有不同的加载器和解析器,我必须检查每个视图并添加这个 init
。
我想,让我们简单地创建一个 class 来执行它并子class 它。但它是 View
struct
所以不可能 subclass.
然后我尝试使用 protocol
,但我想不出一种方法让它工作,因为重写协议中的 init()
不允许您设置 self.loader = ...
是否有更好的方法来做到这一点,或者向每个视图添加 init
是唯一的方法吗?
嗯,实际上有可能(我不知道你所有的 20 多个观点,但仍然)尝试使用泛型来分离公共部分并通过协议和依赖视图概括它们。
这是一个基于您提供的快照的简化泛化演示。使用 Xcode 13.2 / iOS 15.2
测试注意:正如您将看到的结果更通用,但看起来您需要进行更多更改来适应它,而不是仅更改 inits
- 将模型分离到具有关联类型和必需成员的协议中
protocol LoaderInterface: ObservableObject { // observable
associatedtype Parser // associated parser
init() // needed to be creatable
var isLoading: Bool { get } // just for demo
}
- 使用依赖模型和基于该模型的构建器概括视图
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
}
}
- 现在尝试使用上面的方法创建基于具体模型的具体视图
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")
}
}
}