Latest 1.5.0
Homepage https://github.com/ladeiko/CacheTrackerConsumer
License CUSTOM
Platforms ios 9.0, requires ARC
Authors

Helper classes for use as mediator between CacheTracker and UI controls (UITableView, UICollectionView). It keeps local one or two dimentional array of items in sync with cache tracker storage. UI controls can interact with consumer directly. This is very helpful when you use VIPER architecture. You place consumer in VIEW and pass transactions from INTERACTOR through PRESENTER to your VIEW. VIEW in this case stays passive as VIPER rules requires and its state is controlled from PRESENTER only.

Changes

See CHANGELOG

Types

CacheTrackerPlainConsumer

Just keep plain items in linear array in sync with cache tracker storage and generates updates for UI controls.

CacheTrackerPlainRecurrentConsumer

Works like CacheTrackerPlainConsumer, the only difference: whne some item is updated or moved to another index, then you can save some properties from old value to new one, this can be usefull, for example, when you add new ‘man’ object to array with ‘personal number’, this number should not be changed until item is deleted from array, other fields can change:

class Man: CacheTrackerPlainRecurrentConsumerItem {

  let personalNumber: String      // this value should be stay the same
  let firstName: String           // can change
  let lastName: String            // can change

  init(personalNumber: String, firstName: String, lastName: String) {
    self.personalNumber = personalNumber
    self.firstName = firstName
    self.lastName = lastName
  }

  // MARK: CacheTrackerPlainModel

  init() {
    personalNumber = ""
    firstName = ""
    lastName = ""
  }

  // MARK: CacheTrackerPlainRecurrentConsumerItem

  func recurrentPlainConsumerItem(using oldValue: CacheTrackerPlainRecurrentConsumerItem) -> CacheTrackerPlainRecurrentConsumerItem {
      let oldValue = oldValue as! Man
      // Even if item is updated its 'personalNumber' will have the same value since it first time appeared in consumer.
      return Man(personalNumber: oldValue.personalNumber, firstName: firstName, lastName: lastName)
  }

}

let consumer = CacheTrackerPlainRecurrentConsumer<Man>()
consumer.willChange()
consumer.add(Man(personalNumber: "123", firstName:"John", lastName:"Smith"), at: 0)
consumer.didChange()

consumer.willChange()
// NOTE: new value "456" will be ignored
consumer.update(Man(personalNumber: "456", firstName:"John", lastName:"Smith"), at: 0) 
consumer.didChange()

let personalNumber = consumer.object(at: 0).personalNumber

// will print '123', NOT '456'
print(personalNumber) 

CacheTrackerSectionedConsumer

Just keep plain items in sectioned array in sync with cache tracker storage and generates updates for UI controls. Cache tracker generates linear array of plain items, but they are converted to sectioned array and UI controls think they work with sections.
You can say that "why do not use direct section notifications from CacheTracker" – and the answer is: i spent two weeks for analyzing behaviour of notifications generated by CoreData/Realm FRC, i understood, that it is "hell" when you try to use sections in FRC, and there is no normal way to keep items in own two dimentional array in sync with storage managed by only FRC, so i decided to write these classes to make it easy.

NOTE: If you have millions of items, then i recommend to you direct interaction of VIEW with CacheTracker, because of performance issue.

Usage

To use with CacheTrackerSectionedConsumer just make your plain model compatibel with CacheTrackerSectionedConsumerModel protocol.
For complete examples see Demo project.

class PlainItem: CacheTrackerPlainModel, CacheTrackerSectionedConsumerModel {

    let name: String
    let section: String

    init(name: String) {
        self.name = name
        self.section = String(name.prefix(1))
    }

    // MARK: - CacheTrackerPlainModel

    required init() {
        self.name = ""
        self.section = ""
    }

    // MARK: - CacheTrackerSectionedConsumerModel

    func sectionTitle() -> String {
        return self.section
    }

}

Create consumer and set its delegate

consumer = CacheTrackerSectionedConsumer<PlainItem>()
consumer.delegate = self

When you use CacheTrackerSectionedConsumer then do not forget to pass section key as first sort descriptor (in this example we want to use ‘section’ as section key)

let cacheRequest = CacheRequest(predicate: NSPredicate(value: true), sortDescriptors: [
    NSSortDescriptor(key: #keyPath(CoreDataItem.section), ascending: true),
    NSSortDescriptor(key: #keyPath(CoreDataItem.name), ascending: true)
    ])

Fill up consumer with initial set of objects if required

  • using animated insert
consumer.willChange()
consumer.consume(transactions: cacheTracker.transactionsForCurrentState())
consumer.didChange()
  • complete reload
consumer.reset(with: cacheTracker.transactionsForCurrentState())
tableView.reloadData()

NOTE: You should call willChange() before any batch calls to consume(), add(), remove(), update() of consumer. After all batch operations were called you have to complete interaction with didChange() as in example above.

Implement table view datasource (or collection view), where you will refer to consumer methods:

  • where cacheTrackerSectionOffset == 0 and cacheTrackerItemsOffset == 0 (by default)
override func numberOfSections(in tableView: UITableView) -> Int {
    return consumer.sectionsCount()
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return consumer.numberOfItems(at: section)
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    return tableView.dequeueReusableCell(withIdentifier: "Default")!
}

override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    let item = consumer.object(at: indexPath)
    cell.textLabel?.text = item.name
}

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    let item = consumer.object(at: IndexPath(row: 0, section: section))
    return item.section
}
  • where cacheTrackerSectionOffset > 0 or cacheTrackerItemsOffset > 0
override func numberOfSections(in tableView: UITableView) -> Int {
    return self.globalSectionCount(1)
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if self.isPlainDataSection(section) {
        return consumer.numberOfItems() + cacheTrackerItemsOffset
    }
    else {
        return 1
    }
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if self.isPlainDataIndexPath(indexPath) {
        return tableView.dequeueReusableCell(withIdentifier: "Default")!
    }
    else {
        return tableView.dequeueReusableCell(withIdentifier: "Before")!
    }
}

override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if self.isPlainDataIndexPath(indexPath) {
        let item = consumer.object(at: self.plainIndexPath(from: indexPath).row)
        cell.textLabel?.text = item.name
    }
    else {
        cell.textLabel?.text = "Before (indexPath)"
    }
}

Offsets

If you want to show items not from the beginning, then you can set offsets using:

  • var cacheTrackerItemsOffset: Int = 0
  • var cacheTrackerSectionOffset: Int = 0

They control offset of data section / items from the beginning of the tableView or collectionView.

For more detailed example see Demo project

Installation

Cocoapods

Add to you Podfile

pod 'CacheTrackerConsumer'

If you want to use UIKit extensions

pod 'CacheTrackerConsumer'
pod 'CacheTrackerConsumer/UIKit'

UIKit extensions contains default implementations of CacheTrackerPlainConsumerDelegate (or CacheTrackerSectionedConsumerDelegate) for UITableView, UICollectionView, UITableViewController, UICollectionViewController, etc…

And finally import the module

import CacheTrackerConsumer

Latest podspec

{
    "name": "CacheTrackerConsumer",
    "version": "1.5.0",
    "summary": "Helper classes to connect UI and CacheTracker (https://github.com/ladeiko/CacheTracker)",
    "homepage": "https://github.com/ladeiko/CacheTrackerConsumer",
    "license": {
        "type": "CUSTOM",
        "file": "LICENSE"
    },
    "authors": {
        "Siarhei Ladzeika": "[email protected]"
    },
    "platforms": {
        "ios": "9.0"
    },
    "source": {
        "git": "https://github.com/ladeiko/CacheTrackerConsumer.git",
        "tag": "1.5.0"
    },
    "requires_arc": true,
    "default_subspecs": "Core",
    "subspecs": [
        {
            "name": "Core",
            "source_files": "Classes/Core/**/*.{swift}",
            "dependencies": {
                "CacheTracker": []
            }
        },
        {
            "name": "UIKit",
            "source_files": [
                "Classes/Core/**/*.{swift}",
                "Classes/UIKit/**/*.{swift,h}"
            ],
            "frameworks": "UIKit",
            "dependencies": {
                "CacheTracker": []
            }
        }
    ]
}

Pin It on Pinterest

Share This