【问题标题】:SwiftUI with Core Location as ObservableObject crashes核心位置作为 ObservableObject 的 SwiftUI 崩溃
【发布时间】:2019-08-22 13:13:55
【问题描述】:

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

我可以看到两个问题:

  1. 应用程序崩溃,错误Thread 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所做的更改后,控制台日志的内容如下:

Granted: true
2019-08-22 14:30:07.051062-0600 AppName[4452:2089841] locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus)
2019-08-22 14:30:07.052803-0600 New1Thing[4452:2089841] startScanning
2019-08-22 14:30:07.054319-0600 New1Thing[4452:2089841] Current location: <+**.49945068,-***.87504490> +/- 65.00m (speed -1.00 mps / course -1.00) @ 8/22/19, 2:30:07 PM **** Daylight Time

【问题讨论】:

    标签: ios swift xcode swiftui core-location


    【解决方案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..."
            }
        }
    }
    

    【讨论】:

    • 感谢您的工作,但应用程序仍然在同一个地方崩溃。它适用于模拟器,但不适用于设备。
    • 我直接在我的 ipad pro 上运行它。您是否添加了为什么需要位置的描述?另外还需要添加定位功能。最好添加带有先前打印错误的完整错误。
    • 我在问题的末尾添加了控制台日志信息。应用程序的某些功能需要位置才能正常工作。我的设备上允许此应用的位置权限。
    • 请从那里复制到并包括崩溃。没有它,我看不到崩溃的确切位置:(
    • 我相信 iOS 和 iPadOS 现在是两种不同的动物。我必须是一个错误。再次感谢代码建议。我是一个相当缺乏经验的程序员,所以查看您的代码非常有帮助。我可以给你投票并在错误解决后将其标记为已解决。
    【解决方案2】:

    首先,我要感谢 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)
    

    不再崩溃!!

    附:我正在使用 Fabian 的更新代码。再次感谢!

    【讨论】:

      猜你喜欢
      • 2014-08-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-03
      相关资源
      最近更新 更多