[英]Using optional binding when SwiftUI says no
TL;DR: A String
I'm trying to bind to inside TextField
is nested in an Optional
type, therefore I cannot do that in a straightforward manner. TL;DR:我试图绑定到TextField
内部的String
嵌套在Optional
类型中,因此我无法直接执行此操作。 I've tried various fixes listed below.我已经尝试了下面列出的各种修复。
I'm a simple man and my use case is rather simple - I want to be able to use TextField to edit my object's name .我是一个简单的人,我的用例也很简单——我希望能够使用 TextField 来编辑我的对象的名称。
The difficulty arises due to the fact that the object might not exist .困难的产生是因为object 可能不存在。
Stripping the code bare, the code looks like this.剥离代码,代码看起来像这样。
Please note that that the example View does not take Optional
into account请注意,示例 View没有考虑Optional
struct Foo {
var name: String
}
extension Foo {
var sampleData: [Foo] = [
Foo(name: "Bar")
]
}
again, in the perfect world without Optionals it would look like this再次,在没有Optionals 的完美世界中,它看起来像这样
struct Ashwagandha: View {
@StateObject var ashwagandhaVM = AshwagandhaVM()
var body: some View {
TextField("", text: $ashwagandhaVM.currentFoo.name)
}
}
I'm purposely not unwrapping the optional, making the currentFoo: Foo?
我故意不打开可选的包装,使currentFoo: Foo?
class AshwagandhaVM: ObservableObject {
@Published var currentFoo: Foo?
init() {
self.currentFoo = Foo.sampleData.first
}
}
Below are the futile undertakings to make the TextField
and Foo.name
friends, with associated errors.以下是使TextField
和Foo.name
朋友的徒劳承诺,并带有相关错误。
TextField("", text: $ashwagandhaVM.currentFoo?.name)
gets into the cycle of fixes on adding/removing "?"/"!"进入添加/删除“?”/“!”的修复周期
TextField("Change chatBot's name", text: $(ashwagandhaVM.currentFoo..name)
"'$' is not an identifier; use backticks to escape it" “'$' 不是标识符;使用反引号将其转义”
TextField("", text: $ashwagandhaVM.currentFoo..name)
"Cannot force unwrap value of non-optional type 'Binding<Foo?>'" “无法强制解包非可选类型‘Binding<Foo?>’的值”
if let asparagus = ashwagandhaVM.currentFoo.name {
TextField("", text: $asparagus.name)
}
"Cannot find $asparagus in scope" “在范围内找不到 $asparagus”
No luck, as the String
is nested inside an Optional
;运气不好,因为String
嵌套在Optional
中; I just don't think there should be so much hassle with editing a String
.我只是认为编辑String
不应该有那么多麻烦。
ie why this question might be irrelevant即为什么这个问题可能无关紧要
I'm re-learning about the usage of MVVM, especially how to work with nested data types.我正在重新学习 MVVM 的用法,尤其是如何使用嵌套数据类型。 I want to check how far I can get without writing an extra CRUD layer for every property in every ViewModel in my app.我想检查在不为应用程序中每个 ViewModel 中的每个属性编写额外的 CRUD 层的情况下我能走多远。 If you know any better way to achieve this, hit me up.如果您知道实现此目标的更好方法,请联系我。
Folks in the question comments are giving good advice.问题评论中的人们给出了很好的建议。 Don't do this: change your view model to provide a non-optional property to bind instead.不要这样做:更改您的视图 model 以提供一个非可选属性来代替。
But... maybe you're stuck with an optional property, and for some reason you just need to bind to it.但是......也许你被一个可选属性卡住了,出于某种原因你只需要绑定它。 In that case, you can create a Binding
and unwrap by hand:在这种情况下,您可以创建一个Binding
并手动解包:
class MyModel: ObservableObject {
@Published var name: String? = nil
var nameBinding: Binding<String> {
Binding {
self.name ?? "some default value"
} set: {
self.name = $0
}
}
}
struct AnOptionalBindingView: View {
@StateObject var model = MyModel()
var body: some View {
TextField("Name", text: model.nameBinding)
}
}
That will let you bind to the text field.这将使您绑定到文本字段。 If the backing property is nil it will supply a default value.如果支持属性为 nil,它将提供默认值。 If the backing property changes, the view will re-render (as long as it's a @Published
property of your @StateObject
or @ObservedObject
).如果支持属性更改,视图将重新呈现(只要它是您的@StateObject
或@ObservedObject
的@Published
属性)。
I think you should change approach, the control of saving should remain inside the model, in the view you should catch just the new name and intercept the save button coming from the user:我认为你应该改变方法,保存的控制应该保留在 model 中,在视图中你应该只捕获新名称并拦截来自用户的保存按钮:
class AshwagandhaVM: ObservableObject {
@Published var currentFoo: Foo?
init() {
self.currentFoo = Foo.sampleData.first
}
func saveCurrentName(_ name: String) {
if currentFoo == nil {
Foo.sampleData.append(Foo(name: name))
self.currentFoo = Foo.sampleData.first(where: {$0.name == name})
}
else {
self.currentFoo?.name = name
}
}
}
struct ContentView: View {
@StateObject var ashwagandhaVM = AshwagandhaVM()
@State private var textInput = ""
@State private var showingConfirmation = false
var body: some View {
VStack {
TextField("", text: $textInput)
.padding()
.textFieldStyle(.roundedBorder)
Button("save") {
showingConfirmation = true
}
.padding()
.buttonStyle(.bordered)
.controlSize(.large)
.tint(.green)
.confirmationDialog("are you sure?", isPresented: $showingConfirmation, titleVisibility: .visible) {
Button("Yes") {
confirmAndSave()
}
Button("No", role: .cancel) { }
}
//just to check
if let name = ashwagandhaVM.currentFoo?.name {
Text("in model: \(name)")
.font(.largeTitle)
}
}
.onAppear() {
textInput = ashwagandhaVM.currentFoo?.name ?? "default"
}
}
func confirmAndSave() {
ashwagandhaVM.saveCurrentName(textInput)
}
}
UPDATE更新
do it with whole struct用整个结构做
struct ContentView: View {
@StateObject var ashwagandhaVM = AshwagandhaVM()
@State private var modelInput = Foo(name: "input")
@State private var showingConfirmation = false
var body: some View {
VStack {
TextField("", text: $modelInput.name)
.padding()
.textFieldStyle(.roundedBorder)
Button("save") {
showingConfirmation = true
}
.padding()
.buttonStyle(.bordered)
.controlSize(.large)
.tint(.green)
.confirmationDialog("are you sure?", isPresented: $showingConfirmation, titleVisibility: .visible) {
Button("Yes") {
confirmAndSave()
}
Button("No", role: .cancel) { }
}
//just to check
if let name = ashwagandhaVM.currentFoo?.name {
Text("in model: \(name)")
.font(.largeTitle)
}
}
.onAppear() {
modelInput = ashwagandhaVM.currentFoo ?? Foo(name: "input")
}
}
func confirmAndSave() {
ashwagandhaVM.saveCurrentName(modelInput.name)
}
}
struct ContentView: View {
@StateObject var store = Store()
var body: some View {
if let nonOptionalStructBinding = Binding($store.optionalStruct)
TextField("", text: nonOptionalStructBinding.name)
}
else {
Text("optionalStruct does not exist")
}
}
}
Also, MVVM in SwiftUI is a bad idea because the View
data struct is better than a view model object.此外,SwiftUI 中的 MVVM 是一个坏主意,因为View
数据结构比视图 model object 更好。
I've written a couple nice generic optional Binding
helpers to address cases like this.我已经编写了几个不错的通用可选Binding
助手来解决这种情况。 See this thread .请参阅此线程。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.