SwiftUI @State var 初始化问题

Posted

技术标签:

【中文标题】SwiftUI @State var 初始化问题【英文标题】:SwiftUI @State var initialization issue 【发布时间】:2019-06-20 18:07:24 【问题描述】:

我想通过Structinit() 方法在SwiftUI 中初始化@State var 的值,以便它可以从准备好的字典中获取正确的文本,以便在TextField 中进行操作。 源代码如下所示:

struct StateFromOutside: View 
    let list = [
        "a": "Letter A",
        "b": "Letter B",
        // ...
    ]
    @State var fullText: String = ""

    init(letter: String) 
        self.fullText = list[letter]!
    

    var body: some View 
        TextField($fullText)
    

不幸的是,执行失败并出现错误Thread 1: Fatal error: Accessing State<String> outside View.body

我该如何解决这种情况?非常感谢您!

【问题讨论】:

使用State(initialValue:) @Daniel 请在第二个数字上以 150+ 作为已接受的答案。和我一样,很多人都错过了第二个答案,并且停留了很长时间。 most upvoted answer 可能是您想要的答案,而不是接受的答案。 【参考方案1】:

SwiftUI 不允许您在初始化程序中更改 @State,但您可以初始化它。

去掉默认值,直接使用_fullText设置@State,而不是通过属性包装访问器。

@State var fullText: String // No default value of ""

init(letter: String) 
    _fullText = State(initialValue: list[letter]!)

【讨论】:

这应该是公认的答案,在某些情况下 onAppear 可能为时已晚。 同意。这应该是公认的答案。我遇到了一个问题,只要我可以从 Picker 视图返回,属性值就会被重置。 @Diesel 起初我犹豫要不要使用它,因为我认为它正在访问一些内部 API,但事实证明它是 Swift 语言的一部分。有关属性包装器如何在后台工作的简明信息,包括合成下划线 (_),请参阅 propertyWrapper 下的 docs.swift.org/swift-book/ReferenceManual/Attributes.html 您还应该注意,您在init 中初始化的状态可能会在初始化所有存储属性后立即被覆盖。请参阅this comment,其中  工程师明确表示不要使用 init 初始化 @State 而是内联。 答案不是解决问题的办法。它不会像梦一样工作,而是噩梦。在这里,如果我们想改变视图中的值,我们需要使用可以在初始化器中初始化的绑定。在 init 中初始化 @State 只会在第一次创建此视图的值时进行。后续视图将不会看到在 init 中作为参数传递的值,而是由 SwiftUI 系统缓存的前一个值。【参考方案2】:

我会尝试在onAppear 中初始化它。

struct StateFromOutside: View 
    let list = [
        "a": "Letter A",
        "b": "Letter B",
        // ...
    ]
    @State var fullText: String = ""

    var body: some View 
        TextField($fullText)
             .onAppear 
                 self.fullText = list[letter]!
             
    

或者,更好的是,使用模型对象(链接到您的视图的BindableObject)并在那里执行所有初始化和业务逻辑。您的视图将自动更新以反映更改。


更新:BindableObject 现在称为 ObservableObject

【讨论】:

谢谢,这非常有效(尽管我已经在 TextField 周围的 VStack 上应用了 .onAppear)。 破解时间稍长我注意到您的解决方案是这种情况的解决方法,但是我遇到了更多必须初始化@State var的情况。虽然也可以在那里应用这种方法,但我认为这不是处理它的最佳方法。 @DanielMessner :是的,我同意,对于更复杂的情况,只需使用模型对象 (BindableObject) 并在该对象中进行初始化,无论是在运行 init() 时还是在视图中由.onAppear 事件。 这不是问题中特定情况的最佳解决方案 - 但它是另一种类型问题的最佳解决方案。我有一个@State,其初始值取决于我传入的另一个对象,因此每次显示视图时,我都希望它具有不同的初始值。另一个答案不能解决这个问题,但是在 onAppear 中设置状态可以。【参考方案3】:

现在在init 方法中设置@State 变量的默认值已经不是问题了。但是你必须去掉你给状态的默认值,它会按需要工作:

,,,
    @State var fullText: String // <- No default value here

    init(letter: String) 
        self.fullText = list[letter]!
    

    var body: some View 
        TextField("", text: $fullText)
    

【讨论】:

【参考方案4】:

Bogdan Farca 的答案对于这种情况是正确的,但我们不能说这是所提问题的解决方案,因为我发现所提问题中的 Textfield 存在问题。我们仍然可以将 init 用于相同的代码所以查看下面的代码,它显示了所提问题的确切解决方案。

struct StateFromOutside: View 
    let list = [
        "a": "Letter A",
        "b": "Letter B",
        // ...
    ]
    @State var fullText: String = ""

    init(letter: String) 
        self.fullText = list[letter]!
    

    var body: some View 
        VStack 
            Text("\(self.fullText)")
            TextField("Enter some text", text: $fullText)
        
    

只需在视图中调用即可使用它

struct ContentView: View 
    var body: some View 
        StateFromOutside(letter: "a")
    

【讨论】:

【参考方案5】:

上面的答案不正确。永远不要使用State(initialValue:)State(wrappedValue:) 来初始化Viewinit 中的状态。事实上,State 应该只内联初始化,像这样:

@State private var fullText: String = "The value"

如果这不可行,请使用 @Binding@ObservedObject@Binding@State 之间的组合,甚至是自定义 DynamicProperty

在您的具体情况下,@Bindable + @State + onAppear + onChange 应该可以解决问题。

更多关于这方面的信息以及DynamicPropertys 的工作原理,here。

【讨论】:

【参考方案6】:

您也可以创建视图模型并启动它:

 class LetterViewModel: ObservableObject 

     var fullText: String
     let listTemp = [
         "a": "Letter A",
         "b": "Letter B",
         // ...
     ]

     init(initialLetter: String) 
         fullText = listTemp[initialLetter] ?? ""
     
 

 struct LetterView: View 

     @State var viewmodel: LetterViewModel

     var body: some View 
    
         TextField("Enter text", text: $viewmodel.fullText)
     
 

然后像这样调用视图:

 struct ContentView: View 

     var body: some View 

           LetterView(viewmodel: LetterViewModel(initialLetter: "a"))
     
 

这样,您也不必调用 State 实例化方法。

【讨论】:

【参考方案7】:

请参阅下面示例中的.id(count)

import SwiftUI
import MapKit

struct ContentView: View 
@State private var count = 0

var body: some View 
    Button("Tap me") 
        self.count += 1
        print(count)
    
    Spacer()
    testView(count: count).id(count) // <------ THIS IS IMPORTANT. Without this "id" the initializer setting affects the testView only once and calling testView again won't change it (not desirable, of course)





struct testView: View 
var count2: Int
@State private var region: MKCoordinateRegion

init(count: Int) 
    count2 = 2*count
    print("in testView: \(count)")
    
    let lon =  -0.1246402 + Double(count) / 100.0
    let lat =  51.50007773 + Double(count) / 100.0
    let myRegion = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: lat, longitude: lon) , span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
    _region = State(initialValue: myRegion)


var body: some View 
    Map(coordinateRegion: $region, interactionModes: MapInteractionModes.all)
    Text("\(count2)")


【讨论】:

以上是关于SwiftUI @State var 初始化问题的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SwiftUI 中使用 UIButton 更改 @State var

从 @Binding var 修改 @State var 不会刷新 SwiftUI 中的视图

无法更新/修改 SwiftUI 视图的 @state var

为啥我的 SwiftUI 视图不会在更新 @State var 时更新?

SwiftUI - 使用 ForEach 时,您应该在子视图中使用 `@State var` 还是 `let`

在 SwiftUI 中用另一个 @State 属性值初始化 @State 属性