You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftData删除Book后返回列表崩溃问题求助

SwiftData删除数据后返回列表页崩溃问题

崩溃现象

在SwiftUI项目中使用SwiftData管理数据,从BooksListView通过NavigationLink跳转至BookCardDetailsView查看Book详情,在详情页通过Menu的Delete按钮执行modelContext.delete(book)后调用presentationMode.wrappedValue.dismiss()返回列表页时触发崩溃,报错信息:

Could not cast value of type 'Swift.Optional' (0x1e748b1f8) to 'Swift.String' (0x1e7483e68)

该崩溃仅在真机上稳定出现,模拟器偶发,崩溃堆栈关联到Book模型的name属性getter方法。

相关代码

列表页代码

struct BooksListView: View {
    @Query var books: [Book]

    @State private var searchedBook: String = ""
    
    var newBooks: [Book] {
        if searchedBook.isEmpty {
            return books
        } else {
            return books.filter {$0.name.contains(searchedBook)}
        }
    }
    
    var body: some View {
        ZStack {
            Color("customBackground").ignoresSafeArea()
            ScrollView(showsIndicators: false) {
                ForEach(newBooks) { book in
                    NavigationLink {
                        BookCardDetailsView(book: book)
                    } label: {
                        BookCardView(book: book)
                    }

                }
            }
            .searchable(text: $searchedBook, placement: .navigationBarDrawer(displayMode: .always))
            .navigationTitle("Books")
        }
    }
}

详情页删除逻辑代码

Menu {
    Button {
        modelContext.delete(book)
        presentationMode.wrappedValue.dismiss()
    } label: {
        Label("Delete", systemImage: "trash")
    }

} label: {
    Image(systemName: "ellipsis")
        .foregroundStyle(Color(.blue))
        .padding(6)

}

Book模型代码

@Model
final class Book: Identifiable {
    var id = UUID().uuidString
    var name: String
    var category: Category?
    var author: Author?
    var printDate: String
    var printLenght: Int
    var language: String
    var currentPage: Int = 0
    var isbn: String?
    
    @Attribute(.externalStorage)
    var imageData: Data?
    
    var isFavorite: Bool = false
    var isBookmark: Bool = false
    
    @Relationship(deleteRule: .nullify, inverse: \Category.books)
    @Relationship(deleteRule: .nullify, inverse: \Author.books)
    
    init(name: String, printDate: String, printLenght: Int, language: String, imageData: Data?) {
        self.name = name
        self.printDate = printDate
        self.printLenght = printLenght
        self.language = language
        self.imageData = imageData
    }
}

崩溃关联的name属性代码块

{
    @storageRestrictions(accesses: _$backingData, initializes: _name)
        init(initialValue) {
                    _$backingData.setValue(forKey: \.name, to: initialValue)
                    _name = _SwiftDataNoType()
        }

    get {
                    _$observationRegistrar.access(self, keyPath: \.name)
                    return self.getValue(forKey: \.name)
        }

    set {
                    _$observationRegistrar.withMutation(of: self, keyPath: \.name) {
                            self.setValue(forKey: \.name, to: newValue)
                    }
        }
}

原因分析

  1. 对象生命周期冲突:删除Book对象后,SwiftData会将该对象标记为已删除,底层存储的属性值会被置为nil,但模型中name定义为非可选String。此时立即返回列表页,@Query还未完成数据源同步,newBooks数组中仍包含已删除对象的引用,SwiftUI刷新视图时访问book.name,触发可选值转非可选的类型转换崩溃。
  2. 模型定义存在瑕疵:
    • 自定义id为字符串类型,SwiftData默认使用内部ID管理对象,自定义字符串ID可能导致对象追踪异常;
    • 两个@Relationship注解写在同一位置,未分别绑定到category和author属性,可能破坏SwiftData的关系映射逻辑。
  3. 视图刷新时机不匹配:删除后立即调用dismiss(),SwiftData的上下文变更还未完成持久化或通知视图更新,列表页视图仍尝试访问已失效的对象属性。

解决方案

方案1:延迟返回,确保上下文同步

删除后先提交上下文变更,再延迟执行返回操作,给@Query足够时间更新数据源:

Button {
    modelContext.delete(book)
    // 强制提交上下文变更
    do {
        try modelContext.save()
    } catch {
        print("Save error: \(error)")
    }
    // 延迟返回,等待列表页数据源更新
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
        presentationMode.wrappedValue.dismiss()
    }
} label: {
    Label("Delete", systemImage: "trash")
}

方案2:修正Book模型定义

  • 将自定义ID改为SwiftData推荐的UUID类型并标记为唯一;
  • 修正@Relationship的绑定位置,分别关联到对应属性:
@Model
final class Book: Identifiable {
    @Attribute(.unique) var id: UUID = UUID()
    var name: String
    
    // 分别为每个关联属性添加Relationship注解
    @Relationship(deleteRule: .nullify, inverse: \Category.books)
    var category: Category?
    
    @Relationship(deleteRule: .nullify, inverse: \Author.books)
    var author: Author?
    
    var printDate: String
    var printLenght: Int
    var language: String
    var currentPage: Int = 0
    var isbn: String?
    
    @Attribute(.externalStorage)
    var imageData: Data?
    
    var isFavorite: Bool = false
    var isBookmark: Bool = false
    
    init(id: UUID = UUID(), name: String, printDate: String, printLenght: Int, language: String, imageData: Data?) {
        self.id = id
        self.name = name
        self.printDate = printDate
        self.printLenght = printLenght
        self.language = language
        self.imageData = imageData
    }
}

方案3:使用NavigationStack管理导航(iOS 16+)

改用NavigationStack的path机制管理导航栈,避免已删除对象的引用残留:

struct BooksListView: View {
    @Query var books: [Book]
    @State private var searchedBook: String = ""
    @State private var navigationPath = NavigationPath()
    
    var newBooks: [Book] {
        searchedBook.isEmpty ? books : books.filter {$0.name.contains(searchedBook)}
    }
    
    var body: some View {
        NavigationStack(path: $navigationPath) {
            ZStack {
                Color("customBackground").ignoresSafeArea()
                ScrollView(showsIndicators: false) {
                    ForEach(newBooks) { book in
                        Button(action: {
                            navigationPath.append(book)
                        }) {
                            BookCardView(book: book)
                        }
                    }
                }
                .searchable(text: $searchedBook, placement: .navigationBarDrawer(displayMode: .always))
                .navigationTitle("Books")
            }
            .navigationDestination(for: Book.self) { book in
                BookCardDetailsView(book: book)
            }
        }
    }
}

删除后可直接通过navigationPath.removeLast()返回,避免无效对象引用。

内容的提问来源于stack exchange,提问作者madDogAttackToSwiftUI

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 16:50:42