2017-10-23 1 views
1

fetchedResultsControllerが入力されたtableViewがあります。すべてうまく動作します。それはそのままで、tableViewmanagedObjectContextのデータを必要に応じてロードします。問題はそれがあまりにも多くのデータですので、私はtableViewに位置を持っていることを通知するプロトコルを持つCLLocationManagerクラスを作成しました。そして、tableViewコントローラは、可能性の数を減らすためにfetchedResultsControllerに述語を追加します。Location ManagerがTableViewの問題を更新するための代理コール

私が遭遇した問題は、初期ロードのアプリです。 tableViewの読み込みが完了する前に、LocationManager代理人が呼び出されているようです。

私はない回目以降の起動に初期起動時にこのエラーを得続ける、しかし:私はロード時間を短縮するための努力にAppDelegate.swiftlocationManagerを起動していた

[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() 
    } 
    } 
} 

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クラスのパブリック向きの取得専用のプロパティを追加してみました:

public var locationServicesAuthorized: Bool { 
    get { 
     return CLLocationManager.locationServicesEnabled() 
    } 
    } 

そして、次のように私は私のデリゲートメソッド呼び出しを更新:

extension MyViewController: LocationManagerDelegate { 
    func updateTableView() { 
    guard locationManager.locationServicesAuthorized else { return } 
    filterBasedOnDistance(miles: Double(slider.value)) 
    } 
} 

同じ結果です。 beginUpdatesendUpdatesが実装されているにもかかわらず、無効なセクション数があるというエラーが表示されます。

+0

あなたdidChange sectionInfo方法は.insertに応答して.delete変更タイプにセクションを追加/削除する必要があります。 – pbasdf

+0

@pbasdfあなたが話していることが分かります。私はそこに何を入れるべきか分からない。 – Adrian

+0

申し訳ありません - 私はSwiftで定型文を探していましたが、[this](https://developer.apple.com)しか見つけることができません。com/documentation/coredata/nsfetchedresultscontrollerdelegate)。これはObjective Cです。しかし、あまりにも翻訳が難しくありません。基本的にあなただけの変更の種類を確認し、それに応じてのtableViewのinsertSectionsまたはdeleteSectionsメソッドを呼び出す必要があります。 – pbasdf

答えて

1

pbasdfのの提案では、didChange sectionInfo NSFetchedResultsControllerデリゲートメソッドを実装しました。 Apple's boilerplate on their siteは、Objective-Cであるが、ここでは翻訳スウィフトバージョンです:

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 
    } 
    } 
関連する問題