2017-02-17 11:17:10.227864 CuddlePix[4698:1635817] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3600.6.22/UITableView.m:1610
2017-02-17 11:17:10.230274 CuddlePix[4698:1635817] *** Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘Invalid update: invalid number of rows in section 1. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).’
*** First throw call stack:
(0x18b5591b8 0x189f9055c 0x18b55908c 0x18c01102c 0x1915f141c 0x19160a660 0x10006c624 0x100069998 0x100069a5c 0x10006997c 0x10006e6e4 0x100069998 0x10006e3bc 0x10006997c 0x1009ed258 0x1009ed218 0x1009f2280 0x18b506810 0x18b5043fc 0x18b4322b8 0x18cee6198 0x1914797fc 0x191474534 0x10007121c 0x18a4155b8)
libc++abi.dylib: terminating with uncaught exception of type NSException
Code:
import UIKit
import UserNotifications
class NotificationTableViewController: UITableViewController {
var tableSectionProviders = [NotificationTableSection : TableSectionProvider]()
@IBAction func handleRefresh(_ sender: UIRefreshControl) {
loadNotificationData {
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
sender.endRefreshing()
})
}
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(handleNotificationReceived), name: userNotificationReceivedNotificationName, object: .none)
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { (granted, error) in
if granted {
self.loadNotificationData()
} else {
print(error?.localizedDescription ?? "There was an error with requestAuthorization")
}
}
}
}
// MARK: - Table view data source
extension NotificationTableViewController {
override func numberOfSections(in tableView: UITableView) → Int {
return tableSectionProviders.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let notificationTableSection = NotificationTableSection(rawValue: section),
let sectionProvider = tableSectionProviders[notificationTableSection] else { return 0 }
return sectionProvider.numberOfCells
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "standardCell", for: indexPath)
guard let tableSection = NotificationTableSection(rawValue: indexPath.section),
let sectionProvider = tableSectionProviders[tableSection],
let cellProvider = sectionProvider.cellProvider(at: indexPath.row)
else { return cell }
cell = cellProvider.prepare(cell)
return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
guard let notificationTableSection = NotificationTableSection(rawValue: section),
let sectionProvider = tableSectionProviders[notificationTableSection]
else { return .none }
return sectionProvider.name
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return NotificationTableSection(rawValue: indexPath.section) == .some(.pending)
}
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return NotificationTableSection(rawValue: indexPath.section) == .some(.pending) ? .delete : .none
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
guard let section = NotificationTableSection(rawValue: indexPath.section), editingStyle == . delete && section == .pending else { return }
guard let provider = tableSectionProviders[.pending] as? PendingNotificationsTableSectionProvider else { return }
let request = provider.requests[indexPath.row]
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [request.identifier])
loadNotificationData {
self.tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
}
// MARK: - Table refresh handling
extension NotificationTableViewController {
func handleNotificationReceived(_ notification: Notification) {
loadNotificationData()
}
func loadNotificationData(callback: (() -> ())? = .none) {
let group = DispatchGroup()
let notificationCenter = UNUserNotificationCenter.current()
let dataSaveQueue = DispatchQueue(label: "com.raywenderlich.CuddlePix.dataSave")
group.enter()
notificationCenter.getNotificationSettings { (settings) in
let settingsProvider = SettingTableSectionProvider(settings: settings, name: "Notification Settings")
dataSaveQueue.async(execute: {
self.tableSectionProviders[.settings] = settingsProvider
group.leave()
})
}
group.enter()
notificationCenter.getPendingNotificationRequests { (requests) in
let pendingRequestsProvider = PendingNotificationsTableSectionProvider(requests: requests, name: "Pending Notificaitons")
dataSaveQueue.async(execute: {
self.tableSectionProviders[.pending] = pendingRequestsProvider
group.leave()
})
}
group.enter()
notificationCenter.getDeliveredNotifications { (notifications) in
let deliveredNotificationsProvider = DeliveredNotificationsTableSectionProvider(notifications: notifications, name: "Delivered Notifications")
dataSaveQueue.async(execute: {
self.tableSectionProviders[.delivered] = deliveredNotificationsProvider
group.leave()
})
}
group.notify(queue: DispatchQueue.main) {
if let callback = callback {
callback()
} else {
self.tableView.reloadData()
}
}
}
}
// MARK: - ConfigurationViewControllerDelegate
extension NotificationTableViewController: ConfigurationViewControllerDelegate {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destVC = segue.destination as? ConfigurationViewController {
destVC.delegate = self
}
}
func configurationCompleted(newNotifications new: Bool) {
if new {
loadNotificationData()
}
_ = navigationController?.popViewController(animated: true)
}
}