SHMTableView
SHMTableView is a wrapper around UITableView
datasource, that helps you define table contents by mapping view types to model instances.
SHMTableView
helps you abstract away the routine stuff in UITableViewDataSource
and UITableViewDelegate
. Instead you can focus on structure and content to be displayed by UITableView
.
To use SHMTableView
, you must:
- Create data model instances
- Map data models to view types
- Pass your mapping to the
SHMTableView
library
SHMTableView
creates and configures all UITableViewCell
instances to be displayed in UITableView
.
Installation
SHMTableView
is available through CocoaPods.
Add the following line to your Podfile
pod 'SHMTableView'
Install dependencies
pod install
Getting Started
- Include
SHMTableView
into your swift file
import SHMTableView
- Tell
SHMTableView
to manageUITableView
datasource and delegate
var table = SHMTableView(tableView: tableView)
- Define your table sections and rows
var section = SHMTableSection()
section += SHMTableRow<MovieCell>(model: Movie(name: "Monsters, Inc."))
section += SHMTableRow<MovieCell>(model: Movie(name: "Singin' in the Rain"))
section += SHMTableRow<SeriesCell>(model: Series(name: "Shaun the Sheep", numberOfEpisodes: 40))
section += SHMTableRow<MovieCell>(model: Movie(name: "My Fair Lady, Inc."))
section += SHMTableRow<SeriesCell>(model: Series(name: "Friends", numberOfEpisodes: 236))
- Pass the content structure to the
SHMTableView
table.update(withNewSections: [section])
Result: your content displays in UITableView
Documentation
Rows
Each cell requires one model that describes the content and behavior of the cell. The model can be any protocol, struct, class or even just plain String.
struct Movie
{
let name: String
let director: String
let watch: ((Void) -> Void)
}
Each cell must conform to the SHMConfigurableRow
protocol. Specify each model type via typealias.
class MovieCell: UITableViewCell, SHMConfigurableRow
{
typealias T = Movie
@IBOutlet var nameLabel: UILabel!
@IBOutlet var directorLabel: UILabel!
func configure(_ model: T)
{
// called during tableView(_:cellForRowAt:)
}
func configureAtWillDisplay(_ model: T)
{
// called during tableView(_:willDisplay:forRowAt:)
nameLabel.text = model.name
directorLabel.text = model.director
}
}
Connect the view type with the model instance
let monsters = Movie(
name: "Monsters, Inc.",
director: "Pete Docter, Lee Unkrich, David Silverman",
watch: { /* start video player */ }
)
var row = SHMTableRow<MovieCell>(model: monsters)
row.action = { indexPath in
// called on tableView(_:didSelectRowAt:)
monsters.watch()
}
SHMTableView
loads NIB with the same name as the cell type. The library and UITableView
use this name as reuse identifier when registering cells to UITableView
.
Sections
To divide rows you can add sections.
let sectionWithMusicals = SHMTableSection(rows: [
SHMTableRow<SimpleCell>(model: Movie(name: "Singin' in the Rain")),
SHMTableRow<SimpleCell>(model: Movie(name: "My Fair Lady")),
SHMTableRow<SimpleCell>(model: Movie(name: "The Sound of Music"))
])
let sectionForKids = SHMTableSection(rows: [
SHMTableRow<ColorfulCell>(model: Movie(name: "Shaun the Sheep")),
SHMTableRow<ColorfulCell>(model: Movie(name: "Monsters, Inc.")),
SHMTableRow<ColorfulCell>(model: Movie(name: "Shrek"))
])
moviesTable += sectionWithMusicals
moviesTable += sectionForKids
Headers and Footers
Headers and footers can contain titles or views.
Titles can be specified for sections:
let musicals = SHMTableSection()
musicals.headerTitle = "Musicals (header)"
musicals.footerTitle = "Musicals (footer)"
Alternatively a view, can be used instead of title:
let musicals = SHMTableSection()
if let view = Bundle.main.loadNibNamed("ColorfulHeaderView", owner: nil, options: nil)?[0] as? UIView
{
musicals.headerView = SHMTableHeader<ColorfulHeaderView>(model: "Musicals (header)", view: view)
}
if let view = Bundle.main.loadNibNamed("ColorfulFooterView", owner: nil, options: nil)?[0] as? UIView
{
musicals.footerView = SHMTableHeader<ColorfulFooterView>(model: "Musicals (footer)", view: view)
}
Custom header/footer view must conform to SHMConfigurable
protocol.
class ColorfulHeaderView: UIView, SHMConfigurable
{
typealias T = String
@IBOutlet var label: UILabel!
func configure(_ model: T)
{
label.text = model
}
}
Updating table content
Table sections and rows can be updated via update(withNewSections:)
method.
For example, the table is first filled with some initial content.
let table = SHMTableView(tableView: tableView)
table.update(withNewSections: [
SHMTableSection(rows: [
SHMTableRow<TitleCell>(model: "Atlantis"),
SHMTableRow<TitleCell>(model: "Mission: Impossible"),
SHMTableRow<TitleCell>(model: "Stargate"),
]),
SHMTableSection(rows: [
SHMTableRow<TitleCell>(model: "Big Bang Theory"),
SHMTableRow<TitleCell>(model: "Friends"),
SHMTableRow<TitleCell>(model: "Shaun The Sheep"),
])
])
Some time later the table can be updated with changed sections and rows:
table.update(withNewSections: [
SHMTableSection(rows: [
SHMTableRow<TitleCell>(model: "Atlantis"),
SHMTableRow<TitleCell>(model: "Mission: Impossible"),
SHMTableRow<TitleCell>(model: "Game Of Thrones"), // new
SHMTableRow<TitleCell>(model: "Stargate"),
SHMTableRow<TitleCell>(model: "Transformers"), // new
]),
SHMTableSection(rows: [
SHMTableRow<TitleCell>(model: "Friends"),
SHMTableRow<TitleCell>(model: "Shaun The Sheep"),
]),
SHMTableSection(rows: [
SHMTableRow<TitleCell>(model:"Blue Planet"), // new
SHMTableRow<TitleCell>(model:"Lions On The Move"), // new
SHMTableRow<TitleCell>(model:"Shoreline"), // new
])
])
Method update(withNewSections:)
internally computes diff between current list and new list. Once it knows changes, the method tries to animate the section and row changes if it is possible. Our example above causes these updates:
- insertions
- two rows in first section
- Game of Thrones
- Transformers
- whole new third section
- two rows in first section
- deletions
- one rows in second section
- Big Bang Theory
- one rows in second section
Optionally, you can improve diffing by implementing SHMDiffable
into your model.
extension Movie: SHMDiffable
{
public func isEqual(to other: SHMDiffable) -> Bool
{
guard let other = other as? Movie else { return false }
return self.name == other.name
}
}
Operators
Append section to table
table += SHMTableSection()
Append row to the first section in table. Creates section, if table is empty.
table += SHMTableRow<MovieCell>(model: Movie(name: "Singin' in the Rain"))
Append row to specific section.
section += SHMTableRow<MovieCell>(model: Movie(name: "Singin' in the Rain"))
Append array of rows to specific section.
section += [
SHMTableRow<ColorfulCell>(model: Movie(name: "Shaun the Sheep")),
SHMTableRow<ColorfulCell>(model: Movie(name: "Monsters, Inc.")),
SHMTableRow<ColorfulCell>(model: Movie(name: "Shrek"))
]
Using UITableView in editing mode
See example in Example/SHMTableView/EditingController
Using in view controller
You can map SHMTableView
instance to UITableView
in outlet variable didSet
handler. Alternatively you can use SHMTableViewController
, which does exactly that.
import SHMTableView
class MyViewController: UIViewController
{
public var shmTable: SHMTableView!
@IBOutlet weak var tableView: UITableView!
{
didSet
{
shmTable = SHMTableView(tableView: tableView)
}
}
}
Example code
To run the example project, clone the repo, and run pod install
from the Example directory.
cd libs/SHMTableView/Example
pod install
open SHMTableView.xcworkspace
Comparison of SHMTableView and plain UITableView
Example of using SHMTableView
var table = SHMTableView(tableView: tableView)
table += [
SHMTableSection(rows: [
SHMTableRow<MusicalCell>(model: Movie(name: "Singin' in the Rain")),
SHMTableRow<MusicalCell>(model: Series(name: "Nashville")),
SHMTableRow<MusicalCell>(model: Movie(name: "My Fair Lady")),
SHMTableRow<MusicalCell>(model: Series(name: "Glee"))
]),
SHMTableSection(rows: [
SHMTableRow<KidsCell>(model: Movie(name: "Monsters, Inc.")),
SHMTableRow<KidsCell>(model: Series(name: "Shaun the Sheep")),
SHMTableRow<KidsCell>(model: Movie(name: "Shrek"))
])
]
Instances of models are mapped to the view types and passed to the SHMTableView
library, which creates required sections and rows in UITableView
.
Example of using plain UITableView
// Registering cells to UITableView
// ...
// Implementing UITableViewDataSource
public func numberOfSections(in tableView: UITableView) -> Int
{
// ...
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
// ...
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let model = ...
if let movieModel = model as? Movie,
let cell = tableView.dequeueReusableCell(withReuseIdentifier: "Movie", for: indexPath) as? MovieCell
{
cell.nameLabel.text = item.title
return cell
} else if let seriesModel = model as? Series,
let cell = tableView.dequeueReusableCell(withReuseIdentifier: "Series", for: indexPath) as? MovieCell
{
cell.nameLabel.text = item.title
return cell
} else
{
fail()
}
}
public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
{
let model = ...
if model is Movie
{
// ...
} else if model is Series
{
// ...
}
}
// Implementing UITableViewDelegate
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let model = ...
if model is Movie
{
// ...
} else if model is Series
{
// ...
}
}
// Setup section headers and footers
// ...
// Setup cell self sizing
// ...
Using plain UITableView
requires you to implement all the data sources and delegate methods. You have to handle updates in a model structure. You have to write a lot of repetitive code. Each additional view and model requires more if-else checking, ending up with complex and difficult to maintain code.
Development
cd libs/SHMTableView/Example
pod install
open SHMTableView.xcworkspace
Future Ideas
- animate row reloads and moves (currently are supported insertions and deletions)
- support table header view
Thanks
- Dwifft by Jack Flintermann is very nice implementation of diff algorithm based on solving Longest Common Subsequence problem. Available as separate Pod library. We have customized original code to our needs.
Authors
Showmax is an internet-based subscription video on demand service supplying an extensive catalogue of TV shows and movies. By leveraging relationships with major production studios from across the globe, Showmax delivers both world-class international content as well as the best of specialised local content. Showmax is accessible across a wide range of devices from smart TVs and computers to smartphones and tablets.
You can follow us at https://tech.showmax.com and/or https://twitter.com/ShowmaxDevs .
Status
This code is exactly one running in our production app. We are using the same pod as you see here. PRs are welcome.
License
SHMTableView
is available under the Apache license. See the LICENSE
file for more info.