如何在SwiftUI视图中使用可观察对象类中的函数?

如何在SwiftUI视图中使用可观察对象类中的函数?,swift,swiftui,Swift,Swiftui,我试图创建一个位置数组作为一个可观察的对象变量,以便视图在数组发生更改时随时更新。正在另一个视图中添加/删除引脚中的数据。我已经创建了observable object类,如图所示,带有一个可识别的结构: struct Location: Identifiable { let id = UUID() let coordinate: CLLocationCoordinate2D let pinTitle: String let pinDescription: Str

我试图创建一个位置数组作为一个可观察的对象变量,以便视图在数组发生更改时随时更新。正在另一个视图中添加/删除
引脚中的数据。我已经创建了observable object类,如图所示,带有一个可识别的结构:

struct Location: Identifiable {
    let id = UUID()
    let coordinate: CLLocationCoordinate2D
    let pinTitle: String
    let pinDescription: String
}

class Pins: ObservableObject {
    @Environment(\.managedObjectContext) private var viewContext
    @FetchRequest(sortDescriptors: []) private var pins: FetchedResults<Pin>
    @Published var locations: [Location] = []

    func loadPins() -> [Location] {
        for pin in pins {
            locations.append(Location(coordinate: CLLocationCoordinate2D(latitude: pin.pinLatitude, longitude: pin.pinLongitude), pinTitle: pin.pinTitle ?? "error", pinDescription: pin.pinDescription ?? "error"))
        }
        return locations
    }
}
理想情况下,当从另一个视图对可观察对象数组进行添加/删除时,可观察对象应保持自动更新,但我甚至无法对其进行初始化。任何帮助都将不胜感激

解决方案:

解决这个问题的办法是提取核心数据。@FetchRequest属性包装器仅用于视图内部。下面的代码总结了该方法。此解决方案的所有功劳都归于。这篇博文的链接是。我已包含代码以供快速参考:

@main
struct MyApp: App {
    let persistenceManager: PersistenceManager
    @StateObject var pinItemStorage: PinItemStorage

    init() {
        let manager = PersistenceManager()
        self.persistenceManager = manager

        let managedObjectContext = manager.persistentContainer.viewContext
        let storage = PinItemStorage(managedObjectContext: managedObjectContext)
        self._pinItemStorage = StateObject(wrappedValue: storage)
    }

    var body: some Scene {
        WindowGroup {
            MotherView(pinItemStorage: pinItemStorage)
        }
    }
}

class PersistenceManager {
    let persistentContainer: NSPersistentContainer = {
// name is name of core data data model file
        let container = NSPersistentContainer(name: "MyAppModel")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()
}

extension Pin {
    static var dueSoonFetchRequest: NSFetchRequest<Pin> {
        let request: NSFetchRequest<Pin> = Pin.fetchRequest()
        request.sortDescriptors = []

        return request
    }
}


class PinItemStorage: NSObject, ObservableObject {
    @Published var dueSoon: [Pin] = []
    private let dueSoonController: NSFetchedResultsController<Pin>

    init(managedObjectContext: NSManagedObjectContext) {
        dueSoonController = NSFetchedResultsController(fetchRequest: Pin.dueSoonFetchRequest,
        managedObjectContext: managedObjectContext,
        sectionNameKeyPath: nil, cacheName: nil)

        super.init()

        dueSoonController.delegate = self

        do {
            try dueSoonController.performFetch()
            dueSoon = dueSoonController.fetchedObjects ?? []
        } catch {
            print("failed to fetch items!")
        }
    }
}

extension PinItemStorage: NSFetchedResultsControllerDelegate {
    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        guard let pins = controller.fetchedObjects as? [Pin]
            else { return }

        dueSoon = pins
    }
}

从CoreData获取后,需要包装MKPointAnnotations&Pins,如下所示:

import MapKit

extension MKPointAnnotation: ObservableObject{
    public var wrappedTitle: String{
        get{
            self.pinTitle ?? "No Title"
        }
        set{
            self.pinTitle = newValue
        }
    }
    
    public var wrappedSubtitle: String{
        get{
            self.pinDescription ?? "No information on this location"
        }
        set{
            self.pinDescription = newValue
        }
    }
}
您还可以尝试将以下函数添加到Location类中

import SwiftUI
import CoreLocation
import Combine

class LocationManager: NSObject, ObservableObject {
    private let locationManager = CLLocationManager()
    let objectWillChange = PassthroughSubject<Void, Never>()
    private let geocoder = CLGeocoder()
    
    @Published var status: CLAuthorizationStatus? {
        willSet { objectWillChange.send() }
    }
    
    @Published var location: CLLocation? {
        willSet { objectWillChange.send() }
    }
    
    @Published var placemark: CLPlacemark? {
        willSet { objectWillChange.send() }
    }
    
    
    override init() {
        super.init()
        
        self.locationManager.delegate = self
        self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
        self.locationManager.requestWhenInUseAuthorization()
        self.locationManager.startUpdatingLocation()
    }
    
    private func geocode() {
        guard let location = self.location else { return }
        geocoder.reverseGeocodeLocation(location, completionHandler: { (places, error) in
            if error == nil {
                self.placemark = places?[0]
            } else {
                self.placemark = nil
            }
        })
    }
}

extension LocationManager: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        self.status = status
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else { return }
        self.location = location
        self.geocode()
    }
}

extension CLLocation {
    var latitude: Double {
        return self.coordinate.latitude
    }
    
    var longitude: Double {
        return self.coordinate.longitude
    }
}
导入快捷界面
导入核心定位
进口联合收割机
类位置管理器:NSObject,observeObject{
私有let locationManager=CLLocationManager()
let objectWillChange=PassthroughSubject()
私有let geocoder=CLGeocoder()
@已发布变量状态:CLAuthorizationStatus{
willSet{objectWillChange.send()}
}
@发布的变量位置:CLLocation{
willSet{objectWillChange.send()}
}
@已发布变量placemark:CLPlacemark{
willSet{objectWillChange.send()}
}
重写init(){
super.init()
self.locationManager.delegate=self
self.locationManager.desiredAccuracy=KCallocationAccuracyBest
self.locationManager.requestWhenUseAuthorization()
self.locationManager.startUpdatingLocation()
}
专用func地理代码(){
guard let location=self.location else{return}
geocoder.reverseGeocodeLocation(位置,completionHandler:{(位置,错误)在
如果错误==nil{
self.placemark=位置?[0]
}否则{
self.placemark=nil
}
})
}
}
扩展位置管理器:CLLocationManagerDelegate{
func locationManager(\ manager:CLLocationManager,didChangeAuthorization状态:CLAuthorizationStatus){
self.status=状态
}
func locationManager(manager:CLLocationManager,didUpdateLocations位置:[CLLocation]){
guard let location=locations.last else{return}
self.location=位置
self.geocode()
}
}
分机定位{
纬度:双{
返回自坐标纬度
}
经度:双{
返回self.coordinate.longitude
}
}

我猜@FetchRequest在调用
loadPins
时没有实际执行。如果在
.onReceive(pinLocations.$pins)
上更改
onAppear
,会发生什么情况?FetchRequest用于视图内部,而不是对象内部。但就个人而言,我认为这是一个反模式API,因为现在(a)您的视图直接访问您的模型(b)他们解释原始存储模型(!!)和(c)您的应用程序的所有内容都与CoreData关联。如果您希望运行Firebase以实现跨平台实现,该怎么办?你必须改变一切。最佳实践是将数据库抽象出来,让服务来处理它。如果你刚开始观看CS193课程以获得这种模式,这是课程中令人失望的一部分。@qsaluan我喜欢这个博客作者/工程师给你的建议。如果它对您有效,请回来用词汇回答您自己的问题,这将有助于其他学习者了解该主题。让视图决定何时获取视图所依赖的数据。将@FetchRequest移动到视图中,请参考CoreData示例项目。Xcode>newproject>选中CoreData框并查看示例
import MapKit

extension MKPointAnnotation: ObservableObject{
    public var wrappedTitle: String{
        get{
            self.pinTitle ?? "No Title"
        }
        set{
            self.pinTitle = newValue
        }
    }
    
    public var wrappedSubtitle: String{
        get{
            self.pinDescription ?? "No information on this location"
        }
        set{
            self.pinDescription = newValue
        }
    }
}
import SwiftUI
import CoreLocation
import Combine

class LocationManager: NSObject, ObservableObject {
    private let locationManager = CLLocationManager()
    let objectWillChange = PassthroughSubject<Void, Never>()
    private let geocoder = CLGeocoder()
    
    @Published var status: CLAuthorizationStatus? {
        willSet { objectWillChange.send() }
    }
    
    @Published var location: CLLocation? {
        willSet { objectWillChange.send() }
    }
    
    @Published var placemark: CLPlacemark? {
        willSet { objectWillChange.send() }
    }
    
    
    override init() {
        super.init()
        
        self.locationManager.delegate = self
        self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
        self.locationManager.requestWhenInUseAuthorization()
        self.locationManager.startUpdatingLocation()
    }
    
    private func geocode() {
        guard let location = self.location else { return }
        geocoder.reverseGeocodeLocation(location, completionHandler: { (places, error) in
            if error == nil {
                self.placemark = places?[0]
            } else {
                self.placemark = nil
            }
        })
    }
}

extension LocationManager: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        self.status = status
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else { return }
        self.location = location
        self.geocode()
    }
}

extension CLLocation {
    var latitude: Double {
        return self.coordinate.latitude
    }
    
    var longitude: Double {
        return self.coordinate.longitude
    }
}