jrtibbetts/Stylobate

View on GitHub
Source/Core Data/FetchedResultsTableModel.swift

Summary

Maintainability
A
0 mins
Test Coverage
//  Copyright © 2017 Poikile Creations. All rights reserved.

#if os(iOS) || os(tvOS)

import CoreData
import UIKit

/// A `FetchedResultsModel` for table views.
open class FetchedResultsTableModel: FetchedResultsModel,
                                     UITableViewDataSource,
                                     UITableViewDelegate,
                                     NSFetchedResultsControllerDelegate {

    // MARK: - Properties

    /// `true` if the section index should be shown on the right edge of the
    /// table. This can be overridden to return dynamic values depending on the
    /// sorting criteria, etc.
    public var showSectionIndex: Bool = true

    /// The table view for which this model is a delegate and data source.
    let tableView: UITableView

    // MARK: - Initialization

    /// Construct a model for a specific table view.
    ///
    /// - parameter tableView: The `UITableView`. The model sets itself as the
    /// table view's delegate and data source.
    /// - parameter context: The managed object context.
    /// - parameter fetchedResultsController: The fetched results controller
    /// that will populate this model with data.
    public init(_ tableView: UITableView,
                context: NSManagedObjectContext,
                fetchedResultsController: NSFetchedResultsController<NSManagedObject>) {
        self.tableView = tableView
        super.init(context: context, fetchedResultsController: fetchedResultsController)
        self.tableView.delegate = self
        self.tableView.dataSource = self
        self.fetchedResultsController.delegate = self
    }

    // MARK: - UITableViewDataSource & UITableViewDelegate

    open func sectionIndexTitles(for tableView: UITableView) -> [String]? {
        if showSectionIndex {
            return fetchedResultsController.sectionIndexTitles
        } else {
            return nil
        }
    }

    /// Get the number of sections from the fetched results controller.
    ///
    /// - parameter tableView: The table view.
    ///
    /// - returns: The number of sections.
    open func numberOfSections(in tableView: UITableView) -> Int {
        return numberOfSections()
    }

    /// Get the number of sections from the fetched results controller.
    ///
    /// - parameter tableView: The table view.
    ///
    /// - returns: The number of rows in the specified section.
    open func tableView(_ tableView: UITableView,
                        numberOfRowsInSection section: Int) -> Int {
        return numberOfItems(in: section)
    }

    // This default implementation barfs up an assertion failure. You must
    // override it and *not* call `super.tableView(_, cellForRowAt:)`.
    open func tableView(_ tableView: UITableView,
                        cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        return UITableViewCell(style: .default, reuseIdentifier: "tableCell")
    }

    open func tableView(_ tableView: UITableView,
                        canEditRowAt indexPath: IndexPath) -> Bool {
        // Return false if you do not want the specified item to be editable.
        return false
    }

    open func tableView(_ tableView: UITableView,
                        commit editingStyle: UITableViewCell.EditingStyle,
                        forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            deleteElement(at: indexPath)

            do {
                try saveContext()
                try fetchedResultsController.performFetch()
            } catch {
                // How can we handle the error?
            }
        }
    }

    open func tableView(_ tableView: UITableView,
                        sectionForSectionIndexTitle title: String,
                        at index: Int) -> Int {
        return fetchedResultsController.section(forSectionIndexTitle: title, at: index)
    }

    // MARK: - Fetched results controller

    /// Tell the table view that updates are about to begin.
    open func controllerWillChangeContent(_ controller: RequestResultFRC) {
        self.tableView.beginUpdates()
    }

    open func controller(_ controller: RequestResultFRC,
                         didChange sectionInfo: NSFetchedResultsSectionInfo,
                         atSectionIndex sectionIndex: Int,
                         for type: NSFetchedResultsChangeType) {
        let sectionIndices = IndexSet(integer: sectionIndex)

        switch type {
        case .insert:
            tableView.insertSections(sectionIndices, with: .fade)
        case .delete:
            tableView.deleteSections(sectionIndices, with: .fade)
        default:
            return
        }
    }

    open func controller(_ controller: RequestResultFRC,
                         didChange anObject: Any,
                         at indexPath: IndexPath?,
                         for type: NSFetchedResultsChangeType,
                         newIndexPath: IndexPath?) {
        switch type {
        case .insert:
            tableView.insertRows(at: [newIndexPath!], with: .fade)
        case .delete:
            tableView.deleteRows(at: [indexPath!], with: .fade)
        case .update:
            tableView.reloadRows(at: [indexPath!], with: .automatic)
        case .move:
            tableView.moveRow(at: indexPath!, to: newIndexPath!)
        @unknown default:
            break
        }
    }

    open func controllerDidChangeContent(_ controller: RequestResultFRC) {
        self.tableView.endUpdates()
    }

    // Implementing the above methods to update the table view in response to
    // individual changes may have performance implications if a large number of
    // changes are made simultaneously. If this proves to be an issue, you can
    // instead just implement controllerDidChangeContent: which notifies the
    // delegate that all section and object changes have been processed.

    open func controllerDidChangeContent(controller: RequestResultFRC) {
        // In the simplest, most efficient, case, reload the table view.
        self.tableView.reloadData()
    }

}

#endif