繁体   English   中英

当 SwiftUI 拒绝时使用可选绑定

[英]Using optional binding when SwiftUI says no

问题

TL;DR:我试图绑定到TextField内部的String嵌套在Optional类型中,因此我无法直接执行此操作。 我已经尝试了下面列出的各种修复。

我是一个简单的人,我的用例也很简单——我希望能够使用 TextField 来编辑我的对象的名称
困难的产生是因为object 可能不存在

代码

剥离代码,代码看起来像这样。
请注意,示例 View没有考虑Optional

model

struct Foo {
  var name: String
}

extension Foo {
  var sampleData: [Foo] = [
    Foo(name: "Bar")
  ]
}

看法

再次,在没有Optionals 的完美世界中,它看起来像这样

struct Ashwagandha: View {
  @StateObject var ashwagandhaVM = AshwagandhaVM()
  var body: some View {
    TextField("", text: $ashwagandhaVM.currentFoo.name)
  }
}

查看 model

我故意打开可选的包装,使currentFoo: Foo?

class AshwagandhaVM: ObservableObject {
  @Published var currentFoo: Foo?

  init() {
    self.currentFoo = Foo.sampleData.first
  }
}

反复试验

以下是使TextFieldFoo.name朋友的徒劳承诺,并带有相关错误。

可选链接

“Xcode 修复”方式

TextField("", text: $ashwagandhaVM.currentFoo?.name)
进入添加/删除“?”/“!”的修复周期

绝望的方式

TextField("Change chatBot's name", text: $(ashwagandhaVM.currentFoo..name)
“'$' 不是标识符;使用反引号将其转义”

强制展开

愚蠢的方式

TextField("", text: $ashwagandhaVM.currentFoo..name)
“无法强制解包非可选类型‘Binding<Foo?>’的值”

更聪明的方法

if let asparagus = ashwagandhaVM.currentFoo.name {
  TextField("", text: $asparagus.name)
}

“在范围内找不到 $asparagus”

解决方法

我最喜欢的新报价方式

运气不好,因为String嵌套在Optional中; 我只是认为编辑String不应该有那么多麻烦。

这一切背后的理由

即为什么这个问题可能无关紧要

我正在重新学习 MVVM 的用法,尤其是如何使用嵌套数据类型。 我想检查在不为应用程序中每个 ViewModel 中的每个属性编写额外的 CRUD 层的情况下我能走多远。 如果您知道实现此目标的更好方法,请联系我。

问题评论中的人们给出了很好的建议。 不要这样做:更改您的视图 model 以提供一个非可选属性来代替。

但是......也许你被一个可选属性卡住了,出于某种原因你只需要绑定它。 在这种情况下,您可以创建一个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)
    }
}

这将使您绑定到文本字段。 如果支持属性为 nil,它将提供默认值。 如果支持属性更改,视图将重新呈现(只要它是您的@StateObject@ObservedObject@Published属性)。

我认为你应该改变方法,保存的控制应该保留在 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)
    }
}

更新

用整个结构做

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")
    }
  }
}

此外,SwiftUI 中的 MVVM 是一个坏主意,因为View数据结构比视图 model object 更好。

我已经编写了几个不错的通用可选Binding助手来解决这种情况。 请参阅此线程

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM