核心位置为ObservableObject的SwiftUI崩溃

时间:2019-08-22 13:13:55

标签: core-location swiftui

我正在尝试使用Core Location获取CLRegionState来更新SwiftUI应用程序中的元素。我正在使用XCode 11 beta 6,并且在我的设备上安装了iOS 13 beta 7。

我可以看到两个问题:

  1. 应用程序崩溃,并且错误线程1:EXC_BAD_ACCESS出现在第147行(... ScrollView {...)

  2. CLRegionState永远不会被调用或不会更新。

我基于Paul Hudson的SwiftUI Beacon Detector教程(我也无法使它工作),并将其修改为使用CLRegionState而不是信标接近度。

代码如下:

import SwiftUI
import CoreLocation
import Combine

class MYLocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {


    var locationManager: CLLocationManager?
    var willChange = PassthroughSubject<Void, Never>()
    var lastRegionState = CLRegionState.unknown


    override init() {
        super.init()
        locationManager = CLLocationManager()
        locationManager?.delegate = self
        locationManager?.requestWhenInUseAuthorization()
    }

    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        checkLocationAuthorization()
    }

    func update(state: CLRegionState) {
        lastRegionState = state
        willChange.send(())
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else { return }
        print("Your location is \(location)")
        update(state: .unknown)
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error)
    }

    func startScanning() {

        // temporary coordinates
        var workCoordinates: CLLocationCoordinate2D {
            return CLLocationCoordinate2D(
                latitude: 43.486525,
                longitude: -11.912542)
        }

        var homeCoordinates = CLLocationCoordinate2D(
            latitude: 43.499541,
            longitude: -11.875079)

        let workRegion: CLCircularRegion = CLCircularRegion(center: workCoordinates, radius: 100, identifier: "Work")

        let homeRegion: CLCircularRegion = CLCircularRegion(center: homeCoordinates, radius: 100, identifier: "Home")

        locationManager!.startMonitoring(for: workRegion)
        locationManager!.startMonitoring(for: homeRegion)
        locationManager!.requestState(for: workRegion)
        locationManager!.requestState(for: homeRegion)
    }

    func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
        switch state {
        case .inside:
            switch region.identifier {
            case "Work":
                print("You are at work")
            case "Home":
                print("You are at home")
            default:
                print("unknown")
            }
        default:
            break
        }
    }

    func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {

        switch region.identifier {
        case "Work":
            print("Work**********")
        //self.taskTypeSegCtrl.selectedSegmentIndex = 0
        case "Home":
            print("Home*********8")
        //self.taskTypeSegCtrl.selectedSegmentIndex = 1
        default:
            break
        }
    }

    func checkLocationAuthorization() {
        switch CLLocationManager.authorizationStatus() {
        case .authorizedWhenInUse:
            startScanning()
            break
        case .authorizedAlways:
            startScanning()
            break
        case .denied:
            // show an alert instructing them howto turn on permissions
            break
        case .notDetermined:

            print("Location authorization is not determined.")
            locationManager!.requestAlwaysAuthorization()
            break
        case .restricted:
            break
        @unknown default:
            fatalError()
        }
    }
}


struct ContentView: View {
    @Environment(\.managedObjectContext) var managedObjectContext

    @FetchRequest(entity: Task.entity(),
                  sortDescriptors: [NSSortDescriptor(
                    keyPath: \Task.name, ascending: true)])
    var tasks: FetchedResults<Task>

    var locationManager = CLLocationManager()

    @ObservedObject var location: MYLocationManager = MYLocationManager()

    @State private var taskName = ""
    @State private var taskType = 0
    @State private var selectedTask = ""
    @State private var numberOfTaps = 0
    @State private var regionState = CLRegionState.unknown

    var body: some View {

        ScrollView {
            VStack {
                TextField("Enter a task name", text: $taskName)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                Picker(selection: $taskType, label: Text("Task type")) {
                    Text("Work").tag(1)
                    Text("Home").tag(2)
                }.pickerStyle(SegmentedPickerStyle())

                Text(selectedTask)

                Button(action: {
                    let task = Task(context: self.managedObjectContext)
                    task.name = self.taskName
                    task.type = Int16(self.taskType)
                    do {
                        try self.managedObjectContext.save()
                    } catch {
                        // handle the Core Data error
                    }
                    self.taskName = ""
                }) {
                    Text("Save Task")
                }.padding()

                Button(action: {
                    if self.numberOfTaps < self.tasks.count {
                        let task = self.tasks[self.numberOfTaps].name
                        self.selectedTask = task ?? "No task..."
                        self.numberOfTaps = self.numberOfTaps + 1
                    } else {
                        self.selectedTask = "No more tasks!  Have a wonderful day."
                    }
                }) {
                    Text("Next Task")
                }

                List {
                    ForEach(tasks, id: \.self) {
                        task in
                        VStack(alignment: .leading, spacing: 6) {
                            Text(task.name ?? "Unknown")
                                .font(.headline)
                            Text("Task type \(task.type)")
                                .font(.caption)
                        }
                    }.onDelete(perform: removeTask)

                }
            }   .frame(width: 300, height: 400, alignment: .top)
                .padding()
                .border(Color.black)

            if regionState == .inside {
                Text("inside")
            } else if regionState == .outside {
                Text("outside")
            } else {
                Text("unknown")
            }


            Spacer()
        }
    }


    func removeTask(at offsets: IndexSet) {
        for index in offsets {
            let task = tasks[index]
            managedObjectContext.delete(task)
            do {
                try managedObjectContext.save()
            } catch {
                // handle the Core Data error
            }
        }
    }

    func showTask(at offsets: IndexSet) {
        for index in offsets {
            let task = tasks[index]
            selectedTask = task.name ?? "No task..."
        }
    }
}

#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

实施Fabian所做的更改后,以下是控制台日志的内容:

已授予:true 2019-08-22 14:30:07.051062-0600 AppName [4452:2089841] locationManager(_管理器:CLLocationManager,didChangeAuthorization状态:CLAuthorizationStatus) 2019-08-22 14:30:07.052803-0600 New1Thing [4452:2089841] startScanning 2019-08-22 14:30:07.054319-0600 New1Thing [4452:2089841]当前位置:<+ .49945068,-* .87504490> +/- 65.00m(速度-1.00 mps /课程-1.00)@ 18/8/22,2:30:07 PM ****夏令时间

2 个答案:

答案 0 :(得分:1)

这是一个完整的工作示例。我解决了几个问题。

  1. ObservableObject现在可以与objectWillChange一起使用,而不是willChange
  2. 它现在应该在每次状态更改时更新。

我之前认为更新部分尚未完成

import SwiftUI
import CoreLocation
import Combine
import CoreData
import os

class MYLocationManager: NSObject, ObservableObject {
    var locationManager: CLLocationManager?
    var objectWillChange = PassthroughSubject<Void, Never>()
    @Published var lastRegionState = CLRegionState.unknown {
        willSet {
            objectWillChange.send()
        }
    }
    @Published var currentRegion: Region = .nowhereKnown {
        willSet {
            objectWillChange.send()
        }
    }

    override init() {
        super.init()
        locationManager = CLLocationManager()
        locationManager!.delegate = self
        locationManager!.requestWhenInUseAuthorization()
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        fatalError("error: \(error.localizedDescription)")
    }

    enum Region: String {
        case work = "Work"
        case home = "Home"
        case nowhereKnown = "Nowhere Known"
    }

    func startScanning() {
        os_log("startScanning")

        // temporary coordinates
        var workCoordinates: CLLocationCoordinate2D {
            return CLLocationCoordinate2D(
                latitude: 43.486525,
                longitude: -11.912542)
        }

        var homeCoordinates = CLLocationCoordinate2D(
            latitude: 43.499541,
            longitude: -11.875079)

        if let currentLocation = locationManager?.location {
            os_log("Current location: %@", currentLocation.description)
            homeCoordinates = currentLocation.coordinate
        } else {
            os_log("Current location: failed")
        }

        let workRegion: CLCircularRegion = CLCircularRegion(center: workCoordinates, radius: 100, identifier: Region.work.rawValue)
        let homeRegion: CLCircularRegion = CLCircularRegion(center: homeCoordinates, radius: 100, identifier: Region.home.rawValue)

        locationManager!.startMonitoring(for: workRegion)
        locationManager!.startMonitoring(for: homeRegion)
        locationManager!.requestState(for: workRegion)
        locationManager!.requestState(for: homeRegion)
    }
}

// MARK: Authorization
extension MYLocationManager {
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        os_log("locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus)")
        checkLocationAuthorization()
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        os_log("locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])")
        guard let location = locations.last else { return }
        print("Your location is \(location)")
        update(state: .unknown)
    }

    func checkLocationAuthorization() {
        switch CLLocationManager.authorizationStatus() {
        case .authorizedWhenInUse:
            startScanning()
            break
        case .authorizedAlways:
            startScanning()
            break
        case .denied:
            // show an alert instructing them howto turn on permissions
            break
        case .notDetermined:

            print("Location authorization is not determined.")
            locationManager!.requestAlwaysAuthorization()
            break
        case .restricted:
            break
        @unknown default:
            fatalError()
        }
    }
}

// MARK: UI Updates
extension MYLocationManager: CLLocationManagerDelegate {
    func updateCurrentRegion(region: CLRegion) {
        guard let region = Region(rawValue: region.identifier) else {
            currentRegion = .nowhereKnown
            return
        }
        currentRegion = region
    }

    func update(state: CLRegionState) {
        lastRegionState = state
    }

    func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
        self.lastRegionState = state
        updateCurrentRegion(region: region)
    }

    func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
        updateCurrentRegion(region: region)
    }

    func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
        updateCurrentRegion(region: region)
    }
}

struct CoreLocationView: View {
    private static func makeContainer() -> NSPersistentContainer {
        let store = NSPersistentContainer(name: "CoreLocationView")
        store.loadPersistentStores { (desc, err) in
            if let err = err {
                fatalError("core data error: \(err)")
            }
        }
        return store
    }

    let container: NSPersistentContainer

    init() {
        self.container = CoreLocationView.makeContainer()
    }

    var body: some View {
        CoreLocationView_NeedsEnv().environment(\.managedObjectContext, container.viewContext)
    }
}

struct CoreLocationView_NeedsEnv: View {
    @Environment(\.managedObjectContext) var managedObjectContext

    @FetchRequest(entity: Task.entity(),
                  sortDescriptors: [NSSortDescriptor(
                    keyPath: \Task.name, ascending: true)])
    var tasks: FetchedResults<Task>

    var locationManager = CLLocationManager()

    @ObservedObject var location: MYLocationManager = MYLocationManager()

    @State private var taskName = ""
    @State private var taskType = 0
    @State private var selectedTask = ""
    @State private var numberOfTaps = 0
    //@State private var regionState = CLRegionState.unknown

    var body: some View {
        ScrollView {
            VStack {
                TextField("Enter a task name", text: $taskName)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                Picker(selection: $taskType, label: Text("Task type")) {
                    Text("Work").tag(1)
                    Text("Home").tag(2)
                }.pickerStyle(SegmentedPickerStyle())

                Text(selectedTask)

                Button(action: {
                    let task = Task(context: self.managedObjectContext)
                    task.name = self.taskName
                    task.type = Int16(self.taskType)
                    do {
                        try self.managedObjectContext.save()
                    } catch {
                        // handle the Core Data error
                    }
                    self.taskName = ""
                }) {
                    Text("Save Task")
                }.padding()

                Button(action: {
                    if self.numberOfTaps < self.tasks.count {
                        let task = self.tasks[self.numberOfTaps].name
                        self.selectedTask = task ?? "No task..."
                        self.numberOfTaps = self.numberOfTaps + 1
                    } else {
                        self.selectedTask = "No more tasks!  Have a wonderful day."
                    }
                }) {
                    Text("Next Task")
                }

                List {
                    ForEach(tasks, id: \.self) {
                        task in
                        VStack(alignment: .leading, spacing: 6) {
                            Text(task.name ?? "Unknown")
                                .font(.headline)
                            Text("Task type \(task.type)")
                                .font(.caption)
                        }
                    }.onDelete(perform: removeTask)

                }
            }   .frame(width: 300, height: 400, alignment: .top)
                .padding()
                .border(Color.black)

            if location.lastRegionState == .inside {
                Text("inside")
            } else if location.lastRegionState == .outside {
                Text("outside")
            } else {
                Text("unknown")
            }

            Text("Where am I: \(location.currentRegion.rawValue)")


            Spacer()
        }
    }


    func removeTask(at offsets: IndexSet) {
        for index in offsets {
            let task = tasks[index]
            managedObjectContext.delete(task)
            do {
                try managedObjectContext.save()
            } catch {
                // handle the Core Data error
            }
        }
    }

    func showTask(at offsets: IndexSet) {
        for index in offsets {
            let task = tasks[index]
            selectedTask = task.name ?? "No task..."
        }
    }
}

答案 1 :(得分:0)

首先,我要感谢Fabian和graycampbell的帮助。

第二,据我所知@ObservableObject在使用XCode 11 beta 6的iOS 13 beta 8中仍然无法正常工作。

这对我有用: 1.我更改了

@ObservedObject var location: MYLocationManager = MYLocationManager()

收件人:

@EnvironmentObject var location: MYLocationManager

2。在SceneDelegate中,我添加了:

let myLocationManager = MYLocationManager()

和:

 window.rootViewController = UIHostingController(rootView: CoreLocationView_NeedsEnv()
            .environmentObject(myLocationManager)

不再崩溃!

P.S。我正在使用Fabian的更新代码。再次感谢!