首页 > 解决方案 > SwiftUI - 详细视图更改为 @fetchRequest 弹出导航链接视图

问题描述

我有两种看法。一种视图是使用@fetchRequest 从核心数据存储中获取的对象(人员)列表。另一个视图是详细视图,用户可以通过点击列表视图中的人员项目导航到该视图。这个想法是用户可以在详细视图中编辑一个人的详细信息(例如姓名、年龄)。到目前为止,一切都很好。

关键点是列表视图的设计使得并非所有人都必须从核心数据存储中获取。仅提取满足特定条件的人。让我们假设标准是这个人必须在 30 到 40 岁之间。

我的问题是,当用户将详细视图中的人的年龄更改为不符合获取请求标准的某个年龄时(例如,他将年龄从 35 岁更改为 20 岁),详细视图将在用户点击保存按钮,托管对象上下文保存,这是由@fetchRequest 在列表视图中注册的。

我知道会发生这种情况,因为驱动列表视图的人员的 fetchRequest 发生了变化,因为编辑的人被删除了,因为他不再满足 30 到 40 年的要求。但我不希望在用户仍在详细视图中时发生这种情况。点击保存按钮不应自​​动弹出详细视图。在使用@fetchRequest 时有什么方法可以防止这种情况发生吗?

这是导致所描述问题的代码的精简版本:

struct PersonList: View {
    @FetchRequest var persons: FetchedResults<Person>
    
    init() {
        let lowerAge = NSPredicate(format: "%K >= %@", #keyPath(Person.age), 30 as CVarArg)
        let upperAge = NSPredicate(format: "%K <= %@", #keyPath(Person.age), 40 as CVarArg)
        let agePredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [lowerAge, upperAge])
        _persons = FetchRequest(entity: Person.entity(), sortDescriptors: [], predicate: agePredicate)
    }
    
    var body: some View {
      NavigationView {
        ScrollView(showsIndicators: false) {
            LazyVStack(spacing: 10) {
                ForEach(persons, id: \.id) { person in
                    NavigationLink(destination: PersonDetail(person: person)) {
                        Text(person.name)
                    }
                }
            }
        }
      }
}

struct PersonDetail: View {
    @Environment(\.managedObjectContext) var viewContext
    @State var ageInput: String = ""
    var person: Person
    
    var body: some View {
        TextField("Enter Age", text: $ageInput)
        .onAppear(perform: {
           ageInput = "\(person.age)"
        })
        Button("Save", action: { save() } )
        }
    }

    func save() {
        if let age = Int(ageInput) {
            viewContext.saveContext()
        }     
    }
}

标签: iosswiftswiftui

解决方案


正如您所指出的,NavigationLink因为从 fetch 请求中删除了底层模型,所以会弹出。不幸的是,这意味着您必须将导航链接移到列表视图之外,以便即使模型从获取结果列表中删除,您也可以缓存模型。

一种方法是将您的 VStack/ScrollView 嵌入到 ZStack 中,并带有一个隐藏的“单例”导航链接和一个跟踪主动选择的人的@State 变量:

struct PersonList: View {
    @FetchRequest var persons: FetchedResults<Person>

    @State private var selectedPerson = Person(entity: Person.entity(), insertInto: nil)
    @State private var showPersonDetail = false

    var body: some View {
        NavigationView {
            ZStack {
                NavigationLink(destination: PersonDetail(person: selectedPerson), isActive: $showPersonDetail) { EmptyView() }.hidden()
                ScrollView(showsIndicators: false) {
                    LazyVStack(spacing: 10) {
                        ForEach(persons, id: \.id) { person in
                            Button(action: {
                                selectedPerson = person
                                showPersonDetail = true
                            }, label: {
                                // You could hack out the click/tap listener for a navigation link instead of
                                // emulating its appearance, but that hack could stop working in a future iOS release.
                                HStack {
                                    Text(person.name)
                                    Spacer()
                                    Image(systemName: "chevron.right")
                                }
                            })
                        }
                    }
                }
          }
      }
}

推荐阅读