I have a SwiftUI 2.0 app that uses CoreData to store data locally, and although the preview
mode can write data in memory or not (I tested both) when I try to save data to CoreData in the edition View, I get no error message but the data is not written to the sqlite data file for the simulator ... What am I doing wrong?
I have 2 entities in a one-to-many relationship:
Post:
PostPic:
I have extracted the main files below:
MyApp.swift
import SwiftUI
@main
struct MyApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
MyNavigationView().environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
Persistence.swift
import Foundation
import SwiftUI
import CoreData
import CloudKit
struct PersistenceController {
static let shared = PersistenceController()
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
for day in 1...10 {
let newPost = Post(context: viewContext)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy/MM/dd"
newPost.date = dateFormatter.date(from: "2021/02/\(String(format: "%02d", day))")!
newPost.id = UUID()
newPost.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
newpost.timestamp = Date()
for index in 1...7 {
let newPostPic = PostPic(context: viewContext)
if let image = UIImage(named:newPostPic.filename!) {
newPostPic.data = image.pngData()
}
newPost.addToFiles(newPostPic)
}
}
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentCloudKitContainer
private static var _model: NSManagedObjectModel?
private static func model(name: String) throws -> NSManagedObjectModel {
if _model == nil {
_model = try loadModel(name: name, bundle: Bundle.main)
}
return _model!
}
private static func loadModel(name: String, bundle: Bundle) throws -> NSManagedObjectModel {
guard let modelURL = bundle.url(forResource: name, withExtension: "momd") else {
throw CoreDataError.modelURLNotFound(forResourceName: name)
}
guard let model = NSManagedObjectModel(contentsOf: modelURL) else {
throw CoreDataError.modelLoadingFailed(forURL: modelURL)
}
return model
}
enum CoreDataError: Error {
case modelURLNotFound(forResourceName: String)
case modelLoadingFailed(forURL: URL)
}
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "curum")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
}
}
MyNavigationView.swift
struct MyNavigationView: View {
@Environment(\.managedObjectContext) private var viewContext
var body: some View {
NavigationView {
TabView { // tab bar at the bottom of the screen
HomeView()
.tabItem{
Image(systemName: "house.fill")
}.tag(0)
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("MyApp")
}
}
}
struct MyNavigationView_Previews: PreviewProvider {
static var previews: some View {
MyNavigationView()
}
}
HomeView.swift
//
// HomeView.swift
// curum
//
// Created by loic on 2021/02/19.
//
import SwiftUI
struct HomeView: View {
var body: some View {
ZStack {
// ...
VStack { // the plus button
Spacer()
HStack {
Spacer()
NavigationLink(destination: EditionView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)) {
ZStack {
Circle()
.frame(width: 80, height: 80)
Image(systemName: "plus")
.renderingMode(.template)
.font(.system(size: 60, weight: .light))
}
}
.padding(.trailing, 16)
.padding(.bottom, 16)
}
}
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}
EditionView.swift
import Foundation
import SwiftUI
import CoreData
struct EditionView: View {
@Environment(\.managedObjectContext) private var viewContext
@Environment(\.presentationMode) var mode: Binding<PresentationMode>
@State private var postText: String = ""
@State private var postImage: Image = Image("image1")
@State private var showingImagePicker = false
@State private var inputImage: UIImage?
var body: some View {
VStack {
TextEditor(text: $postText)
.lineSpacing(10)
.border(Color.gray)
.padding(.all)
HStack {
Button(action: {
self.showingImagePicker = true
}) {
if (inputImage == nil) {
Text("Pick Image")
} else {
postImage
.resizable()
.renderingMode(.original)
.frame(width: 100, height: 100)
}
}.sheet(isPresented: $showingImagePicker, onDismiss: loadImage) {
ImagePicker(image: self.$inputImage)
}
}
}
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action : {
self.mode.wrappedValue.dismiss()
}){
Image(systemName: "xmark")
},
trailing: Button {
let newPost = Post(use: viewContext)
newPost.date = Date()
newPost.id = UUID()
newPost.text = entryText
newPost.timestamp = Date()
let newPostPic = PostPic(use: viewContext)
newPostPic.data = inputImage!.pngData()
newPost.addToFiles(newPostPic)
do {
try viewContext.save()
mode.wrappedValue.dismiss()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
} label: {
Image(systemName: "checkmark.circle.fill")
})
}
func loadImage() {
guard let inputImage = inputImage else { return }
postImage = Image(uiImage: inputImage)
}
}
struct EditionView_Previews: PreviewProvider {
static var previews: some View {
EditionView()
}
}
struct ImagePicker: UIViewControllerRepresentable {
@Environment(\.presentationMode) var presentationMode
@Binding var image: UIImage?
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let uiImage = info[.originalImage] as? UIImage {
parent.image = uiImage
}
parent.presentationMode.wrappedValue.dismiss()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
}
** Edit in response to @Tushar Sharma**
I forgot to add that I added the following extension to NSManagedObject
to get rid of an error like in Multiple NSEntityDescriptions Claim NSManagedObject Subclass:
import Foundation
import CoreData
public extension NSManagedObject {
convenience init(use context: NSManagedObjectContext) {
let name = String(describing: type(of: self))
let entity = NSEntityDescription.entity(forEntityName: name, in: context)!
self.init(entity: entity, insertInto: context)
}
}
Sorry, it was a simple copy-paste problem in the HomeView.swift file:
On the following line, I used the preview persistence context ...
NavigationLink(destination: EditionView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)) {
... instead of using the shared one:
NavigationLink(destination: EditionView().environment(\.managedObjectContext, PersistenceController.shared.container.viewContext)) {
That solved my problem!