位置管理器委托调用更新 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))
  }
}

同样的结果。我得到的错误是部分数量无效,即使我已经实现了 beginUpdatesendUpdates

根据 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
    }
  }