SwiftUI @State var 初始化问题

SwiftUI @State var initialization issue

我想通过 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

我该如何解决这个问题?非常感谢您!

我会尝试在 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

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

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

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

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

Bogdan Farca 的回答对于这种情况是正确的,但我们不能说这是所问问题的解决方案,因为我发现所问问题中的文本字段存在问题。我们仍然可以将 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")
    }
}

请参阅下面示例中的 .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)")
}
}

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

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

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

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

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

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

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

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

关于此的更多信息以及 DynamicProperty 的工作原理,here

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

 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 实例化方法。