首页 > 解决方案 > SwiftUI 列表在删除后继续显示核心数据项

问题描述

我正在探索 SwiftUI,但遇到了一个问题。我有一个使用 Core Data 保存的歌手列表

let singer1 = Singer(context: viewContext)
singer1.firstName = "Taylor"
singer1.lastName = "Swift"

let singer2 = Singer(context: viewContext)
singer2.firstName = "Ed"
singer2.lastName = "Sheeran"

let singer3 = Singer(context: viewContext)
singer3.firstName = "Adele"
singer3.lastName = "Adkins"

try? viewContext.save()

我创建了另一个视图来填充歌手列表。

struct ListOfSingers<T: NSManagedObject, Content: View>: View {
@Environment(\.managedObjectContext) private var viewContext

@FetchRequest<T> var singers: FetchedResults<T>

let content: (T) -> Content
init(filterKey: String, filterValue: String, @ViewBuilder content: @escaping (T) -> Content) {
    _singers = FetchRequest<T>(entity: T.entity(), sortDescriptors: [], predicate: NSPredicate(format: "%K BEGINSWITH %@", filterKey, filterValue))
    self.content = content
    
}

var body: some View {
    
    List {
        ForEach(singers, id: \.self) {
            content($0)
        }.onDelete(perform: deleteSinger)
    }
    
}

func deleteSinger(at offsets: IndexSet) {
    withAnimation {
        
        offsets.map { singers[$0] }.forEach(viewContext.delete)
        
        try? viewContext.save()
        
    }
}

}

我使用谓词将它们分类为 2 个列表。我无法解决的问题是,当我使用谓词“S”并删除列表中的对象,然后返回谓词“A”并删除该列表中的歌手时,即使我检查了歌手,我仍然有歌手显示在那里' 数组,它说它是空的。我也可以与这个“幽灵”对象交互,但是如果我尝试删除这个对象应用程序崩溃,因为现在它知道数组是空的并且有这样一个对象要删除。

这是我的内容视图:

struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext

@State var lastNameFilter = "A"

var body: some View {
    NavigationView {
        VStack {
            ListOfSingers(filterKey: "lastName", filterValue: lastNameFilter) { (singer: Singer) in
                Text("\(singer.wrappedName) \(singer.wrappedLastName)")
                    .onAppear { print( "\(singer.wrappedName) \(singer.wrappedLastName)" )}
            }

            Button("Add Singers") {
                let singer1 = Singer(context: viewContext)
                singer1.firstName = "Taylor"
                singer1.lastName = "Swift"

                let singer2 = Singer(context: viewContext)
                singer2.firstName = "Ed"
                singer2.lastName = "Sheeran"

                let singer3 = Singer(context: viewContext)
                singer3.firstName = "Adele"
                singer3.lastName = "Adkins"

                try? viewContext.save()
            }
            .frame(width: 280, height: 50)
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
            .padding()

            HStack {
                Button("Sort by A") {
                    withAnimation {
                        lastNameFilter = "A"
                    }
                }
                .frame(width: 130, height: 50)
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(10)

                Spacer()

                Button("Sort by S") {
                    withAnimation {
                        lastNameFilter = "S"
                    }
                }
                .frame(width: 130, height: 50)
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(10)
            }
            .padding()
        }
        .navigationTitle("Singers") }}}

标签: listviewcore-dataswiftui

解决方案


问题是由于某种原因 ListOfSingers 结构被调用了两次。即使我从 Core Data 中删除了最后一位歌手,渲染时间也比重新索引 Core Data 更快。所以我最终得到了一个“幽灵视图”,它是在我的项目从核心数据中完全删除之前呈现的。当我尝试删除 Ghost 视图时,我遇到了一个不存在的数组。它总是导致应用程序崩溃。

解决方案是添加一个函数,在删除触发后立即检查 Core Data 中是否有任何项目。

  1. 在主视图中创建@State var noMoreSingers = false以检查应该呈现哪个视图并将这个条件添加到 VStack 以明确告诉应该呈现哪个视图:

     VStack {
         if !noMoreSingers {
             ListOfSingers(filterKey: "lastName", filterValue: lastNameFilter) { (singer: Singer) in
                 Text("\(singer.wrappedName) \(singer.wrappedLastName)")
             }  } else {
                 Spacer()
             }
    
  2. 添加一个@Binding var noMoreSingers: BooltoListOfSingers以便能够在 Core Data 中没有更多项目时告诉主视图

  3. 向 Persistanse Controller 添加函数以获取所有项目:

         func getAllSingers() -> [Singer] {
         let request: NSFetchRequest<Singer> = Singer.fetchRequest()
    
         do {
             return try container.viewContext.fetch(request)
         } catch {
             fatalError("Error Fetching Users")
         }
     }
    
  4. 将函数添加到 ListOfSingers 以触发getAllSingers并检查检索到的数组是否为空:

     func checkIfSingersAreEmpty() {
         let checkSingers = PersistenceController.shared.getAllSingers()
         if checkSingers.isEmpty {
             noMoreSingers = true
         }
     }
    

现在一切正常!


推荐阅读