#ios #swift #core-data #swiftui
#iOS #swift #основные данные #swiftui
Вопрос:
Приложение iOS, которое я создаю, перебирает список объектов PhraseGroup, определенных в Core Data, и отображает extraText
значение, связанное с каждой PhraseGroup .
Это верхняя часть сгенерированного Xcode подкласса NSManagedObject для 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
Я бы хотел, чтобы пользователи могли долго нажимать любую внетекстовую запись в моем отображаемом списке, а затем редактировать ее в модальном листе. Тем не менее, я изо всех сил пытаюсь найти способ передачи действительной ссылки на давно нажатую PhraseGroup на мой лист.
Ниже показано, как я отображаю список extraText
значений 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 showingPhraseGroupEditView = false
@State private var phraseGroupObjectID: NSManagedObjectID!
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
showingPhraseGroupEditView = true
print(phraseGroup.extraText!) // Sanity check to prove I've got hold of the right row
let phraseGroupForEditing = moc.object(with: phraseGroupObjectID) as! PhraseGroup
dump(phraseGroupForEditing)
print(phraseGroupForEditing.extraText!)
}
}
.onDelete(perform: delete)
}
.sheet(isPresented: $showingPhraseGroupEditView) {
self.phraseGroupObjectID == nil
?
TestPhraseGroupEditView(phraseGroupObjectID: phraseGroupObjectID, message: "phraseGroupObjectID is nil")
:
TestPhraseGroupEditView(phraseGroupObjectID: phraseGroupObjectID, message: "phraseGroupObjectID is not nil")
}
}
}
.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
)
}
Когда я долго нажимаю строку в своем списке, я вижу в своем отладчике, проходя через onLongPressGesture
закрытие, что я нашел правильную PhraseGroup (то есть ту, которую я нажал), что я правильно считываю эту PhraseGroup objectID
в @State var, а затем создаю другую ссылкув тот же объект Core Data и выводит его extraText
значение.
Однако к тому времени, когда сразу после этого отображается закрытие .sheet, для идентификатора phraseGroupObjectID установлено значение nil, что я ясно вижу, когда отображается TestPhraseGroupEditView .
import SwiftUI
import CoreData
struct TestPhraseGroupEditView: View {
@Environment(.managedObjectContext) var moc
var phraseGroupObjectID: NSManagedObjectID!
var message: String
var body: some View {
VStack (spacing: 20){
Text(message)
Text("Tap to create phraseGroup for debugger").onTapGesture {
dump(phraseGroupObjectID)
let phraseGroupForEditing = moc.object(with: phraseGroupObjectID) as! PhraseGroup
dump(phraseGroupForEditing)
}
}
}
}
Сообщение в первом текстовом поле читается phraseGroupObjectID is nil
, и щелчок по второму тексту приводит к первой печати dump() -nil
, за которой следует Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file AK6/TestPhraseGroupEditView.swift, line 22
Что я здесь не понимаю? Есть ли какой-то незначительный сбой в моей реализации, который я упустил из виду, или я делаю это совершенно неправильно? У Apple есть довольно хороший документ об ошибках или уникальности в https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/FaultingandUniquing.html , но ничто из того, что я там прочитал, не бросается мне в глаза как очевидное решение моей проблемы здесь.
Ответ №1:
Вместо того, чтобы использовать Bool для переключения листа, вы можете использовать необязательный объект и передавать данные непосредственно в лист. Измените определение phaseGroupObjectID на следующее
@State private var phraseGroupObjectID: NSManagedObjectID? = nil
Измените свой .onLongПредоСтавьте следующее
.onLongPressGesture {
phraseGroupObjectID = phraseGroup.objectID
print(phraseGroup.extraText!) // Sanity check to prove I've got hold of the right row
let phraseGroupForEditing = moc.object(with: phraseGroupObjectID) as! PhraseGroup
dump(phraseGroupForEditing)
print(phraseGroupForEditing.extraText!)
}
Измените свой лист на следующий
.sheet(item: $self.phraseGroupObjectID) { objID in
TestPhraseGroupEditView(phraseGroupObjectID: objID, message: "phraseGroupObjectID is not nil")
}
Теперь вы можете удалить showingPhraseGroupEditView .
Лист отображается всякий раз, когда идентификатор phaseGroupObjectID не равен нулю. Вы можете отклонить лист, снова установив phaseGroupObjectID равным нулю.
Комментарии:
1. браво. Пришлось реализовать
extension NSManagedObjectID: Identifiable {}
, чтобы избежать повторного несоответствия ошибокIdentifiable
. В TestPhraseGroupEditView теперьlet phraseGroupForEditing = moc.registeredObject(for: phraseGroupObjectID! as NSManagedObjectID) as! PhraseGroup
нужно захватить NSManagedObject . Изменено$self.phraseGroupObjectID
наself.$phraseGroupObjectID
. Не удалось найти способ привязки к phraseGroupObjectID в листе, чтобы я мог установить для него значение nil и закрыть лист с помощью кнопки, но если пользователь закрывает лист, перетаскивая его вниз, эффект эквивалентен.