我正在构建的 iOS 应用程序遍历PhraseGroup
Core Data 中定义的对象列表,并显示extraText
与每个PhraseGroup
. 这显示了如何PhraseGroup
定义:
extension PhraseGroup {
@nonobjc public class func fetchRequest() -> NSFetchRequest<PhraseGroup> {
return NSFetchRequest<PhraseGroup>(entityName: "PhraseGroup")
}
@NSManaged public var extraText: String
@NSManaged public var phraseGroupID: UUID
@NSManaged public var text: String
@NSManaged public var phrase: NSSet?
@NSManaged public var piece: Piece
我希望用户能够长按extraText
列表中的任何条目,然后在模式表中编辑此字段。我不想为此使用 NavigationLink,因为我想将这样的链接用于其他功能。
下面显示了我如何列出 PhraseGroups,以及我如何显示模式表:
import SwiftUI
import CoreData
struct PhraseGroupView: View {
@Environment(\.managedObjectContext) var moc
@Binding var phraseGroupViewAlertItem: AlertItem?
@State private var isEditMode: EditMode = .inactive
@State private var phraseGroupObjectID: NSManagedObjectID? = nil
private var fetchRequest: FetchRequest<PhraseGroup>
private var phraseGroups: FetchedResults<PhraseGroup> { fetchRequest.wrappedValue }
var body: some View {
NavigationView {
VStack(spacing: 20){
Section {
List {
ForEach (phraseGroups, id: (\PhraseGroup.phraseGroupID)) { phraseGroup in
HStack {
Text("\(phraseGroup.wrappedExtraText)")
}
.onLongPressGesture {
phraseGroupObjectID = phraseGroup.objectID
}
}
.onDelete(perform: delete)
}
.sheet(item: self.$phraseGroupObjectID) { objID in
TestPhraseGroupEditView(phraseGroupObjectID: objID).environment(\.managedObjectContext, self.moc)
}
}
}
.navigationBarTitle("This is phraseGroup navBarTitle", displayMode: .inline)
.navigationBarItems(leading:
HStack {
Button(action: {
// yet to come
}) {
Image(systemName: "plus").resizable()
.frame(width: 16, height: 16)
.aspectRatio(contentMode: .fit)
.foregroundColor(.myKeyColor)
}
}, trailing:
HStack {
EditButton()
.frame(width: 60, height: 20)
.aspectRatio(contentMode: .fit)
.foregroundColor(.myKeyColor)
})
.environment(\.editMode, self.$isEditMode)
}
}
init (phraseGroupViewAlertItem: Binding<AlertItem?>, piece: Piece) {
self._phraseGroupViewAlertItem = phraseGroupViewAlertItem
fetchRequest = FetchRequest<PhraseGroup>(
entity: PhraseGroup.entity(),
sortDescriptors: [
NSSortDescriptor(key: "extraText", ascending: true)
],
predicate: NSPredicate(format: "piece == %@", piece)
// 'piece' in the predicate above is the name of a Piece <- PhraseGroup relationship defined in Core Data
)
}
这是我的模态表(目前非常简单,还没有尝试包含保存功能):
import SwiftUI
import CoreData
struct TestPhraseGroupEditView: View {
@Environment(\.managedObjectContext) var moc
@State private var extraTextForEditing = ""
var phraseGroupObjectID: NSManagedObjectID!
var phraseGroup: PhraseGroup {
moc.object(with: phraseGroupObjectID) as! PhraseGroup
}
var body: some View {
NavigationView {
Form {
Section {
TextField("Extra Text", text: $extraTextForEditing)
}
}
}
.onAppear {
phraseGroup.managedObjectContext!.performAndWait {
extraTextForEditing = phraseGroup.extraText
}
}
}
}
长按上一个列表后,此表显示正常。但是,当我打开 TextField 进行输入时,应用程序会抛出NSInternalInconsistencyException
原因An NSManagedObjectContext's retain policy cannot be changed while it has registered objects. Trying using reset() first.
我相信,我的容器和托管对象上下文的设置非常传统。唯一稍微不寻常的功能是,我在将托管对象上下文添加到环境的同时,添加了一个单例 PiecePlayer(我的应用程序的音频管理类)作为环境对象。
这是来自 AppDelegate.swift 的容器部分:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "AK6")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
这是 SceneDelegate.swift 中的上下文设置:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var pp = PiecePlayer()
@Environment(\.managedObjectContext) var moc
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let contentView = ContentView().environment(\.managedObjectContext, context).environmentObject(pp)
对于它的价值,我尝试context.retainsRegisteredObjects = false
在 SceneDelegate.swift 中显式设置,但这对抑制此错误没有影响。我相信无论如何这是默认设置。
我在 onAppear() 中使用 performAndWait 是由https://davedelong.com/blog/2018/05/09/the-laws-of-core-data/上关于更改“相同”对象的危险的警告提示的不同的托管对象上下文,但即使在阅读了关于模式视图没有继承的报告后,Environment(\.managedObjectContext)
因为它们与应用程序的视图层次结构分离,我不相信这就是这里发生的事情。为了更加安全,我在调用它时将托管对象上下文传递给模式视图,并在调试器中检查模式视图的托管对象上下文显示它不是 nil,并且它没有父级 - 两者这向我表明我正在处理“正确的”托管对象上下文。
如果我只能正式捕获它,这个异常是否可能实际上是良性的?如果是这样,我如何以及在哪里可以捕获异常?或者(并且可能最好),有没有办法我可以避免首先触发这个异常?