up 0 down

У меня есть лидеры в моем приложении и данные хранятся в Firebase Firestore. Лидеры динамически изменяется в зависимости от событий, связанных с пользователями.

Каждый так часто, в таблице лидеров происходит сбой, и я получаю NSInternalInconsistencyException. Я не уверен, почему, но это, возможно, придется делать с тем, когда данные в Firebase динамически изменяется и UITableView повторно заполняет данные. Ниже ассоциированный код:

LeadersViewController.Swift

    class LeadersViewController: UIViewController {

    private let ROWS_TO_SHOW = 3


    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var cashierButton: UIButton!
    @IBOutlet weak var allTimeTableCard: TableCard!
    @IBOutlet weak var thisMonthTableCard: TableCard!
    @IBOutlet weak var lastMonthTableCard: TableCard!

    fileprivate var allTimeDataSource: FUIFirestoreTableViewDataSource!
    fileprivate var thisMonthDataSource: FUIFirestoreTableViewDataSource!
    fileprivate var lastMonthDataSource: FUIFirestoreTableViewDataSource!

    var userListener: ListenerRegistration!
    var allTimeListener: ListenerRegistration!
    var thisMonthListener: ListenerRegistration!
    var lastMonthListener: ListenerRegistration!


    override func viewDidLoad() {
        super.viewDidLoad()

    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.userListener = FirestoreUtil.loadUser { (object) in
            guard let user: User = object else {
                return
            }
            self.nameLabel.text = user.displayName ?? ""
            let cash = OddsUtil.formatCash(cashAmount: user.balanceNumeric)
            self.cashierButton.setTitle(cash, for: .normal)
        }
        setupAllTimeStat()
        setupThisMonthStat()
        setupLastMonthStat()
    }

    override func viewWillDisappear(_ animated: Bool) {
        userListener.remove()

        allTimeDataSource.unbind()
        allTimeDataSource.tableView = nil
        allTimeDataSource = nil
        allTimeTableCard.tableView.dataSource = nil
        allTimeTableCard.tableView.reloadData()

        thisMonthDataSource.unbind()
        thisMonthDataSource.tableView = nil
        thisMonthDataSource = nil
        thisMonthTableCard.tableView.dataSource = nil
        thisMonthTableCard.tableView.reloadData()

        lastMonthDataSource.unbind()
        lastMonthDataSource.tableView = nil
        lastMonthDataSource = nil
        lastMonthTableCard.tableView.dataSource = nil
        lastMonthTableCard.tableView.reloadData()

        allTimeListener.remove()
        thisMonthListener.remove()
        lastMonthListener.remove()

        super.viewWillDisappear(animated)

    }

    func setupAllTimeStat() {
        allTimeTableCard.headerImageView.image = UIImage(named: "ic_format_list_numbered_black_18dp")
        allTimeTableCard.headerLabel.text = "Profit: All-Time"
        allTimeTableCard.bottomView.isHidden = false
        allTimeTableCard.bottomLabel.text = "VIEW LEADERBOARD"

        self.allTimeTableCard.tableView.register(UINib(nibName: "LeaderTableViewCell", bundle: nil),
                                                 forCellReuseIdentifier: "LeaderTableViewCell")

        self.allTimeTableCard.tableView.delegate = self

        self.allTimeTableCard.tableView.tableFooterView = UIView(frame: CGRect.zero)

        let query = Firestore.firestore()
            .collection("userStats")
            .whereField("timePeriodString", isEqualTo: "ALLTIME")
            .whereField("statType", isEqualTo: StatType.AMOUNT_NETTED.rawValue)
            .whereField("valueAsDouble", isGreaterThan: 0)
            .order(by:"valueAsDouble", descending: true)
            .limit(to: ROWS_TO_SHOW)

        self.allTimeListener = query.addSnapshotListener { (snapshot, error) in
            DispatchQueue.main.async {
                self.allTimeTableCard.tableView.reloadData()
            }

        }

        self.allTimeDataSource = allTimeTableCard.tableView.bind(toFirestoreQuery: query, populateCell: { (tableView, indexPath, snapshot) -> UITableViewCell in
            let cell = tableView.dequeueReusableCell(withIdentifier: "LeaderTableViewCell", for: indexPath) as! LeaderTableViewCell
            let stat = UserStat(dict: snapshot.data() ?? [:])
            cell.setStat(position: indexPath.row, userStat: stat)
            return cell
        })

        self.allTimeTableCard.viewAll = {
            LeaderboardViewController.openLeaderboard(sender: self, leaderboardTitle: "Profit: All-Time", period: "ALLTIME")
        }

        self.allTimeTableCard.tableView.reloadData()
    }

    func setupThisMonthStat() {
        thisMonthTableCard.headerImageView.image = UIImage(named: "ic_format_list_numbered_black_18dp")
        thisMonthTableCard.headerLabel.text = "Profit: \(DateUtil.formatMonthYear())"
        thisMonthTableCard.bottomView.isHidden = false
        thisMonthTableCard.bottomLabel.text = "VIEW LEADERBOARD"

        self.thisMonthTableCard.tableView.register(UINib(nibName: "LeaderTableViewCell", bundle: nil),
                                                   forCellReuseIdentifier: "LeaderTableViewCell")

        self.thisMonthTableCard.tableView.delegate = self

        self.thisMonthTableCard.tableView.tableFooterView = UIView(frame: CGRect.zero)

        let period = "\(DateUtil.year())\(DateUtil.month() - 1)"

        let query = Firestore.firestore()
            .collection("userStats")
            .whereField("timePeriodString", isEqualTo: period)
            .whereField("statType", isEqualTo: StatType.AMOUNT_NETTED.rawValue)
            .whereField("valueAsDouble", isGreaterThan: 0)
            .order(by:"valueAsDouble", descending: true)
            .limit(to: ROWS_TO_SHOW)

        self.thisMonthListener = query.addSnapshotListener { (snapshot, error) in
            DispatchQueue.main.async {
                self.thisMonthTableCard.tableView.reloadData()
            }
        }

        self.thisMonthDataSource = thisMonthTableCard.tableView.bind(toFirestoreQuery: query, populateCell: { (tableView, indexPath, snapshot) -> UITableViewCell in
            let cell = tableView.dequeueReusableCell(withIdentifier: "LeaderTableViewCell", for: indexPath) as! LeaderTableViewCell
            let stat = UserStat(dict: snapshot.data() ?? [:])
            cell.setStat(position: indexPath.row, userStat: stat)
            return cell
        })

        self.thisMonthTableCard.viewAll = {
            LeaderboardViewController.openLeaderboard(sender: self, leaderboardTitle: "Profit: \(DateUtil.formatMonthYear())", period: period)
        }

        self.thisMonthTableCard.tableView.reloadData()

    }

    func setupLastMonthStat() {
        let previousMonth = Calendar.current.date(byAdding: .month, value: -1, to: Date()) ?? Date()
        lastMonthTableCard.headerImageView.image = UIImage(named: "ic_format_list_numbered_black_18dp")
        lastMonthTableCard.headerLabel.text = "Profit: \(DateUtil.formatMonthYear(date: previousMonth))"
        lastMonthTableCard.bottomView.isHidden = false
        lastMonthTableCard.bottomLabel.text = "VIEW LEADERBOARD"

        self.lastMonthTableCard.tableView.register(UINib(nibName: "LeaderTableViewCell", bundle: nil),
                                                   forCellReuseIdentifier: "LeaderTableViewCell")
        self.lastMonthTableCard.tableView.delegate = self


        self.lastMonthTableCard.tableView.tableFooterView = UIView(frame: CGRect.zero)

        let period = "\(DateUtil.year(date: previousMonth))\(DateUtil.month(date: previousMonth) - 1)"

        let query = Firestore.firestore()
            .collection("userStats")
            .whereField("timePeriodString", isEqualTo: period)
            .whereField("statType", isEqualTo: StatType.AMOUNT_NETTED.rawValue)
            .whereField("valueAsDouble", isGreaterThan: 0)
            .order(by:"valueAsDouble", descending: true)
            .limit(to: ROWS_TO_SHOW)

        self.lastMonthListener = query.addSnapshotListener { (snapshot, error) in
            DispatchQueue.main.async {
                self.lastMonthTableCard.tableView.reloadData()
            }
        }

        self.lastMonthDataSource = lastMonthTableCard.tableView.bind(toFirestoreQuery: query, populateCell: { (tableView, indexPath, snapshot) -> UITableViewCell in
            let cell = tableView.dequeueReusableCell(withIdentifier: "LeaderTableViewCell", for: indexPath) as! LeaderTableViewCell
            let stat = UserStat(dict: snapshot.data() ?? [:])
            cell.setStat(position: indexPath.row, userStat: stat)
            return cell
        })

        self.lastMonthTableCard.viewAll = {
            LeaderboardViewController.openLeaderboard(sender: self, leaderboardTitle: "Profit: \(DateUtil.formatMonthYear(date: previousMonth))", period: period)
        }

        self.lastMonthTableCard.tableView.reloadData()

    }

    @IBAction func cashier(_ sender: Any) {
        CashierViewController.openCashier(sender: self)
    }
   }

    extension LeadersViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: 
   IndexPath) {
        let userId = (tableView.cellForRow(at: 
    tableView.indexPathForSelectedRow!) as! 
    LeaderTableViewCell).userStat?.userId ?? ""

        ProfileViewController.openPorfile(vc: self, userId: userId)
    }
}

Выход:

NSInternalInconsistencyException когда Наполнение UITableView

LeaderboardViewController.Swift (где ошибка происходит):

 import UIKit
import FirebaseUI

class LeaderboardViewController: UIViewController {

    private let ROWS_TO_SHOW = 100

    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var indicator: UIActivityIndicatorView!
    fileprivate var dataSource: FUIFirestoreTableViewDataSource!

    var listener: ListenerRegistration!

    var leaderboardTitle: String!
    var period: String!

    override func viewDidLoad() {
        super.viewDidLoad()

        title = leaderboardTitle

        self.tableView.register(UINib(nibName: "LeaderTableViewCell", bundle: nil),
                                forCellReuseIdentifier: "LeaderTableViewCell")

        self.tableView.tableFooterView = UIView(frame: CGRect.zero)

        self.tableView.delegate = self
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        let query = Firestore.firestore()
            .collection("userStats")
            .whereField("timePeriodString", isEqualTo: period)
            .whereField("statType", isEqualTo: StatType.AMOUNT_NETTED.rawValue)
            .order(by:"valueAsDouble", descending: true)
            .limit(to: ROWS_TO_SHOW)

        self.listener = query.addSnapshotListener { (snapshot, error) in
            DispatchQueue.main.async {
                if snapshot?.count ?? 0 > 3 {
                    self.indicator.stopAnimating()
                }
                self.tableView.reloadData()
            }
        }

        self.dataSource = tableView.bind(toFirestoreQuery: query, populateCell: { (tableView, indexPath, snapshot) -> UITableViewCell in
            let cell = tableView.dequeueReusableCell(withIdentifier: "LeaderTableViewCell", for: indexPath) as! LeaderTableViewCell
            let stat = UserStat(dict: snapshot.data() ?? [:])
            cell.setStat(position: indexPath.row, userStat: stat)
            return cell
        })
        self.tableView.reloadData()
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        dataSource.unbind()
        dataSource.tableView = nil
        dataSource = nil
        tableView.dataSource = nil
        tableView.reloadData()
        listener.remove()
    }

}

extension LeaderboardViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let userId = (tableView.cellForRow(at: tableView.indexPathForSelectedRow!) as! LeaderTableViewCell).userStat?.userId ?? ""

        ProfileViewController.openPorfile(vc: self, userId: userId)
    }
}

extension LeaderboardViewController {
    public static func openLeaderboard(sender: UIViewController, leaderboardTitle: String, period: String) {
        let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
        let vc = storyboard.instantiateViewController(withIdentifier: "LeaderboardViewController") as! LeaderboardViewController
        vc.leaderboardTitle = leaderboardTitle
        vc.period = period
        sender.navigationController?.pushViewController(vc, animated: true)
    }

}

Выход:

NSInternalInconsistencyException когда Наполнение UITableView

Исключение:

2019-02-06 21:15:49.293694-0600 BetShark[18200:155059] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKitCore_Sim/UIKit-3698.93.8/UITableView.m:1776
2019-02-06 21:15:49.316429-0600 BetShark[18200:155059] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to perform an insert and a move to the same index path (<NSIndexPath: 0xc55d03bd8e95ff5b> {length = 2, path = 0 - 68})'
*** First throw call stack:
(
    0   CoreFoundation                      0x00000001116921bb __exceptionPreprocess + 331
    1   libobjc.A.dylib                     0x0000000110c30735 objc_exception_throw + 48
    2   CoreFoundation                      0x0000000111691f42 +[NSException raise:format:arguments:] + 98
    3   Foundation                          0x000000010c8e1877 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 194
    4   UIKitCore                           0x000000011a47688a -[UITableView _endCellAnimationsWithContext:] + 9355
    5   UIKitCore                           0x000000011a492711 -[UITableView endUpdates] + 75
    6   BetShark                            0x0000000108a0095c -[FUIFirestoreTableViewDataSource batchedArray:didUpdateWithDiff:] + 2321
    7   BetShark                            0x00000001089f44f8 __31-[FUIBatchedArray observeQuery]_block_invoke + 658
    8   BetShark                            0x00000001088e7fdc __60-[FIRQuery addSnapshotListenerInternalWithOptions:listener:]_block_invoke + 197
    9   BetShark                            0x00000001088d5e68 _ZZN8firebase9firestore4util8internal13DispatchAsyncEPU28objcproto17OS_dispatch_queue8NSObjectONSt3__18functionIFvvEEEEN3$_08__invokeEPv + 14
    10  libdispatch.dylib                   0x0000000112885602 _dispatch_client_callout + 8
    11  libdispatch.dylib                   0x000000011289299a _dispatch_main_queue_callback_4CF + 1541
    12  CoreFoundation                      0x00000001115f73e9 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
    13  CoreFoundation                      0x00000001115f1a76 __CFRunLoopRun + 2342
    14  CoreFoundation                      0x00000001115f0e11 CFRunLoopRunSpecific + 625
    15  GraphicsServices                    0x0000000113da71dd GSEventRunModal + 62
    16  UIKitCore                           0x000000011a27081d UIApplicationMain + 140
    17  BetShark                            0x0000000108722052 main + 50
    18  libdyld.dylib                       0x00000001128fb575 start + 1
    19  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)