位置管理器委托调用更新 TableView 问题
Location Manager delegate call to update TableView issue
我有一个 tableView
,其中填充了一个 fetchedResultsController
。一切正常。就目前而言,tableView
根据需要从 managedObjectContext
加载数据。问题是数据太多,所以我创建了一个 CLLocationManager
class 协议,当它有一个位置时通知 tableView
并且 tableView 控制器在 fetchedResultsController
减少可能性的数量。
我遇到的问题是在应用程序的初始加载 上。似乎 LocationManager
委托在 tableView
完成加载之前被调用。
我在首次启动时一直收到此错误,但在后续启动时没有出现:
[error] error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (0), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted). with userInfo (null)
CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (0), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted). with 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
class 上添加一个 public-facing 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
。
根据 pbasdf 的 建议,我实现了 didChange sectionInfo
NSFetchedResultsController 委托方法。 Apple's boilerplate on their site 在 Objective-C 中,但这里是翻译后的 Swift 版本:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
switch type {
case .insert:
tableView.insertSections([sectionIndex], with: .fade)
break
case .delete:
tableView.deleteSections([sectionIndex], with: .fade)
default:
break
}
}
我有一个 tableView
,其中填充了一个 fetchedResultsController
。一切正常。就目前而言,tableView
根据需要从 managedObjectContext
加载数据。问题是数据太多,所以我创建了一个 CLLocationManager
class 协议,当它有一个位置时通知 tableView
并且 tableView 控制器在 fetchedResultsController
减少可能性的数量。
我遇到的问题是在应用程序的初始加载 上。似乎 LocationManager
委托在 tableView
完成加载之前被调用。
我在首次启动时一直收到此错误,但在后续启动时没有出现:
[error] error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (0), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted). with userInfo (null)
CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (0), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted). with 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
class 上添加一个 public-facing 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
。
根据 pbasdf 的 建议,我实现了 didChange sectionInfo
NSFetchedResultsController 委托方法。 Apple's boilerplate on their site 在 Objective-C 中,但这里是翻译后的 Swift 版本:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
switch type {
case .insert:
tableView.insertSections([sectionIndex], with: .fade)
break
case .delete:
tableView.deleteSections([sectionIndex], with: .fade)
default:
break
}
}