@@ -154,7 +84,7 @@

DTTableViewManager

  • Advanced Usage @@ -168,7 +98,6 @@

    DTTableViewManager

  • Anomaly handler
  • Unregistering mappings
  • -
  • ObjectiveC support
  • Thanks
  • Features

    @@ -176,38 +105,38 @@

    Features

    • [x] Powerful mapping system between data models and cells, headers and footers
    • [x] Support for all Swift types - classes, structs, enums, tuples, protocols
    • +
    • [x] Support for diffable datasources in iOS 13
    • [x] Powerful events system, that covers all UITableView delegate and datasource methods
    • [x] Views created from code, XIB, or storyboard
    • [x] Flexible Memory/CoreData/Realm.io storage options
    • [x] Automatic datasource and interface synchronization.
    • [x] Automatic XIB registration and dequeue
    • -
    • [x] No type casts required
    • -
    • [x] No need to subclass
    • -
    • [x] Support for Drag&Drop in iOS 11
    • +
    • [x] Support for Drag&Drop API for iOS 11 and higher
    • [x] Can be used with UITableViewController, or UIViewController with UITableView, or any other class, that contains UITableView
    • [x] Complete documentation

    Requirements

      -
    • Xcode 8 and higher
    • +
    • Xcode 9 and higher
    • iOS 8.0 and higher / tvOS 9.0 and higher
    • -
    • Swift 3 and higher
    • +
    • Swift 4 and higher

    Installation

    +

    Swift Package Manager(requires Xcode 11)

    -

    CocoaPods:

    -
    pod 'DTTableViewManager', '~> 6.5'
    +

    Add package into Project settings -> Swift Packages

    +

    CocoaPods:

    +
    pod 'DTTableViewManager'
     
    - -

    Carthage:

    -
    github "DenTelezhkin/DTTableViewManager" ~> 6.5
    +

    Carthage:

    +
    github "DenTelezhkin/DTTableViewManager"
     
    -

    After running carthage update drop DTTableViewManager.framework and DTModelStorage.framework to Xcode project embedded binaries.

    +

    After running carthage update add DTTableViewManager.framework and DTModelStorage.framework to Xcode project embedded binaries.

    Quick start

    -

    DTTableViewManager framework has two parts - core framework, and storage classes. Import them both to your view controller class to start:

    +

    DTTableViewManager framework has two parts - core framework, and storage classes. Import them both to your view controller class to start:

    import DTTableViewManager
     import DTModelStorage
     
    @@ -225,10 +154,18 @@

    Quick start

      -
    • Declare your class as DTTableViewManageable, and it will be automatically injected with manager property, that will hold an instance of DTTableViewManager.

    • -
    • Make sure your UITableView outlet is wired to your class and call registration methods (typically in viewDidLoad method):

    • +
    • Declare your class as DTTableViewManageable, and it will be automatically injected with manager property, that will hold an instance of DTTableViewManager.

    • +
    • Make sure your UITableView outlet is wired to your class and call registration methods:

    -
        manager.register(PostCell.self)
    +
    class PostsViewController: UIViewController, DTTableViewManageable {
    +
    +    @IBOutlet weak var tableView: UITableView!
    +
    +    override func viewDidLoad() {
    +        super.viewDidLoad()
    +        manager.register(PostCell.self)
    +    }
    +}   
     

    ModelType will be automatically gathered from your PostCell. If you have a PostCell.xib file, it will be automatically registered for PostCell. If you have a storyboard with PostCell, set it’s reuseIdentifier to be identical to class - PostCell.

    @@ -254,12 +191,12 @@

    Mapping and registration

  • registerNiblessFooter(_:)
  • -

    By default, DTTableViewManager uses section titles and tableView(_:titleForHeaderInSection:) UITableViewDatasource methods. However, if you call any mapping methods for headers or footers, it will automatically switch to using tableView(_:viewForHeaderInSection:) methods and dequeue UITableViewHeaderFooterView instances. Make your UITableViewHeaderFooterView subclasses conform to ModelTransfer protocol to allow them participate in mapping.

    +

    By default, DTTableViewManager uses section titles and tableView(_:titleForHeaderInSection:) UITableViewDatasource methods. However, if you call any mapping methods for headers or footers, it will automatically switch to using tableView(_:viewForHeaderInSection:) methods and dequeue UITableViewHeaderFooterView instances. Make your UITableViewHeaderFooterView subclasses conform to ModelTransfer protocol to allow them participate in mapping.

    You can also use UIView subclasses for headers and footers.

    Data models

    -

    DTTableViewManager supports all Swift and Objective-C types as data models. This also includes protocols and subclasses.

    +

    DTTableViewManager supports all Swift and Objective-C types as data models. This also includes protocols and subclasses.

    protocol Food {}
     class Apple : Food {}
     class Carrot: Food {}
    @@ -274,13 +211,13 @@ 

    Data models

    Storage classes

    -

    DTModelStorage is a framework, that provides storage classes for DTTableViewManager. By default, storage property on DTTableViewManager holds a MemoryStorage instance.

    +

    DTModelStorage is a framework, that provides storage classes for DTTableViewManager. By default, storage property on DTTableViewManager holds a MemoryStorage instance.

    MemoryStorage

    -

    MemoryStorage is a class, that manages UITableView models in memory. It has methods for adding, removing, replacing, reordering table view models etc. You can read all about them in DTModelStorage repo. Basically, every section in MemoryStorage is an array of SectionModel objects, which itself is an object, that contains optional header and footer models, and array of table items.

    +

    MemoryStorage is a class, that manages UITableView models in memory. It has methods for adding, removing, replacing, reordering table view models etc. You can read all about them in DTModelStorage repo. Basically, every section in MemoryStorage is an array of SectionModel objects, which itself is an object that contains array of table items.

    CoreDataStorage

    -

    CoreDataStorage is meant to be used with NSFetchedResultsController. It automatically monitors all NSFetchedResultsControllerDelegate methods and updates UI accordingly to it’s changes. All you need to do to display CoreData models in your UITableView, is create CoreDataStorage object and set it on your storage property of DTTableViewManager.

    +

    CoreDataStorage is meant to be used with NSFetchedResultsController. It automatically monitors all NSFetchedResultsControllerDelegate methods and updates UI accordingly to it’s changes. All you need to do to display CoreData models in your UITableView, is create CoreDataStorage object and set it on your storage property of DTTableViewManager.

    It also recommended to use built-in CoreData updater to properly update UITableView:

    manager.tableViewUpdater = manager.coreDataUpdater()
    @@ -300,9 +237,45 @@ 

    CoreDataStorage

    Keep in mind, that MemoryStorage is not limited to objects in memory. For example, if you have CoreData database, and you now for sure, that number of items is not big, you can choose not to use CoreDataStorage and NSFetchedResultsController. You can fetch all required models, and store them in MemoryStorage.

    RealmStorage

    -

    RealmStorage is a class, that is meant to be used with realm.io databases. To use RealmStorage with DTTableViewManager, add following line to your Podfile:

    +

    RealmStorage is a class, that is meant to be used with realm.io databases. To use RealmStorage with DTTableViewManager, add following line to your Podfile:

        pod 'DTModelStorage/Realm'
     
    +

    Diffable datasources in iOS 13

    + +

    Diffable datasources is a cool new feature, that is introduced in UIKit in iOS / tvOS 13. DTTableViewManager 7 provides a powerful integration layer with it, but in order to understand how this layer works, it’s highly recommended to check out great Advances in UI Data Sources WWDC session.

    + +

    If you don’t use DTTableViewManager, you would typically create diffable datasource like so (taken from Apple’s sample code on diffable datasources):

    +
    dataSource = UICollectionViewDiffableDataSource
    +    <Section, MountainsController.Mountain>(collectionView: mountainsCollectionView) {
    +        (collectionView: UICollectionView, indexPath: IndexPath,
    +        mountain: MountainsController.Mountain) -> UICollectionViewCell? in
    +    guard let mountainCell = collectionView.dequeueReusableCell(
    +        withReuseIdentifier: LabelCell.reuseIdentifier, for: indexPath) as? LabelCell else {
    +            fatalError("Cannot create new cell") }
    +    mountainCell.label.text = mountain.name
    +    return mountainCell
    +}
    +
    + +

    One of DTTableViewManagers main goals is to get rid of String identifiers, and to handle cell creation, as well as updating cell with it’s model, for you. Which is why with DTTableViewManager 7 code, equivalent to one above, is the following:

    +
    dataSource = manager.configureDiffableDataSource { indexPath, model in
    +    return model
    +}
    +
    + +

    You should persist strong reference to dataSource object, and use it for constructing sections and items exactly as described in Apple documentation and WWDC session.

    + +

    Diffable datasources and DTTableViewManager 7 are tightly integrated, so all events, even datasource ones like manager.configure(_:), continue to work in the same way as they were working before.

    + +

    On top of that, there is an additional functionality, that currently UITableViewDiffableDataSource class does not provide. It’s currently not possible to use it and have section titles/headers/footers in UITableView. With DTTableViewManager however, it works just as you would expect:

    +
    manager.supplementaryStorage?.setSectionHeaderModels(["Foo"])
    +
    + +

    Both events and section header/footer integration is possible, because DTTableViewManager injects a special ProxyDiffableDataSourceStorage object between UITableViewDiffableDataSource and UITableView. This storage does not store data models and just queries diffable data source to receive them. It does, however, implement section header and footer model providers, which unlocks possibility to have section titles/headers/footers when using diffable datasources.

    + +

    DTTableViewManager supports both generic UITableViewDiffableDataSource<SectionType,ItemType> and non-generic UITableViewDiffableDataSourceReference with the same method name(configureDiffableDataSource). Resulting diffable datasource type is inferred from your declaration of the datasource.

    + +

    Keep in mind, that for diffable datasources, tableViewUpdater property will contain nil, since UI updates are handled by diffable datasource itself.

    Reacting to events

    Event system in DTTableViewManager 5 allows you to react to UITableViewDelegate and UITableViewDataSource events based on view and model types, completely bypassing any switches or ifs when working with UITableView API. For example:

    @@ -313,7 +286,7 @@

    Reacting to events

    Important

    -

    All events with closures are stored on DTTableViewManager instance, so be sure to declare [weak self] in capture lists to prevent retain cycles.

    +

    While it’s possible to register multiple closures for a single event, only first closure will be called once event is fired. This means that if the same event has two closures for the same view/model type, last one will be ignored. You can still register multiple event handlers for a single event and different view/model types. You can see how reactions are being searched for in DTModelStorage EventReaction extension.

    Event types

    There are two types of events:

    @@ -337,17 +310,17 @@

    Event types

    It’s also important to understand, that event system is implemented using responds(to:) method override and is working on the following rules:

      -
    • If DTTableViewManageable is implementing delegate method, responds(to:) returns true
    • -
    • If DTTableViewManager has events tied to selector being called, responds(to:) also returns true
    • +
    • If DTTableViewManageable is implementing delegate method, responds(to:) returns true
    • +
    • If DTTableViewManager has events tied to selector being called, responds(to:) also returns true
    -

    What this approach allows us to do, is configuring UITableView knowledge about what delegate method is implemented and what is not. For example, DTTableViewManager is implementing tableView(_:heightForRowAt:) method, however if you don’t call heightForCell(withItem:_:) method, you are safe to use self-sizing cells in UITableView. While all delegate methods are implemented, only those that have events or are implemented by delegate will be called by UITableView.

    +

    What this approach allows us to do, is configuring UITableView knowledge about what delegate method is implemented and what is not. For example, DTTableViewManager is implementing tableView(_:heightForRowAt:) method, however if you don’t call heightForCell(withItem:_:) method, you are safe to use self-sizing cells in UITableView. While all delegate methods are implemented, only those that have events or are implemented by delegate will be called by UITableView.

    -

    DTTableViewManager has the same approach for handling each delegate and datasource method:

    +

    DTTableViewManager has the same approach for handling each delegate and datasource method:

    • Try to execute event, if cell and model type satisfy requirements
    • -
    • Try to call delegate or datasource method on DTTableViewManageable instance
    • +
    • Try to call delegate or datasource method on DTTableViewManageable instance
    • If two previous scenarios fail, fallback to whatever default UITableView has for this delegate or datasource method

    Events configuration

    @@ -363,10 +336,10 @@

    Events configuration

    Advanced usage

    Drag and Drop in iOS 11

    -

    There is a dedicated repo, containing Apple’s sample on Drag&Drop, enhanced with DTTableViewManager and DTCollectionViewManager. Most of the stuff is just usual drop and drag delegate events, but there is also special support for UITableView and UICollectionView placeholders, that makes sure calls are dispatched to main thread, and if you use MemoryStorage, performs datasource updates automatically.

    +

    There is a dedicated repo, containing Apple’s sample on Drag&Drop, enhanced with DTTableViewManager and DTCollectionViewManager. Most of the stuff is just usual drop and drag delegate events, but there is also special support for UITableView and UICollectionView placeholders, that makes sure calls are dispatched to main thread, and if you use MemoryStorage, performs datasource updates automatically.

    Reacting to content updates

    -

    Sometimes it’s convenient to know, when data is updated, for example to hide UITableView, if there’s no data. TableViewUpdater has willUpdateContent and didUpdateContent properties, that can help:

    +

    Sometimes it’s convenient to know, when data is updated, for example to hide UITableView, if there’s no data. TableViewUpdater has willUpdateContent and didUpdateContent properties, that can help:

    updater.willUpdateContent = { update in
       print("UI update is about to begin")
     }
    @@ -377,21 +350,21 @@ 

    Reacting to content updates

    Customizing UITableView updates

    -

    DTTableViewManager uses TableViewUpdater class by default. However for CoreData you might want to tweak UI updating code. For example, when reloading cell, you might want animation to occur, or you might want to silently update your cell. This is actually how Apple’s guide for NSFetchedResultsController suggests you should do. Another interesting thing it suggests that .Move event reported by NSFetchedResultsController should be animated not as a move, but as deletion of old index path and insertion of new one.

    +

    DTTableViewManager uses TableViewUpdater class by default. However for CoreData you might want to tweak UI updating code. For example, when reloading cell, you might want animation to occur, or you might want to silently update your cell. This is actually how Apple’s guide for NSFetchedResultsController suggests you should do. Another interesting thing it suggests that .Move event reported by NSFetchedResultsController should be animated not as a move, but as deletion of old index path and insertion of new one.

    If you want to work with CoreData and NSFetchedResultsController, just call:

    manager.tableViewUpdater = manager.coreDataUpdater()
     
    -

    TableViewUpdater constructor allows customizing it’s basic behaviour:

    +

    TableViewUpdater constructor allows customizing it’s basic behaviour:

    let updater = TableViewUpdater(tableView: tableView, reloadRow: { indexPath in
       // Reload row
     }, animateMoveAsDeleteAndInsert: false)
     
    -

    These are all default options, however you might implement your own implementation of TableViewUpdater, the only requirement is that object needs to conform to StorageUpdating protocol. This gives you full control on how and when DTTableViewManager will update UITableView.

    +

    These are all default options, however you might implement your own implementation of TableViewUpdater, the only requirement is that object needs to conform to StorageUpdating protocol. This gives you full control on how and when DTTableViewManager will update UITableView.

    -

    TableViewUpdater also contains all animation options, that can be changed, for example:

    +

    TableViewUpdater also contains all animation options, that can be changed, for example:

    updater.deleteSectionAnimation = UITableViewRowAnimation.fade
     updater.insertRowAnimation = UITableViewRowAnimation.automatic
     
    @@ -444,9 +417,9 @@

    Conditional mappings

    Anomaly handler

    -

    DTTableViewManager is built on some conventions. For example, your cell needs to have reuseIdentifier that matches the name of your class, XIB files need to be named also identical to the name of your class(to work with default mapping without customization). However when those conventions are not followed, or something unexpected happens, your app may crash or behave inconsistently. Most of the errors are reported by UITableView API, but there’s space to improve.

    +

    DTTableViewManager is built on some conventions. For example, your cell needs to have reuseIdentifier that matches the name of your class, XIB files need to be named also identical to the name of your class(to work with default mapping without customization). However when those conventions are not followed, or something unexpected happens, your app may crash or behave inconsistently. Most of the errors are reported by UITableView API, but there’s space to improve.

    -

    And so, starting with 6.3.0 release, DTTableViewManager as well as DTCollectionViewManager and DTModelStorage now have dedicated anomaly analyzer, that tries to find inconsistencies and programmer errors when using those frameworks. It detects stuff like missing mappings, inconsistencies in xib files, and even unused events. By default, detected anomalies will be printed in console while you are debugging your app. For example, if you try to register an empty xib to use for your cell, here’s what you’ll see in console:

    +

    DTTableViewManager as well as DTCollectionViewManager and DTModelStorage have dedicated anomaly analyzers, that try to find inconsistencies and programmer errors when using those frameworks. They detect stuff like missing mappings, inconsistencies in xib files, and even unused events. By default, detected anomalies will be printed in console while you are debugging your app. For example, if you try to register an empty xib to use for your cell, here’s what you’ll see in console:

    ⚠️[DTTableViewManager] Attempted to register xib EmptyXib for PostCell, but this xib does not contain any views.
     
    @@ -458,7 +431,7 @@

    Anomaly handler

    }
    -

    For example, you may want to send all detected anomalies to analytics you have in your app. For this case anomalies implement shorter description, that is more suitable for analytics, that often have limits for amount of data you can put in. To do that globally for all instances of DTTableViewManager that will be created during runtime of your app, set default action:

    +

    For example, you may want to send all detected anomalies to analytics you have in your app. For this case anomalies implement shorter description, that is more suitable for analytics, that often have limits for amount of data you can put in. To do that globally for all instances of DTTableViewManager that will be created during runtime of your app, set default action:

    DTTableViewManagerAnomalyHandler.defaultAction = { anomaly in
       print(anomaly.debugDescription)
     
    @@ -466,23 +439,20 @@ 

    Anomaly handler

    }
    -

    If you use DTTableViewManager and DTCollectionViewManager, you can override 3 default actions for both manager frameworks and DTModelStorage, presumably during app initialization, before any views are loaded:

    +

    If you use DTTableViewManager and DTCollectionViewManager, you can override 3 default actions for both manager frameworks and DTModelStorage, presumably during app initialization, before any views are loaded:

    DTTableViewManagerAnomalyHandler.defaultAction = { anomaly in }
     DTCollectionViewManagerAnomalyHandler.defaultAction = { anomaly in }
     MemoryStorageAnomalyHandler.defaultAction = { anomaly in }
     

    Unregistering mappings

    -

    You can unregister cells, headers and footers from DTTableViewManager and UITableView by calling:

    +

    You can unregister cells, headers and footers from DTTableViewManager and UITableView by calling:

    manager.unregister(FooCell.self)
     manager.unregisterHeader(HeaderView.self)
     manager.unregisterFooter(FooterView.self)
     

    This is equivalent to calling tableView(register:nil,forCellWithReuseIdenfier: "FooCell")

    -

    ObjectiveC support

    - -

    DTTableViewManager is heavily relying on Swift protocol extensions, generics and associated types. Enabling this stuff to work on Objective-c right now is not possible. Because of this DTTableViewManager 4 and greater only supports building from Swift. If you need to use Objective-C, you can use latest Objective-C compatible version of DTTableViewManager.

    Thanks

      @@ -499,8 +469,8 @@

      Thanks