diff --git a/Demo/Classes/Presentation/Views/AutolayoutTableViewCell.swift b/Demo/Classes/Presentation/Views/AutolayoutTableViewCell.swift index 6f5f7c4..ec505d6 100644 --- a/Demo/Classes/Presentation/Views/AutolayoutTableViewCell.swift +++ b/Demo/Classes/Presentation/Views/AutolayoutTableViewCell.swift @@ -19,13 +19,13 @@ class AutolayoutTableViewCell: UITableViewCell, ConfigurableCell { @IBOutlet var titleLabel: UILabel! @IBOutlet var subtitleLabel: UILabel! + static var estimatedHeight: CGFloat? { + return 500 + } + func configure(with string: T) { titleLabel.text = LoremIpsumTitle subtitleLabel.text = LoremIpsumBody } - - static func estimatedHeight() -> CGFloat? { - return 500 - } } \ No newline at end of file diff --git a/Demo/Classes/Presentation/Views/NibTableViewCell.swift b/Demo/Classes/Presentation/Views/NibTableViewCell.swift index 74b4a27..d561314 100644 --- a/Demo/Classes/Presentation/Views/NibTableViewCell.swift +++ b/Demo/Classes/Presentation/Views/NibTableViewCell.swift @@ -13,11 +13,11 @@ class NibTableViewCell: UITableViewCell, ConfigurableCell { @IBOutlet weak var titleLabel: UILabel! + static var defaultHeight: CGFloat? { + return 100 + } + func configure(with number: Int) { titleLabel.text = "\(number)" } - - static func defaultHeight() -> CGFloat? { - return 100 - } } \ No newline at end of file diff --git a/README.md b/README.md index f740cc1..c3adbf6 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Build Status Swift 2.2 compatible Carthage compatible - CocoaPods compatible + CocoaPods compatible Platform iOS License: MIT

@@ -12,12 +12,12 @@ TableKit is a super lightweight yet powerful generic library that allows you to build complex table views in a declarative type-safe manner. It hides a complexity of `UITableViewDataSource` and `UITableViewDelegate` methods behind the scene, so your code will be look clean, easy to read and nice to maintain. -## Features +# Features - [x] Type-safe generic cells - [x] Functional programming style friendly - [x] The easiest way to map your models or view models to cells -- [x] Automatic cell registration +- [x] Automatic cell registration* - [x] Correctly handles autolayout cells with multiline labels - [x] Chainable cell actions (select/deselect etc.) - [x] Support cells created from code, xib, or storyboard @@ -26,17 +26,19 @@ It hides a complexity of `UITableViewDataSource` and `UITableViewDelegate` metho - [x] No need to subclass - [x] Extensibility -## Getting Started +# Getting Started An [example app](Demo) is included demonstrating TableKit's functionality. -#### Basic usage +## Basic usage Create your rows: ```swift +import TableKit + let row1 = TableRow(item: "1") let row2 = TableRow(item: 2) -let row3 = TableRow(item: 3.0) +let row3 = TableRow(item: User(name: "John Doe", rating: 5)) ``` Put rows into section: ```swift @@ -47,24 +49,38 @@ And setup your table: let tableDirector = TableDirector(tableView: tableView) tableDirector += section ``` -Done. Your table is ready. You may want to look at your cell. It has to conform to `ConfigurableCell` protocol: +Done. Your table is ready. Your cells have to conform to `ConfigurableCell` protocol: ```swift class StringTableViewCell: UITableViewCell, ConfigurableCell { - typealias T = String + func configure(with string: String) { + + textLabel?.text = string + } +} - func configure(string: T, isPrototype: Bool) { - titleLabel.text = string +class UserTableViewCell: UITableViewCell, ConfigurableCell { + + static var estimatedHeight: CGFloat? { + return 100 } - static func estimatedHeight() -> CGFloat { - return 44 + // is not required to be implemented + // by default reuse id is equal to cell's class name + static var reuseIdentifier: String { + return "my id" } + + func configure(with user: User) { + + textLabel?.text = user.name + detailTextLabel?.text = "Rating: \(user.rating)" + } } ``` You could have as many rows and sections as you need. -#### Row actions +## Row actions It nice to have some actions that related to your cells: ```swift @@ -74,7 +90,7 @@ let action = TableRowAction(.click) { (data) in // data.cell - StringTableViewCell? // data.item - String - // data.path - NSIndexPath + // data.indexPath - NSIndexPath } let row = TableRow(item: "some", actions: [action]) @@ -82,30 +98,54 @@ let row = TableRow(item: "some", actions: [action]) Or, using nice chaining approach: ```swift let row = TableRow(item: "some") - .action(TableRowAction(.click) { (data) in + .action(.click) { (data) in - }) - .action(TableRowAction(.shouldHighlight) { (data) -> Bool in + } + .action(.shouldHighlight) { (data) -> Bool in return false - }) + } ``` You could find all available actions [here](Sources/TableRowAction.swift). -## Advanced +## Custom row actions -#### Cell height calculating strategy +You are able to define your own actions: +```swift +struct MyActions { + + static let ButtonClicked = "ButtonClicked" +} + +class MyTableViewCell: UITableViewCell, ConfigurableCell { + + @IBAction func myButtonClicked(sender: UIButton) { + + TableCellAction(key: MyActions.ButtonClicked, sender: self).invoke() + } +} +``` +And handle them accordingly: +```swift +let myAction = TableRowAction(.custom(MyActions.ButtonClicked)) { (data) in + +} +``` + +# Advanced + +## Cell height calculating strategy By default TableKit relies on self-sizing cells. In that case you have to provide an estimated height for your cells: ```swift class StringTableViewCell: UITableViewCell, ConfigurableCell { // ... - static func estimatedHeight() -> CGFloat { - return 44 + static var estimatedHeight: CGFloat? { + return 255 } } ``` -It's enough for most cases. But you may be not happy with this. So you could use a prototype cell to calculate cell's heights. To enable this feature simply use this property: +It's enough for most cases. But you may be not happy with this. So you could use a prototype cell to calculate cells heights. To enable this feature simply use this property: ```swift tableDirector.shouldUsePrototypeCellHeightCalculation = true ``` @@ -113,11 +153,9 @@ It does all dirty work with prototypes for you [behind the scene](Sources/Height ```swift class ImageTableViewCell: UITableViewCell, ConfigurableCell { - func configure(url: NSURL, isPrototype: Bool) { + func configure(with url: NSURL) { - if !isPrototype { - loadImageAsync(url: url, imageView: imageView) - } + loadImageAsync(url: url, imageView: imageView) } override func layoutSubviews() { @@ -128,37 +166,55 @@ class ImageTableViewCell: UITableViewCell, ConfigurableCell { } } ``` -First of all you have to set `preferredMaxLayoutWidth` for all your multiline labels. And check if a configuring cell is a prototype cell. If it is, you don't have to do any additional work that not actually affect cell's height. For example you don't have to load remote image for a prototype cell. +You have to additionally set `preferredMaxLayoutWidth` for all your multiline labels. -#### Functional programming +## Functional programming It's never been so easy to deal with table views. ```swift let users = /* some users array */ -let rows: [Row] = users.filter({ $0.state == .active }).map({ TableRow(item: $0.username) }) +let click = TableRowAction(.click) { + +} + +let rows: [Row] = users.filter({ $0.state == .active }).map({ TableRow(item: $0.name, actions: [click]) }) tableDirector += rows ``` -Done, your table is ready. It's just awesome! +Done, your table is ready. +## Automatic cell registration -## Installation +TableKit can register your cells in table view automatically. In case if your reusable cell id mathces cell's xib name: -#### CocoaPods +```ruby +MyTableViewCell.swift +MyTableViewCell.xib + +``` +You can also turn off this behaviour: +```swift +let tableDirector = TableDirector(tableView: tableView, shouldUseAutomaticCellRegistration: false) +``` +and register your cell manually. + +# Installation + +## CocoaPods To integrate TableKit into your Xcode project using CocoaPods, specify it in your `Podfile`: ```ruby pod 'TableKit' ``` -#### Carthage +## Carthage Add the line `github "maxsokolov/tablekit"` to your `Cartfile`. -#### Manual +## Manual Clone the repo and drag files from `Sources` folder into your Xcode project. -## Requirements +# Requirements - iOS 8.0+ - Xcode 7.0+ -## License +# License TableKit is available under the MIT license. See LICENSE for details. \ No newline at end of file diff --git a/Sources/ConfigurableCell.swift b/Sources/ConfigurableCell.swift index 6c37e1f..18035ff 100644 --- a/Sources/ConfigurableCell.swift +++ b/Sources/ConfigurableCell.swift @@ -20,39 +20,36 @@ import UIKit -public protocol ReusableCell { - - static func reusableIdentifier() -> String - static func nib() -> UINib? -} +public protocol ConfigurableCell { -public protocol ConfigurableCell: ReusableCell { - associatedtype T - - static func estimatedHeight() -> CGFloat? - static func defaultHeight() -> CGFloat? - func configure(with _: T) -} -public extension ReusableCell where Self: UITableViewCell { - - static func reusableIdentifier() -> String { - return String(self) - } - - static func nib() -> UINib? { - return nil - } + static var reuseIdentifier: String { get } + static var estimatedHeight: CGFloat? { get } + static var defaultHeight: CGFloat? { get } + + func configure(with _: T) } public extension ConfigurableCell where Self: UITableViewCell { - static func estimatedHeight() -> CGFloat? { - return UITableViewAutomaticDimension + static var reuseIdentifier: String { + get { + return String(self) + } + } - static func defaultHeight() -> CGFloat? { - return nil + static var estimatedHeight: CGFloat? { + get { + return UITableViewAutomaticDimension + } + + } + + static var defaultHeight: CGFloat? { + get { + return nil + } } } \ No newline at end of file diff --git a/Sources/HeightStrategy.swift b/Sources/HeightStrategy.swift index 7723bdd..66eec6f 100644 --- a/Sources/HeightStrategy.swift +++ b/Sources/HeightStrategy.swift @@ -49,7 +49,7 @@ public class PrototypeHeightStrategy: CellHeightCalculatable { return height } - guard let cell = tableView.dequeueReusableCellWithIdentifier(row.reusableIdentifier) else { return 0 } + guard let cell = tableView.dequeueReusableCellWithIdentifier(row.reuseIdentifier) else { return 0 } cell.bounds = CGRectMake(0, 0, tableView.bounds.size.width, cell.bounds.height) diff --git a/Sources/TableCellManager.swift b/Sources/TableCellManager.swift index 5cc29e8..891266e 100644 --- a/Sources/TableCellManager.swift +++ b/Sources/TableCellManager.swift @@ -20,25 +20,25 @@ import UIKit -public class TableCellManager { +class TableCellManager { private var registeredIds = Set() private weak var tableView: UITableView? - public init(tableView: UITableView?) { + init(tableView: UITableView?) { self.tableView = tableView } - public func register(cellType cellType: AnyClass, forReusableCellIdentifier reusableIdentifier: String) { + func register(cellType cellType: AnyClass, forCellReuseIdentifier reuseIdentifier: String) { - if registeredIds.contains(reusableIdentifier) { + if registeredIds.contains(reuseIdentifier) { return } // check if cell is already registered, probably cell has been registered by storyboard - if tableView?.dequeueReusableCellWithIdentifier(reusableIdentifier) != nil { + if tableView?.dequeueReusableCellWithIdentifier(reuseIdentifier) != nil { - registeredIds.insert(reusableIdentifier) + registeredIds.insert(reuseIdentifier) return } @@ -46,13 +46,13 @@ public class TableCellManager { // we hope that cell's xib file has name that equals to cell's class name // in that case we could register nib - if let _ = bundle.pathForResource(reusableIdentifier, ofType: "nib") { - tableView?.registerNib(UINib(nibName: reusableIdentifier, bundle: bundle), forCellReuseIdentifier: reusableIdentifier) + if let _ = bundle.pathForResource(reuseIdentifier, ofType: "nib") { + tableView?.registerNib(UINib(nibName: reuseIdentifier, bundle: bundle), forCellReuseIdentifier: reuseIdentifier) // otherwise, register cell class } else { - tableView?.registerClass(cellType, forCellReuseIdentifier: reusableIdentifier) + tableView?.registerClass(cellType, forCellReuseIdentifier: reuseIdentifier) } - registeredIds.insert(reusableIdentifier) + registeredIds.insert(reuseIdentifier) } } \ No newline at end of file diff --git a/Sources/TableDirector.swift b/Sources/TableDirector.swift index 6794b24..ff6fd3f 100644 --- a/Sources/TableDirector.swift +++ b/Sources/TableDirector.swift @@ -123,9 +123,9 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate let row = sections[indexPath.section].items[indexPath.row] - cellManager?.register(cellType: row.cellType, forReusableCellIdentifier: row.reusableIdentifier) + cellManager?.register(cellType: row.cellType, forCellReuseIdentifier: row.reuseIdentifier) - let cell = tableView.dequeueReusableCellWithIdentifier(row.reusableIdentifier, forIndexPath: indexPath) + let cell = tableView.dequeueReusableCellWithIdentifier(row.reuseIdentifier, forIndexPath: indexPath) if cell.frame.size.width != tableView.frame.size.width { cell.frame = CGRectMake(0, 0, tableView.frame.size.width, cell.frame.size.height) diff --git a/Sources/TableRow.swift b/Sources/TableRow.swift index d4f56e3..b3cd9d1 100644 --- a/Sources/TableRow.swift +++ b/Sources/TableRow.swift @@ -38,7 +38,7 @@ public protocol RowHashable { public protocol Row: RowConfigurable, RowActionable, RowHashable { - var reusableIdentifier: String { get } + var reuseIdentifier: String { get } var cellType: AnyClass { get } var estimatedHeight: CGFloat? { get } @@ -54,16 +54,16 @@ public class TableRow String { - return TestTableViewCellOptions.ReusableIdentifier - } - - static func estimatedHeight() -> Float { + static var estimatedHeight: CGFloat? { return TestTableViewCellOptions.EstimatedHeight } + + static var reuseIdentifier: String { + return TestTableViewCellOptions.ReusableIdentifier + } func configure(with item: T) { textLabel?.text = item.title