核心位置作为 ObservableObject 的 SwiftUI 崩溃
Posted
技术标签:
【中文标题】核心位置作为 ObservableObject 的 SwiftUI 崩溃【英文标题】:SwiftUI with Core Location as ObservableObject crashes 【发布时间】:2019-08-22 13:13:55 【问题描述】:我正在尝试使用 Core Location 获取 CLRegionState 来更新 SwiftUI 应用程序中的元素。我正在使用 XCode 11 beta 6 并在我的设备上安装了 ios 13 beta 7。
我可以看到两个问题:
应用程序崩溃,错误Thread 1: EXC_BAD_ACCESS
出现在147
行(...ScrollView ... )
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
实现F***所做的更改后,控制台日志的内容如下:
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
【问题讨论】:
【参考方案1】:这是一个完整的工作示例。我修复了几个问题。
ObservableObject
现在可以使用 objectWillChange
而不是 willChange
。
它现在应该在每次状态更改时更新。
之前的更新部分不完整(我的看法)
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】:首先,我要感谢 F*** 和 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)
不再崩溃!!
附:我正在使用 F*** 的更新代码。再次感谢!
【讨论】:
以上是关于核心位置作为 ObservableObject 的 SwiftUI 崩溃的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI = ObservableObject 作为类的选择
SwiftUI 致命错误:未找到“”类型的 ObservableObject