TableCollectionManager 1.0.2

TableCollectionManager 1.0.2

Maintained by Dima Mishchenko.



  • By
  • Dmytro Mishchenko

TableCollectionManager

TableCollectionManager is lightweight generic library that allows you to build table and collection views in a declarative type-safe style.

  • Type-safe generic cells and models

  • Functional programming style

  • No need for delegates and data sources

  • Automatic update for View with Model

Table of content

Installation

  • add following to Podfile
pod 'TableCollectionManager'
  • Run pod install.
  • Add to files:
import TableCollectionManager

Usage

The main idea is that you have manager to manage table/collection view. And the manager have storage that you can update with items. After that magic happens and view will updates and reloads automatically. Also you can easily handle actions in functional style.

TableView

Table Quick start

import TableCollectionManager

// create cell and implement TableConfigurable

class ExampleCell: UITableViewCell, TableConfigurable {
  
  func update(with model: String) {
    textLabel?.text = model
  }
}

class VC: UIViewController {
  
  // init manager
  let manager = TableManager()
  var tableView = UITableView(frame: .zero, style: .plain)
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    // set tableView to manager
    manager.tableView = tableView
    
    // create rows
    
    let firstRow = TableRow<ExampleCell>("First").didSelect { result in
      // result.model - cell model  
      // result.cell - cell 
			// result.indexPath - indexPath                       
      print("first")
    }
    
    let secondRow = TableRow<ExampleCell>("Second").didSelect { _ in                    
      print("second")
    }
    
    // add rows to storage
    manager.storage.append([firstRow, secondRow])
  }
}

TableCell

To use cells with TableManager you have to implement TableConfigurable protocol.

You have to implement update(with:) func to provide model type.

Imagine you have Pet object and you want to create cell to show data of this item. In this case cell will look like this.

struct Pet {
  
  let name: String
  let age: Int
}

class PetCell: UITableViewCell, TableConfigurable {
  
  func update(with model: Pet) {
    // fill cell with data 
    textLabel?.text = "\(model.name) is \(model.age) years old"
  }
}

You can also provide cell height and estimatedHeight. By default they equals UITableView.automaticDimension.

class Cell: UITableViewCell, TableConfigurable {
  
  // custom height
  static var height: CGFloat { 
    44
  }
  
  // custom estimatedHeight
  static var estimatedHeight: CGFloat { 
    44
  }
  
  func update(with model: String) {
 
  }
}

You also don't have to register cell. TableManager will do this for you.

TableRow

TableRow is a wrapper around cell. So you only work with rows and models (viewModel) for this row.

To create TableRow provide cell type and model instance.

let pet = Pet(name: "Elon", age: 2)
let petRow = TableRow<PetCell>(pet)

You can also subscribe on some actions from UITableViewDelegate/UITableViewDataSource. For example you want didSelect for this row. In this case just call didSelect.

petRow.didSelect { result in
  // do something
  // result.model - cell model  
  // result.cell - cell 	
  // result.indexPath - indexPath                       
}

TableRow supports this actions:

  • didSelect

  • didDeselect

  • willSelect

  • willDeselect

  • willDisplay

  • didEndDisplaying

  • moveTo

  • shouldHighlight

  • editingStyle

  • leadingSwipeActions

  • trailingSwipeActions

  • canEdit

  • canMove

  • commitStyle

  • move

You can also chain actions.

let row = TableRow<Cell>(model).didSelect { _ in
  print("selected")
}.willDisplay { _ in
  print("willDisplay")
}.canEdit { _ in
  return true
}

TableHeaderFooter

Headers and footer works almost like cells. The only difference is that estimatedHeight not optional.

class HeaderFooter: UITableViewHeaderFooterView, TableConfigurable {
  
  // required
  static var estimatedHeight: CGFloat {
    44
  }
  
  // optional (UITableView.automaticDimension by default)
  static var height: CGFloat { 
    44
  }
  
  func update(with model: String) {
    textLabel?.text = model
  }
}

TableHeaderFooter is a wrapper for headers and footers. To add them to table view just do this.

let header = TableHeaderFooter<HeaderFooter>("Header")
// add header to section at index
manager.storage.setHeader(header, to: 0)

let footer = TableHeaderFooter<HeaderFooter>("Footer")
// add header to section at index
manager.storage.setFooter(footer, to: 1)

TableSection

TableSection is a wrapper around UITableView sections. If you have more then one section in you table view you should use TableSection. You can set rows, header and footer to each section.

let rows = (1...10).map { TableRow<Cell>("\($0)") }
let header = TableHeaderFooter<HeaderFooter>("Header")
let footer = TableHeaderFooter<HeaderFooter>("Footer")

let section = TableSection(
  rows: rows,
  header: header,
  footer: footer
)

manager.storage.append(section)

TableStorage

TableStorage is a place where all sections and rows store. Each TableManager has storage. And if pass data to this storage table view will updates automatically.

manager.storage.append(sections)
manager.storage.delete(row)
manager.storage.reload(rows)
// and ect.

You can also get index of section, indexPath of row, section at index or row at indexPath.

let sectionIndex = manager.storage.index(for: section)
let rowIndexPath = manager.storage.indexPath(for: row)

let section = manager.storage.section(at: index)
let row = manager.storage.row(at: indexPath)

Storage has performWithoutAnimation block. Every change you call inside this block will perform without animation.

manager.storage.performWithoutAnimation {
  manager.storage.append(sections)
  manager.storage.reload(rows)
}

Storage has performBatchUpdate block. You can use this method in cases where you want to make multiple changes in one single animated operation.

manager.storage.performBatchUpdate({
  manager.storage.append(sections)
  manager.storage.delete(row)
}, completion: { isFinished in
  // completion
})

All storage functionality

  • isEmpty

  • numberOfSections

  • numberOfAllRows

  • index(for section:)

  • indexPath(for row:)

  • section(at index:)

  • row(at indexPath:)

  • performWithoutAnimation(_ block:)

  • performBatchUpdate(_ block:, completion:)

  • reload()

  • reload(_ row:)

  • reload(_ rows:)

  • reload(_ section:)

  • reload(_ sections:)

  • append(_ row:)

  • append(_ rows:)

  • append(_ row:, to sectionIndex:)

  • append(_ rows:, to sectionIndex:)

  • append(_ section:)

  • append(_ sections:)

  • insert(_ row:, at indexPath:)

  • insert(_ section:, at index:)

  • delete(_ row:)

  • delete(_ rows:)

  • delete(_ section:)

  • delete(_ sections:)

  • deleteAllItems()

  • moveRowWithoutUpdate(from source:, to destination:)

  • moveRow(from source:, to destination:)

  • moveSection(from source:, to destination:)

TableManager

TableManager is object that manage all the delegates and data sources. Manager holds storage and tableView. You can change both of them.

Manager also has scrollHandler which handles UIScrollViewDelegate.

manager.scrollHandler.didScroll {
  print("didScroll")
}.shouldScrollToTop {
  return true
}

CollectionView

Collection Quick start

import TableCollectionManager

// create cell and implement CollectionConfigurable

class ExampleCell: UICollectionViewCell, CollectionConfigurable {
  
  func update(with model: String) {
    textLabel?.text = model
  }
}

class VC: UIViewController {
  
  // init manager
  let manager = CollectionManager()
	var collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    // set collectionView to manager
    manager.collectionView = collectionView
    
    // create rows
    
    let firstRow = CollectionRow<ExampleCell>("First").didSelect { result in
      // result.model - cell model  
      // result.cell - cell 
			// result.indexPath - indexPath                       
      print("first")
    }
    
    let secondRow = CollectionRow<ExampleCell>("Second").didSelect { _ in                    
      print("second")
    }
    
    // add rows to storage
    manager.storage.append([firstRow, secondRow])
  }
}

CollectionCell

To use cells with TableManager you have to implement CollectionConfigurable protocol.

You have to implement update(with:) func to provide model type.

Imagine you have Pet object and you want to create cell to show data of this item. In this case cell will look like this.

struct Pet {
  
  let name: String
  let age: Int
}

class PetCell: UICollectionViewCell, CollectionConfigurable {
  
  func update(with model: Pet) {
    // fill cell with data 
  }
}

You can also provide cell size. It's optional you can also provide height using UICollectionViewLayout or using layoutHandler of CollectionManager.

class Cell: UICollectionViewCell, CollectionConfigurable {
  
  // custom size
  static var size: CGSize? {
    CGSize(width: 44, height: 44)
  }
  
  func update(with model: String) {
 
  }
}

You also don't have to register cell. CollectionManager will do this for you.

CollectionRow

CollectionRow is a wrapper around cell. So you only work with rows and models (viewModel) for this row.

To create CollectionRow provide cell type and model instance.

let pet = Pet(name: "Elon", age: 2)
let petRow = CollectionRow<PetCell>(pet)

You can also subscribe on some actions from UICollectionViewDelegate/UICollectionViewDataSource. For example you want didSelect for this row. In this case just call didSelect.

petRow.didSelect { result in
  // do something
  // result.model - cell model  
  // result.cell - cell 	
  // result.indexPath - indexPath                       
}

CollectionRow supports this actions:

  • didSelect

  • didDeselect

  • willDisplay

  • didEndDisplaying

  • moveTo

  • shouldHighlight

  • canMove

  • move

You can also chain actions.

let row = CollectionRow<Cell>(model).didSelect { _ in
  print("selected")
}.willDisplay { _ in
  print("willDisplay")
}

CollectionHeaderFooter

Headers and footer works almost like cells. Size handles same way as CollectionRow.

class HeaderFooter: UICollectionReusableView, CollectionConfigurable {
  
  // custom size
  static var size: CGSize? {
    CGSize(width: 44, height: 44)
  }
  
  func update(with model: String) {

  }
}

CollectionHeaderFooter is a wrapper for headers and footers. You can add headers/footers using sections.

let header = CollectionHeaderFooter<HeaderFooter>("Header")
let footer = CollectionHeaderFooter<HeaderFooter>("Footer")
let section = CollectionSection(
  rows: [],
  header: header,
  footer: footer
)

CollectionSection

CollectionSection is a wrapper around UICollectioView sections. If you have more then one section in you table view you should use CollectionSection. You can set rows, header and footer to each section.

let rows = (1...10).map { CollectioRow<Cell>("\($0)") }
let header = CollectionHeaderFooter<HeaderFooter>("Header")
let footer = CollectionHeaderFooter<HeaderFooter>("Footer")

let section = CollectionSection(
  rows: rows,
  header: header,
  footer: footer
)

manager.storage.append(section)

CollectionStorage

CollectionStorage is a place where all sections and rows store. Each CollectionManager has storage. And if pass data to this storage collectio view will updates automatically.

manager.storage.append(sections)
manager.storage.delete(row)
manager.storage.reload(rows)
// and ect.

You can also get index of section, indexPath of row, section at index or row at indexPath.

let sectionIndex = manager.storage.index(for: section)
let rowIndexPath = manager.storage.indexPath(for: row)

let section = manager.storage.section(at: index)
let row = manager.storage.row(at: indexPath)

Storage has performWithoutAnimation block. Every change you call inside this block will perform without animation.

manager.storage.performWithoutAnimation {
  manager.storage.append(sections)
  manager.storage.reload(rows)
}

Storage has performBatchUpdate block. You can use this method in cases where you want to make multiple changes in one single animated operation.

manager.storage.performBatchUpdate({
  manager.storage.append(sections)
  manager.storage.delete(row)
}, completion: { isFinished in
  // completion
})

All storage functionality - same as TableStorage.

CollectionManager

CollectionManager is object that manage all the delegates and data sources. Manager holds storage and collectionView. You can change both of them.

Manager also has scrollHandler which handles UIScrollViewDelegate.

manager.scrollHandler.didScroll {
  print("didScroll")
}.shouldScrollToTop {
  return true
}

Manager also has layoutHandler which handles UICollectionViewDelegateFlowLayout and collectionView(_ :transitionLayoutForOldLayout:newLayout:) delegate method.

You can for example provide sizes for items.

manager.layoutHandler.sizeForItem { indexPath in
  return CGSize(width: 44, height: 44)
}.sizeForHeader { sectionIndex in
  return CGSize(width: 44, height: 44)
}

License

TableCollectionManager is under MIT license. See the LICENSE file for more info.