【问题标题】:Passing binding back to parent's parent view将绑定传递回父级的父级视图
【发布时间】:2021-04-14 00:44:19
【问题描述】:

我有 4 个视图。

  1. 祖父母
  2. 父母
  3. 孩子
  4. 编辑视图

Grandparent 有一个指向 Parent 的导航链接,Parent 有一个指向 Child 的导航链接。 Child 有一个按钮,该按钮通过 Parent 中的绑定和 Child 中的绑定从 Grandparent 初始化 @State 变量位置(类)。同一个按钮还更新了来自祖父的@State 变量 showEditView(再次通过绑定),它显示了 EditView。

目前有 11 行被注释掉。如果它们被注释掉,当我点击子视图中的按钮时,应用程序会抛出“致命错误:意外发现 nil ...”错误。如果任一部分未注释,则该按钮不会引发错误。

如果我将绑定位置传递给 EditView,而不是像我当前正在做的那样将属性本身传递给它,它也可以工作,然后将其包装为 @ObservedObject。

我不明白这里发生了什么。我唯一能做到的是,当它工作时,SwiftUI 正在更新 location 属性,因为它在正文中使用。如果是这种情况,这似乎表明我每次想要以这种方式传递属性时都必须包含该属性的隐藏文本视图。

祖父母

import SwiftUI

struct Grandparent: View {
    @State var location: Location!
    @State var showEditView: Bool = false
    
    var body: some View {
        NavigationView {
            VStack{
                NavigationLink(
                    destination: Parent(location: $location, showEditView: $showEditView)) {
                    Text("Navigate")
                }
// //               section 1
//                if location != nil {
//                    Text(location.name)
//                } else {
//                    Text("No location yet")
//                }
            }
// //            section 2
//            .navigationBarItems(trailing:
//                Button("Edit"){
//                    showEditView = true
//                }
//                .disabled(location == nil)
//            )
        }
        .padding()
        .sheet(isPresented: $showEditView) {
            EditView(placemark: location, dismiss: { showEditView = false })
        }
    }
}

父母

import SwiftUI

struct Parent: View {
    @Binding var location: Location!
    @Binding var showEditView: Bool
    
    var body: some View {
        NavigationLink(
            destination: Child(location: $location, showEditView: $showEditView),
            label: {
                Text("Child")
            })
    }
}

孩子

import SwiftUI

struct Child: View {
    @Binding var location: Location!
    @Binding var showEditView: Bool
    var body: some View {
        Button("Make location") {
            location = Location(name: "Lebanon")
            showEditView = true
        }
    }
}

编辑视图

import SwiftUI

struct EditView: View {
    @ObservedObject var placemark: Location
    var dismiss: () -> Void
    
    var body: some View {
        NavigationView {
            Text(placemark.name)
                .navigationTitle("Edit place")
                .navigationBarItems(trailing: Button("Done") { dismiss() })
        }
    }
}

位置

import Foundation

class Location: ObservableObject {
    init(name: String) {
        self.name = name
    }
    
    @Published var name: String
}

【问题讨论】:

  • 你有没有试过不使用强制换行可选@State var location: Location?
  • 您可能还想要@StateObject 而不是@State....而且不初始化它也没有意义

标签: ios swiftui


【解决方案1】:

您可能不应该将您的Location 声明为!,然后对它进行nil 检查。使用! 有点要求发生崩溃。我认为您遇到的是在设置location 之前呈现sheetwhen 在运行循环中没有任何保证会设置@State 变量,因此最好考虑到它是nil 的情况(绝对不要使用! 强制打开它)。

其次,至少考虑到您在这里的情况,您可能不应该将class 用于Location - 它应该只是struct

最终,您会遇到一点复杂性,因为根据您的视图名称判断,您想在某个时候编辑Location。使用Optional 会变得有点棘手,因为像TextField 这样的东西需要非可选值,但这可以通过各种方式解决(参见我在哪里使用nonNilBinding)。

这样的事情绝对比你目前正在做的更安全。它可能不是完全你想要的,但希望它能让你走上正确的道路。


struct Location {
    var name : String
}

struct Grandparent: View {
    @State var location: Location?
    @State var showEditView: Bool = false
    
    var body: some View {
        NavigationView {
            VStack{
                NavigationLink(
                    destination: Parent(location: $location, showEditView: $showEditView)) {
                    Text("Navigate")
                }
                if let location = location {
                    Text(location.name)
                } else {
                    Text("No location yet")
                }
            }
            .padding()
            .sheet(isPresented: $showEditView) {
                EditView(placemark: $location, dismiss: { showEditView = false })
            }
        }
    }
}


struct Parent: View {
    @Binding var location: Location?
    @Binding var showEditView: Bool
    
    var body: some View {
        NavigationLink(
            destination: Child(location: $location, showEditView: $showEditView),
            label: {
                Text("Child")
            })
    }
}

struct Child: View {
    @Binding var location: Location?
    @Binding var showEditView: Bool
    var body: some View {
        Button("Make location") {
            location = Location(name: "Lebanon")
            showEditView = true
        }
    }
}

struct EditView: View {
    @Binding var placemark: Location?
    var dismiss: () -> Void
    
    var nonNilBinding : Binding<Location> {
        .init { () -> Location in
            placemark ?? Location(name:"Default")
        } set: { (newValue) in
            placemark = newValue
        }

    }
    
    var body: some View {
        NavigationView {
            TextField("Name", text: nonNilBinding.name)
                    .navigationTitle("Edit place")
                    .navigationBarItems(trailing: Button("Done") { dismiss() })
        }
    }
}

【讨论】:

  • 当我第一次读到它们时,我认为隐式展开的可选项很愚蠢,但我比我预期的更快地遇到了一个用例,这基本上就是我想要在这里复制的内容。使用 MapKit,我有一个注释(我在这里使用的位置)。它是隐式展开的,因为当视图加载时,它是 nil。它只有在创建后才会被编辑,就像我在这里处理位置一样。调试时,我看到创建了位置,但是,就像你说的,“在设置位置之前渲染了工作表。”
  • 这就是为什么 location 是一个类——它只是我真正想要的东西的替代品,它是一个 MKPointAnnotation,是的,虽然很痛苦,但可以通过一个扩展和一个带有 getter 和 setter 的计算属性。虽然现在我说的是可选对象上的可选属性,所以还是有点不一样。
  • 隐式展开的选项似乎是一个相当冒险的计划。关于注释,我一直处理这些注释的方式是为地图动态生成它们——而不是持有对它们的引用。
猜你喜欢
  • 2014-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多