将可选绑定桥接到非可选子项 (SwiftUI)
Bridging Optional Binding to Non-Optional Child (SwiftUI)
我有一个 可能 存在的父状态:
class Model: ObservableObject {
@Published var name: String? = nil
}
如果那个状态存在,我想显示一个子视图。在此示例中,显示 name
.
如果 name
可见,我希望它可以显示和编辑。我希望这是双向可编辑的,这意味着如果 Model.name
发生变化,我希望它推送到 ChildUI,如果 ChildUI 编辑它,我希望它反映回 Model.name
.
但是,如果Model.name
变成nil
,我想ChildUI
隐藏。
当我这样做时,通过解包 Model.name
,然后只有第一个值被现在控制该状态的 Child
捕获。随后的更改不会向上游推送,因为它不是 Binding
.
问题
当可选的存在时,我可以将非可选的上游绑定到可选的吗? (这些词对吗?)
完整示例
import SwiftUI
struct Child: View {
// within Child, I'd like the value to be NonOptional
@State var text: String
var body: some View {
TextField("OK: ", text: $text).multilineTextAlignment(.center)
}
}
class Model: ObservableObject {
// within the parent, value is Optional
@Published var name: String? = nil
}
struct Parent: View {
@ObservedObject var model: Model = .init()
var body: some View {
VStack(spacing: 12) {
Text("Demo..")
// whatever Child loads the first time will retain
// even on change of model.name
if let text = model.name {
Child(text: text)
}
// proof that model.name changes are in fact updating other state
Text("\(model.name ?? "<waiting>")")
}
.onAppear {
model.name = "first change of optionality works"
loop()
}
}
@State var count = 0
func loop() {
async(after: 1) {
count += 1
model.name = "updated: \(count)"
loop()
}
}
}
func async(_ queue: DispatchQueue = .main,
after: TimeInterval,
run work: @escaping () -> Void) {
queue.asyncAfter(deadline: .now() + after, execute: work)
}
struct OptionalEditingPreview: PreviewProvider {
static var previews: some View {
Parent()
}
}
Child
应该对非可选字符串使用 Binding
,而不是使用 @State
,因为您希望它与其父级共享状态:
struct Child: View {
// within Child, I'd like the value to be NonOptional
@Binding var text: String
var body: some View {
TextField("OK: ", text: $text).multilineTextAlignment(.center)
}
}
Binding
具有将 Binding<V?>
转换为 Binding<V>?
的 an initializer,您可以像这样使用它:
if let binding = Binding<String>($model.name) {
Child(text: binding)
}
如果您因此而崩溃,那是 SwiftUI 中的错误,但您可以像这样解决它:
if let text = model.name {
Child(text: Binding(
get: { model.name ?? text },
set: { model.name = [=12=] }
))
}
像这样绑定你的变量。使用自定义绑定并使您的 child 视图变量 @Binding
.
struct Child: View {
@Binding var text: String //<-== Here
// Other Code
if model.name != nil {
Child(text: Binding($model.name)!)
}
我有一个 可能 存在的父状态:
class Model: ObservableObject {
@Published var name: String? = nil
}
如果那个状态存在,我想显示一个子视图。在此示例中,显示 name
.
如果 name
可见,我希望它可以显示和编辑。我希望这是双向可编辑的,这意味着如果 Model.name
发生变化,我希望它推送到 ChildUI,如果 ChildUI 编辑它,我希望它反映回 Model.name
.
但是,如果Model.name
变成nil
,我想ChildUI
隐藏。
当我这样做时,通过解包 Model.name
,然后只有第一个值被现在控制该状态的 Child
捕获。随后的更改不会向上游推送,因为它不是 Binding
.
问题
当可选的存在时,我可以将非可选的上游绑定到可选的吗? (这些词对吗?)
完整示例
import SwiftUI
struct Child: View {
// within Child, I'd like the value to be NonOptional
@State var text: String
var body: some View {
TextField("OK: ", text: $text).multilineTextAlignment(.center)
}
}
class Model: ObservableObject {
// within the parent, value is Optional
@Published var name: String? = nil
}
struct Parent: View {
@ObservedObject var model: Model = .init()
var body: some View {
VStack(spacing: 12) {
Text("Demo..")
// whatever Child loads the first time will retain
// even on change of model.name
if let text = model.name {
Child(text: text)
}
// proof that model.name changes are in fact updating other state
Text("\(model.name ?? "<waiting>")")
}
.onAppear {
model.name = "first change of optionality works"
loop()
}
}
@State var count = 0
func loop() {
async(after: 1) {
count += 1
model.name = "updated: \(count)"
loop()
}
}
}
func async(_ queue: DispatchQueue = .main,
after: TimeInterval,
run work: @escaping () -> Void) {
queue.asyncAfter(deadline: .now() + after, execute: work)
}
struct OptionalEditingPreview: PreviewProvider {
static var previews: some View {
Parent()
}
}
Child
应该对非可选字符串使用 Binding
,而不是使用 @State
,因为您希望它与其父级共享状态:
struct Child: View {
// within Child, I'd like the value to be NonOptional
@Binding var text: String
var body: some View {
TextField("OK: ", text: $text).multilineTextAlignment(.center)
}
}
Binding
具有将 Binding<V?>
转换为 Binding<V>?
的 an initializer,您可以像这样使用它:
if let binding = Binding<String>($model.name) {
Child(text: binding)
}
如果您因此而崩溃,那是 SwiftUI 中的错误,但您可以像这样解决它:
if let text = model.name {
Child(text: Binding(
get: { model.name ?? text },
set: { model.name = [=12=] }
))
}
像这样绑定你的变量。使用自定义绑定并使您的 child 视图变量 @Binding
.
struct Child: View {
@Binding var text: String //<-== Here
// Other Code
if model.name != nil {
Child(text: Binding($model.name)!)
}