-
Notifications
You must be signed in to change notification settings - Fork 485
Closed
Description
I have a leaderboard in my app and the data is stored in Firebase Firestore. The leaderboard dynamically changes based on events related to the users.
Every so often, the leaderboard is crashing and I am receiving an NSInternalInconsistencyException
. I am unsure why, however it may have to do with when the data in Firebase dynamically changes and the UITableView
repopulates the data. Below is the associated code:
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)
}
}
Output:
LeaderboardViewController.Swift (where error is occurring):
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)
}
}
Output:
Exception:
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)
Metadata
Metadata
Assignees
Labels
No labels