【发布时间】:2021-11-06 15:13:23
【问题描述】:
大家好,
我正在尝试弄清楚 CoreData 关系如何与选择器等 UI 元素一起使用。
目前,我有一个 3 视图应用程序(基于 Xcode 样板代码),它显示父实体列表,其中有子实体和子实体。我想要一个选择器来选择子实体应该引用哪个孙子。
目前我有两个有趣的副作用:
- 当我将应用程序作为预览运行时(因此有预填充的数据...此示例代码会在没有数据的情况下中断),
- picker 中选中的孙子是第一个的孙子 孩子,不管你第一次遇到哪个孩子 查看。
- 当我退回并选择另一个子实体时,现在被选中的子实体会从子实体中获取正确的初始选择
- 当我选择一个子项并“保存”它时,子项摘要中的值不会改变,直到我单击另一个子项,此时值会在转换到模态视图之前发生变化。
在 SwiftUI 中呈现模态时,我对事件顺序的理解显然遗漏了一些东西……有什么可以说明我做错了什么吗?
这里有一个视频可以更清楚地说明这一点: https://github.com/andrewjdavison/Test31/blob/main/Test31%20-%20first%20click%20issue.mov?raw=true
示例的 Git 存储库是 https://github.com/andrewjdavison/Test31.git,但总结一下:
数据模型:
查看源代码:
import SwiftUI
import CoreData
struct LicenceView : View {
@Environment(\.managedObjectContext) private var viewContext
@Binding var licence: Licence
@Binding var showModal: Bool
@State var selectedElement: Element
@FetchRequest private var elements: FetchedResults<Element>
init(currentLicence: Binding<Licence>, showModal: Binding<Bool>, context: NSManagedObjectContext) {
self._licence = currentLicence
self._showModal = showModal
let fetchRequest: NSFetchRequest<Element> = Element.fetchRequest()
fetchRequest.sortDescriptors = []
self._elements = FetchRequest(fetchRequest: fetchRequest)
_selectedElement = State(initialValue: currentLicence.wrappedValue.licenced!)
}
func save() {
licence.licenced = selectedElement
try! viewContext.save()
showModal = false
}
var body: some View {
VStack {
Button(action: {showModal = false}) {
Text("Close")
}
Picker(selection: $selectedElement, label: Text("Element")) {
ForEach(elements, id: \.self) { element in
Text("\(element.desc!)")
}
}
Text("Selected: \(selectedElement.desc!)")
Button(action: {save()}) {
Text("Save")
}
}
}
}
struct RegisterView : View {
@Environment(\.managedObjectContext) private var viewContext
@State var showModal: Bool = false
var currentRegister: Register
@State var currentLicence: Licence
init(currentRegister: Register) {
currentLicence = Array(currentRegister.licencedUsers! as! Set<Licence>)[0]
self.currentRegister = currentRegister
}
var body: some View {
VStack {
List {
ForEach (Array(currentRegister.licencedUsers! as! Set<Licence>), id: \.self) { licence in
Button(action: {currentLicence = licence; showModal = true}) {
HStack {
Text("\(licence.leasee!) : ")
Text("\(licence.licenced!.desc!)")
}
}
}
}
}
.sheet(isPresented: $showModal) {
LicenceView(currentLicence: $currentLicence, showModal: $showModal, context: viewContext )
}
}
}
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Register.id, ascending: true)],
animation: .default)
private var registers: FetchedResults<Register>
var body: some View {
NavigationView {
List {
ForEach(registers) { register in
NavigationLink(destination: RegisterView(currentRegister: register)) {
Text("Register id \(register.id!)")
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}
[1]: https://i.stack.imgur.com/AfaNb.png
【问题讨论】:
-
可能是因为您没有观察到
ObservableObjects/ CoreData 对象的变化。不要使用@Binding或@State和ObservableObjects/ CoreData 对象使用@ObservedObject代替。这也将让您摆脱LicenceView和selectedElement中的@FetchRequest,因为您可以改用使用$licence.licenced。通常,如果您发现自己在自定义init中完成所有这些工作,则 SwiftUI 不能很好地使用自定义inits,您需要重新考虑您的方法。 SwiftUI 会随时重新加载View,并且您的变量将重置。 -
另外,在
RegisterView中使用sheet(item:)和@State var currentLicence: Licence?可能会更好。知道你不会看到这个变量有任何变化,因为它没有观察,它的唯一目的是触发sheet。如果您想查看更改,您必须在子视图中使用@ObservedObject -
您的选择器缺少标签。选择不知道有变化
-
可能是类型问题尝试将您的标签更改为
.tag(element as Element?),当然licence.licenced应该是Element -
您还应该查看this
@ObservedObject不应该在它所在的View中初始化。它应该来自以前的View。如果你在init上这样做,你将会有泄漏和一些不稳定的情况@