Monday 28 September 2020

SwiftUI Preview Does Not Work with Core Data when Entity Injected in View

I've been frustrated for months trying to get previews to work in Xcode when Core Data is used. I can definitely make the preview work for the views that don't include an injected item. But not any subsequent views that depend on the source Entity.

Let's say I have a master/detail-like SwiftUI project with ContentView containing my list and ThingDetailView showing the detail for the entity Thing.

I created a wrapper for the preview of ContentView:

struct PreviewCoreDataWrapper<Content: View>: View {
    @Environment(\.managedObjectContext) private var viewContext
    let content: (NSManagedObjectContext) -> Content

    var body: some View {
        let managedObjectContext = viewContext
    
        let sampleThing = Thing(context: managedObjectContext)
        sampleThing.name = "Sample Name"
        sampleThing.comment = "Sample Comment"
        sampleThing.id = UUID()
        //more attributes

        return self.content(managedObjectContext)
    }

    init(@ViewBuilder content: @escaping (NSManagedObjectContext) -> Content) {
        self.content = content
    }
}

Then the preview in ContentView, this WORKS.

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
    
        PreviewCoreDataWrapper { managedObjectContext in
            ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
        }
    }
}

But then in the detail view, no matter what I have tried, I cannot get the preview to work. I've tried dozens of ways to satisfy the injected Thing call.

struct ThingDetailView_Previews: PreviewProvider {
    static var previews: some View {
        PreviewCoreDataWrapper { managedObjectContext in
            ThingDetailView(thing: sampleThing).environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
        }
    }
}

Any guidance would be greatly appreciated. Xcode Version 12.0 (12A7208), iOS 14

The ThingDetailView is pretty standard stuff:

struct ThingDetailView: View {

    @Environment(\.presentationMode) var presentationMode
    @Environment(\.managedObjectContext) private var managedObjectContext

    var thing: Thing

    @State private var localName: String = ""
    @State private var localComment: String = ""
    //bunch more properties

    var body: some View {
    
        DispatchQueue.main.async {
            self.localName = self.thing.wrappedName
            self.localComment = self.thing.wrappedComment
            //buch more properties
        }//dispatch
        DispatchQueue.main.async {
            if !showEditView {
            localNewUIImage = UIImage(data: thing.tImage)
            }
        }
    
        //you need this to allow the TextEditor background to be changed
        UITextView.appearance().backgroundColor = .clear

        return ScrollView(.vertical, showsIndicators: false) {
            VStack(alignment: .center, spacing: 20) {
            
                //this is for an image
                if !showEditView {
                ThingHeaderView(thing: thing)
                } else {
                    NewPhotoView(myImage: $localMyImage, newUIImage: $localNewUIImage, disableSaveButton: $localDisableSaveButton)
                }
            
                VStack(alignment: .leading, spacing: 10) {
                    Group {//group 1
                        //MARK: name
                        VStack (alignment: .leading) {
                            if !showEditView {
                                Text("\(thing.wrappedName)")
                                    .frame(maxWidth: .infinity, alignment: .leading)
                                    .padding(.vertical, 5)
                                    .textFieldStyle(RoundedBorderTextFieldStyle())
                                    .font(.system(size: 22, weight: .bold, design: .default))
                                    .foregroundColor(Color("CardBlue"))
                            } else {
                                TextField("tf name", text: self.$localName)
                                    .modifier(TextFieldSetup())
                            }
                        }//name v
                    
                        //MARK: comment
                        VStack (alignment: .leading) {
                            if !showEditView {
                                Text("\(thing.wrappedComment)")
                                    .frame(maxWidth: .infinity, alignment: .leading)
                                    .padding(.vertical, 5)
                                    .textFieldStyle(RoundedBorderTextFieldStyle())
                                    .font(.headline)
                            } else {
                                TextEditor(text: self.$localComment)
                                    .modifier(TextEditorSetup())
                            }
                        }//comment v
                        //bunch more TextFields and TextEditors
                    }//group 1
                }//inner v
                .padding(.horizontal, 20)
                .frame(maxWidth: 640, alignment: .center)
            }//outer v
        
            //seems like a title is needed to remofe large space at top - then hide
            .navigationBarTitle(thing.wrappedName, displayMode: .inline)
            //.navigationBarHidden(true)
            .navigationBarBackButtonHidden(showEditView)
            .navigationBarItems(
            
                leading:
                    Button(action: {
                        if showEditView {
                            self.showEditView = false
                        }
                    }) {
                        Text(showEditView ? "Cancel" : "")
                            .font(.system(size: 20))
                    },
                trailing:
                    Button(action: {
                        if !showEditView {
                            print("showEditView is \(showEditView)")
                            self.showEditView = true
                        } else {
                            self.saveEditedRecord()
                            self.showEditView = false
                        }
                    }) {
                        Image(systemName: self.showEditView ? "square.and.arrow.down.fill" : "square.and.pencil")
                            .font(.system(size: 25))
                            .frame(width: 60, height: 60)
                    }//images
                    .disabled(self.localDisableSaveButton)
            )//nav bar item
        }//scroll
        .navigationViewStyle(StackNavigationViewStyle())
    }//body

    func saveEditedRecord() {
        print("...and you are in the save function...")
        let context = self.managedObjectContext
        thing.id = UUID()
        //all the rest and 
        //standard Core Data save
    }//save record
}//struct thing detail view

And the ThingHeaderView:

struct ThingHeaderView: View {

    @Environment(\.horizontalSizeClass) var sizeClass
    @State private var isAnimatingImage: Bool = false

    var thing: Thing

    var body: some View {
    
        let sc = sizeClass == .compact
    
        return ZStack {
        
            if UIImage(data: thing.tImage) != nil {
                Image(uiImage: UIImage(data: thing.tImage)!)
                    .resizable()
                    .renderingMode(.original)
                    .aspectRatio(contentMode: .fit)
                    .cornerRadius(10)
                    .shadow(color: (Color.black.opacity(0.5)), radius: 8, x: 10, y: 10)
                    .padding(sc ? 6 : 10)
            } else {
                Image(systemName: "camera.circle.fill")
                    .resizable()
                    .frame(width: 60, height: 60, alignment: .center)
            }
        }
        .onAppear {
            withAnimation(.easeOut(duration: 0.5)) {
                isAnimatingImage = true
            }
        }
    }
}


from SwiftUI Preview Does Not Work with Core Data when Entity Injected in View

No comments:

Post a Comment