I am new to SwiftUI and Coredata. I am working on an editor using NSAttributableString in an NSRepresentableView encapsulating an NSView.
The sequence is:
I created a "project" (in my app - Hermes) which has many different document types. The documents are listed in a left hand sidebar. Selecting one of the documents loads the center view (Document) with that text and the right sidebar (inspector) shows information about that document. (similar layout to XCode)
The documents are created and stored in CoreData when the user selects "Create new project" from the initial project screen.
That works correctly and the data is saved in Core Data as expected. The main screen is then displayed with the various documents listed in the left hadn Sidebar. The user then selects a sidebar item (document) and the text for that document is displayed.
The user then edits the text (type, change, delete, etc.) as the typing is done (not efficient, just for now) the DocumentView is called back as a delegate of the NSRepresentableView "coordinator". There the change is saved to CoreData as each letter or change is entered.
I print out a series of debugging prints tracing the location and the contents of the attrributedString.
I can see the string sent to the Document.textEdited(...) func. I check that the MOC viewContext has no changes, then I assign the updated string to the ManagedObject (sidebarItem.document?.text = attrString) and attempt to save it back using context.save().
The attempt reports that is recognizes the context has changes and it says it successfully saves the update. However, looking in the SQL store (using Core Data Lab) I can see the data is not updated. If I switch to another document and back, the changes appears to be in memory, but If I stop the app and restart it, the changes are lost. I can verify the changes are not reflected in the SQL store by using Core Data Lab to view the entities in the SQL tables.
If it would help to see/use the entire app it is in github at:
Hermes
ANY suggestions on how to fix or even debug this would be appreciated. I have been trying to solve this for over a week and have refactored the code several times and I can not see where the issue is.
Here is the clip of added save() in Persistence.swift - standard from App with SwftUI Template
public static func saveContext () {
print("\n---> saveContext()")
let context = shared.container.viewContext
if context.hasChanges {
print("\n----> viewContext has changes - try context.save()")
do {
try context.save()
print("\n----> viewContext saved changes")
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
} else {
print("\n----> viewContext does not have changes")
}
}
Here is the clip from the code initializing the CD store:
fileprivate func LoadSidebarChildren(children: [SidebarJSONModel], parent: Sidebar? = nil) {
children.forEach { childJSON in
let doc = Document(context: viewContext)
doc.id = UUID()
doc.text = createAttributedString(string: "\(childJSON.name)")
let sidebarItem = Sidebar(context: viewContext)
sidebarItem.id = UUID()
sidebarItem.iconName = childJSON.iconName
sidebarItem.index = seqNo; seqNo += 1
sidebarItem.name = childJSON.name
sidebarItem.type = childJSON.type
sidebarItem.document = doc
sidebarItem.parent = parent
PersistenceController.saveContext()
if sidebarItem.type == "Project" {
projectSidebarItem = sidebarItem
}
if let children = childJSON.children {
LoadSidebarChildren(children: children, parent: sidebarItem)
}
}
}
func createAttributedString(string: String) -> NSAttributedString {
let paragraph = NSMutableParagraphStyle()
paragraph.alignment = .center
let astr = NSMutableAttributedString(string: "\(string): ", attributes: [:])
let a1 = [NSAttributedString.Key.font: NSFont.boldSystemFont(ofSize: 18),
NSAttributedString.Key.foregroundColor: NSColor.systemBlue]
astr.append(NSAttributedString(string: "Hello ", attributes: a1))
let a2 = [NSAttributedString.Key.font: NSFont.systemFont(ofSize: 32),
NSAttributedString.Key.foregroundColor: NSColor.systemGreen]
astr.append(NSAttributedString(string: "World!", attributes: a2))
astr.addAttributes([.paragraphStyle: paragraph],
range: NSMakeRange(0, (astr.string as NSString).length))
return astr
}
}
Here is the code in DocumentView sending the text to the NSRepresentableView, and the teztedit func that is called back.
struct DocumentView: View {
let sidebar: Sidebar
@EnvironmentObject var currentSidebar: oCurrentSidebar
var body: some View {
print("\n\n_____________________________________________________\nDocumentView(\(sidebar.name)) - Document Selected")
var attrString = NSAttributedString()
if let attrStr = sidebar.document?.text {
print("\nDocumwntView: [\(attrStr)]\n")
attrString = attrStr
}
if currentSidebar.sidebar != sidebar {
currentSidebar.sidebar = sidebar
}
return GeometryReader { geometry in
NavigationView {
VStack {
VStack {
Divider()
CustomTextEditor(delegate: self, attrString: attrString)
}
.background(Color.white)
DocumentFooterView()
}
.frame( minWidth: geometry.size.width * 0.5, idealWidth: geometry.size.width * 0.75 )
.frame( maxWidth:.infinity, maxHeight: .infinity )
InspectorView()
}
}
}
func textEdited( attrString: NSAttributedString ) {
// first save attempt is just a check to verify no changes exist in the context yet. This is just for debugging
//
print("\n\n-> Document.textEdit(sidebar.document.text - before assignment : [\(String(describing: sidebar.document?.text))]")
PersistenceController.saveContext()
sidebar.document?.text = attrString
print("\n\n-> Document.textEdit(sidebar.document.text - after assignment : [\(String(describing: sidebar.document?.text))\n")
PersistenceController.saveContext()
}
}
Below is the sample printouts. (edit is to the word "World!" - it starts World! and is edited to be World! a)
// coordinator in NSRepresentableView
//
--> coordinator.textDidChange(Manuscript: {
NSFont = "\"Helvetica 12.00 pt. P [] (0x7fd23d580ba0) fobj=0x7fd20d4068a0, spc=3.33\"";
NSParagraphStyle = "Alignment 2, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (\n 28L,\n 56L,\n 84L,\n 112L,\n 140L,\n 168L,\n 196L,\n 224L,\n 252L,\n 280L,\n 308L,\n 336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation YES, HeaderLevel 0 LineBreakStrategy 0";
}Hello {
NSColor = "Catalog color: System systemBlueColor";
NSFont = "\".AppleSystemUIFontBold 18.00 pt. P [] (0x7fd20d50f4f0) fobj=0x7fd20d50f4f0, spc=4.15\"";
NSParagraphStyle = "Alignment 2, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (\n 28L,\n 56L,\n 84L,\n 112L,\n 140L,\n 168L,\n 196L,\n 224L,\n 252L,\n 280L,\n 308L,\n 336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation YES, HeaderLevel 0 LineBreakStrategy 0";
}World! a{
NSColor = "Catalog color: System systemGreenColor";
NSFont = "\".AppleSystemUIFont 32.00 pt. P [] (0x7fd20d512210) fobj=0x7fd20d512210, spc=6.97\"";
NSParagraphStyle = "Alignment 2, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (\n 28L,\n 56L,\n 84L,\n 112L,\n 140L,\n 168L,\n 196L,\n 224L,\n 252L,\n 280L,\n 308L,\n 336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation YES, HeaderLevel 0 LineBreakStrategy 0";
}
// Coordinator calls document.txtEdit
//
-> Document.textEdit(sidebar.document.text - before assignment : [Optional(Manuscript: {
NSFont = "\"Helvetica 12.00 pt. P [] (0x7fd23d580ba0) fobj=0x7fd20d4068a0, spc=3.33\"";
NSParagraphStyle = "Alignment 2, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (\n 28L,\n 56L,\n 84L,\n 112L,\n 140L,\n 168L,\n 196L,\n 224L,\n 252L,\n 280L,\n 308L,\n 336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation YES, HeaderLevel 0 LineBreakStrategy 0";
}Hello {
NSColor = "Catalog color: System systemBlueColor";
NSFont = "\".AppleSystemUIFontBold 18.00 pt. P [] (0x7fd20d50f4f0) fobj=0x7fd20d50f4f0, spc=4.15\"";
NSParagraphStyle = "Alignment 2, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (\n 28L,\n 56L,\n 84L,\n 112L,\n 140L,\n 168L,\n 196L,\n 224L,\n 252L,\n 280L,\n 308L,\n 336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation YES, HeaderLevel 0 LineBreakStrategy 0";
}World! a{
NSColor = "Catalog color: System systemGreenColor";
NSFont = "\".AppleSystemUIFont 32.00 pt. P [] (0x7fd20d512210) fobj=0x7fd20d512210, spc=6.97\"";
NSParagraphStyle = "Alignment 2, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (\n 28L,\n 56L,\n 84L,\n 112L,\n 140L,\n 168L,\n 196L,\n 224L,\n 252L,\n 280L,\n 308L,\n 336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation YES, HeaderLevel 0 LineBreakStrategy 0";
})]
---> saveContext()
----> viewContext does not have changes
-> Document.textEdit(sidebar.document.text - after assignment : [Optional(Manuscript: {
NSFont = "\"Helvetica 12.00 pt. P [] (0x7fd22d411a20) fobj=0x7fd20d4068a0, spc=3.33\"";
NSParagraphStyle = "Alignment 2, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (\n 28L,\n 56L,\n 84L,\n 112L,\n 140L,\n 168L,\n 196L,\n 224L,\n 252L,\n 280L,\n 308L,\n 336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation YES, HeaderLevel 0 LineBreakStrategy 0";
}Hello {
NSColor = "Catalog color: System systemBlueColor";
NSFont = "\".AppleSystemUIFontBold 18.00 pt. P [] (0x7fd20d50f4f0) fobj=0x7fd20d50f4f0, spc=4.15\"";
NSParagraphStyle = "Alignment 2, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (\n 28L,\n 56L,\n 84L,\n 112L,\n 140L,\n 168L,\n 196L,\n 224L,\n 252L,\n 280L,\n 308L,\n 336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation YES, HeaderLevel 0 LineBreakStrategy 0";
}World! a{
NSColor = "Catalog color: System systemGreenColor";
NSFont = "\".AppleSystemUIFont 32.00 pt. P [] (0x7fd20d512210) fobj=0x7fd20d512210, spc=6.97\"";
NSParagraphStyle = "Alignment 2, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (\n 28L,\n 56L,\n 84L,\n 112L,\n 140L,\n 168L,\n 196L,\n 224L,\n 252L,\n 280L,\n 308L,\n 336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation YES, HeaderLevel 0 LineBreakStrategy 0";
})
---> saveContext()
----> viewContext has changes - try context.save()
----> viewContext saved changes
And finally, here is the beginning of the data in the SQL store, note it does not change/update. And the "a" is not saved.
{
"$archiver" = NSKeyedArchiver;
"$objects" = (
"$null",
{
"$class" = "<CFKeyedArchiverUID 0x600001a84820 [0x7fff8007d050]>{value = 35}";
NSAttributeInfo = "<CFKeyedArchiverUID 0x600001a84220 [0x7fff8007d050]>{value = 33}";
NSAttributes = "<CFKeyedArchiverUID 0x600001a84260 [0x7fff8007d050]>{value = 4}";
NSDelegate = "<CFKeyedArchiverUID 0x600001a84ee0 [0x7fff8007d050]>{value = 0}";
NSString = "<CFKeyedArchiverUID 0x600001a86fc0 [0x7fff8007d050]>{value = 2}";
},
{
"$class" = "<CFKeyedArchiverUID 0x600001a843a0 [0x7fff8007d050]>{value = 3}";
"NS.string" = "Manuscript: Hello World!";
},
{
"$classes" = (
NSMutableString,
NSString,
NSObject
);
"$classname" = NSMutableString;
},