【发布时间】:2023-03-22 08:47:02
【问题描述】:
我有一个tableView,其中填充了fetchedResultsController。一切正常。就目前而言,tableView 根据需要从managedObjectContext 加载数据。问题是它的数据太多,所以我创建了一个带有协议的CLLocationManager 类,当它有一个位置时通知tableView,并且tableView 控制器在fetchedResultsController 上添加一个谓词以减少数量可能性。
我遇到的问题是在应用的初始加载上。似乎在 tableView 完成加载之前调用了 LocationManager 委托。
我在首次启动时不断收到此错误,但在后续启动时没有:
[error] 错误:严重的应用程序错误。在调用 -controllerDidChangeContent: 期间,从 NSFetchedResultsController 的委托中捕获了一个异常。无效更新:无效的节数。更新后表视图中包含的节数(1)必须等于更新前表视图中包含的节数(0),加上或减去插入或删除的节数(0插入,0已删除)。与 userInfo (null)
CoreData:错误:严重的应用程序错误。在调用 -controllerDidChangeContent: 期间,从 NSFetchedResultsController 的委托中捕获了一个异常。无效更新:无效的节数。更新后表视图中包含的节数(1)必须等于更新前表视图中包含的节数(0),加上或减去插入或删除的节数(0插入,0已删除)。与 userInfo (null)
import CoreLocation
protocol LocationManagerDelegate: class {
func updateTableView()
}
class LocationManager: NSObject {
static let sharedInstance = LocationManager()
public var currentLocation: CLLocation? {
get {
return locationManager.location
}
}
private var locationManager = CLLocationManager()
private override init() {
super.init()
locationManager.delegate = self
self.getCurrentLocation()
}
public weak var delegate: LocationManagerDelegate?
public func updateLocation() {
getCurrentLocation()
}
private func getCurrentLocation() {
// Also tried without the DispatchQueue
DispatchQueue.global(qos: .userInitiated).async {
// DispatchQueue.global(qos: .userInitiated).sync {
self.locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
}
}
}
extension LocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
delegate?.updateTableView()
// still crashes
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.delegate?.updateTableView()
}
}
}
我一直在 AppDelegate.swift 中启动 locationManager 以加快加载时间。
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
LocationManager.sharedInstance.updateLocation()
return true
}
认为这是罪魁祸首,我将其移至viewDidLoad 的末尾,但没有成功。该应用程序在初始启动后加载正常,locationManager 的委托,但我无法绕过初始启动时的错误。
这是我的NSFetchedResultsControllerDelegate 实现:
// MARK: - NSFetchedResultsControllerDelegate methods
extension MyViewController: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch (type) {
case .insert:
if let indexPath = newIndexPath {
tableView.insertRows(at: [indexPath], with: .automatic)
}
break;
case .delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .automatic)
}
break;
case .update:
if let indexPath = indexPath, let cell = tableView.cellForRow(at: indexPath) {
configureCell(cell, at: indexPath)
}
break;
case .move:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .automatic)
}
if let newIndexPath = newIndexPath {
tableView.insertRows(at: [newIndexPath], with: .automatic)
}
break;
}
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
print("controller didChange sectionInfo")
}
}
extension MyViewController: LocationManagerDelegate {
func updateTableView() {
filterBasedOnDistance(miles: Double(slider.value))
}
}
下面是updateTableView()调用的方法:
private func degreeRangeInMiles(degrees: Double, miles: Double) -> Range<Double> {
let metersPerDegree: Double = 111_000
let degreesInMeters = degrees * metersPerDegree
let metersPerMileDouble = 1_609.344
let milesDifferential = miles * metersPerMileDouble
let upperBound = (degreesInMeters + milesDifferential) / metersPerDegree
let lowerBound = (degreesInMeters - milesDifferential) / metersPerDegree
return Range(lowerBound..<upperBound)
}
func filterBasedOnDistance(miles: Double) {
guard locationManager.currentLocation != nil else { return }
let latitude = locationManager.currentLocation?.coordinate.latitude
let longitude = locationManager.currentLocation?.coordinate.longitude
let latitudeRange = degreeRangeInMiles(degrees: latitude!, miles: miles)
let longitudeRange = degreeRangeInMiles(degrees: longitude!, miles: miles)
let distancePredicate = NSPredicate(format: "latitude BETWEEN { \(latitudeRange.lowerBound),\(latitudeRange.upperBound) } AND longitude BETWEEN {\(longitudeRange.lowerBound),\(longitudeRange.upperBound)}")
fetchedResultsController.fetchRequest.predicate = distancePredicate
do {
try fetchedResultsController.performFetch()
} catch {
print(error.localizedDescription)
}
tableView.reloadData()
}
我正在试图找出我的错误在哪里以及如何解决它?感谢您的阅读!
更新
我尝试在我的 LocationManager 类上添加一个面向公众的 get-only 属性以确定设备的授权状态:
public var locationServicesAuthorized: Bool {
get {
return CLLocationManager.locationServicesEnabled()
}
}
我更新了我的委托方法调用如下:
extension MyViewController: LocationManagerDelegate {
func updateTableView() {
guard locationManager.locationServicesAuthorized else { return }
filterBasedOnDistance(miles: Double(slider.value))
}
}
同样的结果。即使我已经实现了beginUpdates 和endUpdates,我也得到了无效数量的部分的错误。 ??????????
【问题讨论】:
-
您的 didChange sectionInfo 方法需要添加/删除部分以响应 .insert 和 .delete 更改类型。
-
@pbasdf 我明白你在说什么。我不知道该放什么。
-
抱歉 - 我一直在寻找 Swift 中的样板代码,但只能找到 this,它是 Objective C。不过翻译起来并不难。本质上,您只需要检查更改类型并相应地调用 tableView insertSections 或 deleteSections 方法。
-
@pbasdf 如果你想复制我的答案,我会接受你的,所以你得到 25 分。
-
谢谢,但不用担心 - 你做了腿部工作,我只是指出了方向。
标签: ios uitableview core-data cllocationmanager