Emi*_*pis 44 swift swiftui xcode12
我想知道目前是否有(在询问时,第一个 Xcode 12.0 Beta)使用@StateObject
来自初始化程序的参数来初始化 a 的方法。
更具体地说,这段代码可以正常工作:
struct MyView: View {
@StateObject var myObject = MyObject(id: 1)
}
Run Code Online (Sandbox Code Playgroud)
但这不会:
struct MyView: View {
@StateObject var myObject: MyObject
init(id: Int) {
self.myObject = MyObject(id: id)
}
}
Run Code Online (Sandbox Code Playgroud)
从我理解的作用@StateObject
是使视图成为对象的所有者。我使用的当前解决方法是传递已经初始化的 MyObject 实例,如下所示:
struct MyView: View {
@ObservedObject var myObject: MyObject
init(myObject: MyObject) {
self.myObject = myObject
}
}
Run Code Online (Sandbox Code Playgroud)
但是现在,据我所知,创建对象的视图拥有它,而这个视图没有。
谢谢。
And*_*kyi 54
有StateObject
下一个 init: init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType)
。这意味着它将StateObject
在正确的时间创建对象的实例 - 在body
第一次运行之前。但这并不意味着您必须在 View 中的一行中声明该实例,例如@StateObject var viewModel = ContentViewModel()
.
我找到的解决方案是传递一个闭包并允许StateObject
在对象上创建实例。这个解决方案效果很好。有关更多详细信息,请阅读下面的长答案。
class ContentViewModel: ObservableObject {}
struct ContentView: View {
@StateObject private var viewModel: ContentViewModel
init(viewModel: @autoclosure @escaping () -> ContentViewModel) {
_viewModel = StateObject(wrappedValue: viewModel())
}
}
struct RootView: View {
var body: some View {
ContentView(viewModel: ContentViewModel())
}
}
Run Code Online (Sandbox Code Playgroud)
无论RootView
创建多少次body
, 的实例都ContentViewModel
只有一个。
通过这种方式,您可以初始化@StateObject
具有参数的视图模型。
在第一次运行之前创建一个 value 实例(SwiftUI@StateObject
中的 Data Essentials)。并且它在整个视图生命周期中保留该值的这一实例。您可以在 a 之外的某个位置创建视图的实例,您将看到of不会被调用。请参阅下面的示例:body
body
init
ContentViewModel
onAppear
struct ContentView: View {
@StateObject private var viewModel = ContentViewModel()
}
struct RootView: View {
var body: some View {
VStack(spacing: 20) {
//...
}
.onAppear {
// Instances of ContentViewModel will not be initialized
_ = ContentView()
_ = ContentView()
_ = ContentView()
// The next line of code
// will create an instance of ContentViewModel.
// Buy don't call body on your own in projects :)
_ = ContentView().view
}
}
}
Run Code Online (Sandbox Code Playgroud)
因此,将创建实例委托给StateObject
.
让我们考虑一个通过传递实例来创建StateObject
with实例的示例。当根视图触发 的额外调用时,将创建 的新实例。如果您的视图是整个屏幕视图,那可能会工作得很好。尽管如此,最好不要使用此解决方案。因为您永远无法确定父视图何时以及如何重绘其子视图。_viewModel = StateObject(wrappedValue: viewModel)
viewModel
body
viewModel
final class ContentViewModel: ObservableObject {
@Published var text = "Hello @StateObject"
init() { print("ViewModel init") }
deinit { print("ViewModel deinit") }
}
struct ContentView: View {
@StateObject private var viewModel: ContentViewModel
init(viewModel: ContentViewModel) {
_viewModel = StateObject(wrappedValue: viewModel)
print("ContentView init")
}
var body: some View { Text(viewModel.text) }
}
struct RootView: View {
@State var isOn = false
var body: some View {
VStack(spacing: 20) {
ContentView(viewModel: ContentViewModel())
// This code is change the hierarchy of the root view.
// Therefore all child views are created completely,
// including 'ContentView'
if isOn { Text("is on") }
Button("Trigger") { isOn.toggle() }
}
}
}
Run Code Online (Sandbox Code Playgroud)
我点击“触发”按钮 3 次,这是 Xcode 控制台中的输出:
视图模型初始化 内容视图初始化 视图模型初始化 内容视图初始化 视图模型初始化 内容视图初始化 ViewModel 解初始化 视图模型初始化 内容视图初始化 ViewModel 解初始化
正如您所看到的, 的实例ContentViewModel
被创建了很多次。这是因为当根视图层次结构发生更改时,其中的所有内容body
都会从头开始创建,包括ContentViewModel
. 无论您@StateObject
在子视图中将其设置为多少。您在根视图中调用的init
次数与根视图更新body
.
至于StateObject
init 中的 use 闭包 -init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType)
我们也可以使用它并传递闭包。ContentViewModel
代码与上一节(和)完全相同RootView
,但唯一的区别是使用闭包作为 的 init 参数ContentView
:
ViewModel init ContentView init ViewModel init ContentView init ViewModel init ContentView init ViewModel deinit ViewModel init ContentView init ViewModel deinit
点击“触发”按钮 3 次后 - 输出如下:
内容视图初始化 视图模型初始化 内容视图初始化 内容视图初始化 内容视图初始化
可以看到只ContentViewModel
创建了一个实例。也是ContentViewModel
在之后创建的ContentView
。
顺便说一句,最简单的方法是将属性设置为内部/公共并删除 init:
struct ContentView: View {
@StateObject private var viewModel: ContentViewModel
init(viewModel: @autoclosure @escaping () -> ContentViewModel) {
_viewModel = StateObject(wrappedValue: viewModel())
print("ContentView init")
}
var body: some View { Text(viewModel.text) }
}
Run Code Online (Sandbox Code Playgroud)
结果是一样的。但viewModel
在这种情况下不能是私有财产。
Mar*_*ark 33
@Asperi 给出的答案应该避免 Apple 在他们的 StateObject 文档中这么说。
您不直接调用此初始化程序。相反,在视图、应用程序或场景中使用 @StateObject 属性声明一个属性,并提供初始值。
苹果试图在幕后进行很多优化,不要与系统作斗争。
只需为您首先要使用的参数创建ObservableObject
一个Published
值。然后使用.onAppear()
来设置它的值,SwiftUI 将完成剩下的工作。
代码:
class SampleObject: ObservableObject {
@Published var id: Int = 0
}
struct MainView: View {
@StateObject private var sampleObject = SampleObject()
var body: some View {
Text("Identifier: \(sampleObject.id)")
.onAppear() {
sampleObject.id = 9000
}
}
}
Run Code Online (Sandbox Code Playgroud)
Asp*_*eri 24
这是解决方案的演示。使用 Xcode 12b 测试。
class MyObject: ObservableObject {
@Published var id: Int
init(id: Int) {
self.id = id
}
}
struct MyView: View {
@StateObject private var object: MyObject
init(id: Int = 1) {
_object = StateObject(wrappedValue: MyObject(id: id))
}
var body: some View {
Text("Test: \(object.id)")
}
}
Run Code Online (Sandbox Code Playgroud)
我想我找到了一种解决方法,能够控制用 @StateObject 包装的视图模型的实例化。如果您没有在视图上将视图模型设为私有,则可以使用合成的成员初始化,这样您就可以毫无问题地控制它的实例化。如果您需要一种公共方式来实例化视图,您可以创建一个工厂方法来接收视图模型依赖项并使用内部合成的 init。
import SwiftUI
class MyViewModel: ObservableObject {
@Published var message: String
init(message: String) {
self.message = message
}
}
struct MyView: View {
@StateObject var viewModel: MyViewModel
var body: some View {
Text(viewModel.message)
}
}
public func myViewFactory(message: String) -> some View {
MyView(viewModel: .init(message: message))
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
9233 次 |
最近记录: |