CoreData 实体更改时如何更新视图

时间:2021-03-09 18:06:59

标签: swift swiftui

更新

根据@Tushar Sharma 的建议,我从 PlaceDetail.swift 和 EditPlaceForm.swift 中的 @ObservedObject 变量中删除了 place 标记。这使得 place 属性成为视图的参数而不是状态对象。然后我在 EditPlaceForm.swift 中创建了单独的状态变量来保存文本输入的值。我在视图中添加了一个 onAppear,它使用提供的 place 对象设置状态值。这很好用,并解决了后退按钮消失的问题。不过,它引入了一个新问题:PlaceDetail 在关闭并重新打开之前不会更新。然后我做了一些测试,看看更新 place 内的 PlaceDetail 变量是否会导致更新。为此,我只是添加了一个按钮,该按钮更改了地点的名称,并在按下时将其保存到视图上下文中。视图在重新加载之前仍然没有更新。因此,我的问题已切换到如何在更新 place 时更新视图,而不是在 EditPlaceForm.swift 中使用 @ObservedObject,这会导致后退按钮消失。

更新的代码片段

PlaceDetail.swift

原文:

@ObservedObject var place: Place

更新:

var place: Place

EditPlaceForm.swift

更新:

//
//  EditPlaceForm.swift
//

import SwiftUI
import CoreLocation

struct EditPlaceForm: View {
    
    @Environment(\.presentationMode) private var presentationMode
    @Environment(\.managedObjectContext) private var viewContext
    
    var place: Place
    
    @State private var name = ""
    @State private var street = ""
    @State private var city = ""
    @State private var state = ""
    @State private var zip = ""
    @State private var country = ""
    
    @State private var showAlert = false
    @State private var alertText = ""
    
    var body: some View {
        NavigationView {
            Form {
                Section (header: Text("Name")) {
                    TextField("Name", text: $name)
                }
                Section (header: Text("Address")) {
                    VStack {
                        TextField("Street", text: $street)
                        TextField("City", text: $city)
                        HStack {
                            TextField("State", text: $state)
                            Divider()
                            TextField("ZIP", text: $zip)
                        }
                        TextField("Country", text: $country)
                    }
                }
                Section(header: Text("Save")) {
                    Button (action: {
                        save()
                    }, label: {
                        Text("Save")
                    })
                }
            }
            .navigationTitle("Edit Place")
            .alert(isPresented: $showAlert, content: {
                Alert(title: Text("Error"), message: Text(alertText))
            })
        }
        .onAppear {
            loadPlaceData()
        }
    }
    
    func loadPlaceData() {
        name = place.name
        street = place.street
        city = place.city
        state = place.state
        zip = place.zip
        country = place.country
    }
    
    func save() {
        let geocoder = CLGeocoder()
        geocoder.geocodeAddressString("\(street), \(city) \(state), \(zip), \(country)", completionHandler: {
            placemarks, error in
            
            if error != nil {
                print("Error forward geocoding")
                print(error!.localizedDescription)
                alertText = "Encountered geocoding error."
                showAlert = true
                return
            }
            
            if placemarks == nil {
                print("No placemarks found")
                alertText = "Location could not be found."
                showAlert = true
                return
            }
            
            let placemark = placemarks![0]
            
            let testVars = [
                placemark.thoroughfare,
                placemark.locality,
                placemark.administrativeArea,
                placemark.country,
                placemark.postalCode
            ]
            for testVar in testVars {
                if testVar == nil {
                    print("Not enough information to complete place")
                    alertText = "Not enough information."
                    showAlert = true
                    return
                }
            }
            
            if placemark.location == nil {
                print("Not enough information to complete place")
                alertText = "Not enough information."
                showAlert = true
                return
            }
            
            viewContext.performAndWait {
                place.street = placemark.subThoroughfare! + " " + placemark.thoroughfare!
                place.city = placemark.locality!
                place.state = placemark.administrativeArea!
                place.country = placemark.country!
                place.zip = placemark.postalCode!
                place.latitude = placemark.location!.coordinate.latitude
                place.longitude = placemark.location!.coordinate.latitude
                do {
                    try viewContext.save()
                    presentationMode.wrappedValue.dismiss()
                } catch {
                    print("ERROR: FAILED TO SAVE PLACE DETAILS")
                    alertText = "Failed to save."
                    showAlert = true
                }
            }
        })
    }
}

struct EditPlaceForm_Previews: PreviewProvider {
    
    private static var viewContext = PersistenceController.preview.container.viewContext
    
    static var previews: some View {
        EditPlaceForm(place: Place.getDefault(context: viewContext))
    }
}

原帖

设置

我的应用有一个主页,它是一个导航视图。在该页面上,有导航链接。其中一个链接指向包含列表的“地点”页面。该页面上的列表项是名为 Place 的 CoreData 实体的详细信息页面的导航链接。在详细信息页面中,我有一个编辑按钮,可以将编辑表单作为工作表打开。

预期行为

您可以使用导航链接从“地点”视图中打开详细信息视图。然后单击编辑按钮并更改一些信息。单击保存时,编辑表单将关闭,更改将反映在详细信息视图中。然后,您可以单击后退按钮返回“地点”视图。

当前行为

您可以使用导航链接从“地点”视图中打开详细信息视图。然后单击编辑按钮并更改一些信息。保存时,编辑表单关闭,更改会反映在详细信息视图中,但返回按钮已消失,您卡在详细信息视图中。

视频:https://drive.google.com/file/d/1lzj-hOCb7hgMutQQwPl0ShR7l-c9sDib/view?usp=sharing

代码

“地点”视图:

//
//  PlacesList.swift
//

import SwiftUI

struct PlacesList: View {
    
    @Environment(\.managedObjectContext) private var viewContext
    
    @FetchRequest(entity: Place.entity(), sortDescriptors: [
        NSSortDescriptor(key: "name", ascending: true)
    ])
    private var places: FetchedResults<Place>
    
    @State private var showAddPlaceForm = false
    
    var body: some View {
        List {
            ForEach (places) { place in
                NavigationLink (destination: PlaceDetail(place: place)) {
                    PlaceRow(place: place)
                }
            }
            .onDelete(perform: deleteRow)
        }
        .listStyle(PlainListStyle())
        .navigationTitle(Text("Places"))
        .navigationBarItems(trailing:
            HStack {
                Button(action: {
                    showAddPlaceForm = true
                }, label: {
                    Image(systemName: "plus.circle")
                        .imageScale(.large)
                })
                .sheet(isPresented: $showAddPlaceForm) {
                    NewPlaceForm()
                }
            }
        )
    }
    
    func deleteRow(at offsets: IndexSet) {
        for offset in offsets {
            viewContext.performAndWait {
                viewContext.delete(places[offset])
                try? viewContext.save()
            }
        }
    }
}

struct PlacesList_Previews: PreviewProvider {
    
    private static var viewContext = PersistenceController.preview.container.viewContext
    
    static var previews: some View {
        NavigationView {
            PlacesList()
                .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
        }
    }
}

PlaceRow.swift

//
//  PlaceRow.swift
//

import SwiftUI

struct PlaceRow: View {
    
    @Environment(\.managedObjectContext) private var viewContext
    
    @ObservedObject var place: Place
    
    var body: some View {
        VStack (alignment: .leading) {
            Text(place.name)
                .font(.headline)
            Group {
                Text(place.street)
                Text(place.city) + Text(", ") + Text(place.state)
            }
            .font(.subheadline)
            .foregroundColor(.secondary)
        }
    }
}

struct PlaceRow_Previews: PreviewProvider {
    
    private static var viewContext = PersistenceController.preview.container.viewContext
    
    static var previews: some View {
        List {
            PlaceRow(place: Place.getDefault(context: viewContext))
        }
    }
}

PlaceDetail.swift

//
//  PlaceDetail.swift
//

import SwiftUI
import CoreLocation

struct PlaceDetail: View {
    
    @Environment(\.managedObjectContext) private var viewContext
    
    @ObservedObject var place: Place
    
    @State private var showEditForm = false
    
    var body: some View {
        List {
            VStack (alignment: .leading) {
                Text("Address")
                    .font(.headline)
                Group {
                    Text(place.street)
                    Text(place.cityStateZIPString)
                    Text(place.country)
                }
                .font(.subheadline)
                .foregroundColor(.secondary)
            }
            VStack {
                MapView(place: place)
                    .frame(height: 300)
            }
            if place.encodedAddress != nil {
                Link(destination:
                    URL(string: "http://maps.apple.com/?daddr=\(place.encodedAddress!)")!,
                    label: {
                        HStack {
                            Text("Get Directions with Apple Maps")
                            Spacer()
                            Image(systemName: "chevron.forward.circle")
                                .imageScale(.large)
                        }
                        .foregroundColor(.blue)
                    })
                Link(destination:
                        URL(string: "https://www.google.com/maps/search/?api=1&destination=\(place.encodedAddress!)")!,
                     label: {
                        HStack {
                            Text("Get Directions with Google Maps")
                            Spacer()
                            Image(systemName: "chevron.forward.circle")
                                .imageScale(.large)
                        }
                        .foregroundColor(.blue)
                     })
            }
            
        }
        .toolbar {
            Button(action: {
                showEditForm = true
            }, label: {
                Image(systemName: "pencil.circle")
            })
        }
        .navigationTitle(place.name)
        .sheet(isPresented: $showEditForm, content: {
            EditPlaceForm(place: place)
        })
    }
}

struct PlaceDetail_Previews: PreviewProvider {
    
    static var viewContext = PersistenceController.preview.container.viewContext
    
    static var previews: some View {
        NavigationView {
            PlaceDetail(place: Place.getDefault(context: viewContext))
                .environment(\.managedObjectContext, viewContext)
        }
    }
}

EditPlaceForm.swift

//
//  EditPlaceForm.swift
//

import SwiftUI
import CoreLocation

struct EditPlaceForm: View {
    
    @Environment(\.presentationMode) private var presentationMode
    @Environment(\.managedObjectContext) private var viewContext
    
    @ObservedObject var place: Place
    @State private var showAlert = false
    @State private var alertText = ""
    
    var body: some View {
        NavigationView {
            Form {
                Section (header: Text("Name")) {
                    TextField("Name", text: $place.name)
                }
                Section (header: Text("Address")) {
                    VStack {
                        TextField("Street", text: $place.street)
                        TextField("City", text: $place.city)
                        HStack {
                            TextField("State", text: $place.state)
                            TextField("ZIP", text: $place.zip)
                        }
                        TextField("Country", text: $place.country)
                    }
                }
                Section(header: Text("Save")) {
                    Button (action: {
                        save()
                    }, label: {
                        Text("Save")
                    })
                }
            }
            .navigationTitle("Edit Place")
            .alert(isPresented: $showAlert, content: {
                Alert(title: Text("Error"), message: Text(alertText))
            })
        }
    }
    
    func save() {
        let geocoder = CLGeocoder()
        geocoder.geocodeAddressString(place.address, completionHandler: {
            placemarks, error in
            
            if error != nil {
                print("Error forward geocoding")
                print(error!.localizedDescription)
                alertText = "Encountered geocoding error."
                showAlert = true
                return
            }
            
            if placemarks == nil {
                print("No placemarks found")
                alertText = "Location could not be found."
                showAlert = true
                return
            }
            
            let placemark = placemarks![0]
            
            let testVars = [
                placemark.thoroughfare,
                placemark.locality,
                placemark.administrativeArea,
                placemark.country,
                placemark.postalCode
            ]
            for testVar in testVars {
                if testVar == nil {
                    print("Not enough information to complete place")
                    alertText = "Not enough information."
                    showAlert = true
                    return
                }
            }
            
            if placemark.location == nil {
                print("Not enough information to complete place")
                alertText = "Not enough information."
                showAlert = true
                return
            }
            
            viewContext.performAndWait {
                place.street = placemark.subThoroughfare! + " " + placemark.thoroughfare!
                place.city = placemark.locality!
                place.state = placemark.administrativeArea!
                place.country = placemark.country!
                place.zip = placemark.postalCode!
                place.latitude = placemark.location!.coordinate.latitude
                place.longitude = placemark.location!.coordinate.latitude
                do {
                    try viewContext.save()
                    presentationMode.wrappedValue.dismiss()
                } catch {
                    print("ERROR: FAILED TO SAVE PLACE DETAILS")
                }
            }
        })
    }
}

struct EditPlaceForm_Previews: PreviewProvider {
    
    private static var viewContext = PersistenceController.preview.container.viewContext
    
    static var previews: some View {
        EditPlaceForm(place: Place.getDefault(context: viewContext))
    }
}

0 个答案:

没有答案