diff --git a/Demo/Classes/Presentation/Controllers/AutolayoutCellsController.swift b/Demo/Classes/Presentation/Controllers/AutolayoutCellsController.swift
index 522ea56..618e02a 100644
--- a/Demo/Classes/Presentation/Controllers/AutolayoutCellsController.swift
+++ b/Demo/Classes/Presentation/Controllers/AutolayoutCellsController.swift
@@ -14,7 +14,6 @@ class AutolayoutCellsController: UIViewController {
@IBOutlet weak var tableView: UITableView! {
didSet {
tableDirector = TableDirector(tableView: tableView)
- tableDirector.register(ConfigurableTableViewCell.self)
}
}
var tableDirector: TableDirector!
diff --git a/Demo/Classes/Presentation/Controllers/MainController.swift b/Demo/Classes/Presentation/Controllers/MainController.swift
index 5a286a1..cea1b05 100644
--- a/Demo/Classes/Presentation/Controllers/MainController.swift
+++ b/Demo/Classes/Presentation/Controllers/MainController.swift
@@ -14,7 +14,6 @@ class MainController: UIViewController {
@IBOutlet weak var tableView: UITableView! {
didSet {
tableDirector = TableDirector(tableView: tableView)
- tableDirector.register(ConfigurableTableViewCell.self)
}
}
var tableDirector: TableDirector!
diff --git a/Demo/Classes/Presentation/Controllers/NibCellsController.swift b/Demo/Classes/Presentation/Controllers/NibCellsController.swift
index c6d69f5..5309ebf 100644
--- a/Demo/Classes/Presentation/Controllers/NibCellsController.swift
+++ b/Demo/Classes/Presentation/Controllers/NibCellsController.swift
@@ -19,7 +19,6 @@ class NibCellsController: UITableViewController {
title = "Nib cells"
tableDirector = TableDirector(tableView: tableView)
- tableDirector.register(NibTableViewCell.self)
let numbers = [1000, 2000, 3000, 4000, 5000]
diff --git a/Demo/Classes/Presentation/Views/AutolayoutTableViewCell.swift b/Demo/Classes/Presentation/Views/AutolayoutTableViewCell.swift
index 455f618..4e626f1 100644
--- a/Demo/Classes/Presentation/Views/AutolayoutTableViewCell.swift
+++ b/Demo/Classes/Presentation/Views/AutolayoutTableViewCell.swift
@@ -25,7 +25,7 @@ class AutolayoutTableViewCell: UITableViewCell, ConfigurableCell {
subtitleLabel.text = LoremIpsumBody
}
- static func estimatedHeight() -> CGFloat {
+ static func estimatedHeight() -> CGFloat? {
return 500
}
}
\ No newline at end of file
diff --git a/README.md b/README.md
index 60a5329..327826e 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
-
+
@@ -17,6 +17,7 @@ It hides a complexity of `UITableViewDataSource` and `UITableViewDelegate` metho
- [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] Correctly handles autolayout cells with multiline labels
- [x] Chainable cell actions (select/deselect etc.)
- [x] Support cells created from code, xib, or storyboard
@@ -44,7 +45,6 @@ let section = TableSection(rows: [row1, row2, row3])
And setup your table:
```swift
let tableDirector = TableDirector(tableView: tableView)
-tableDirector.register(StringTableViewCell.self, IntTableViewCell.self, FloatTableViewCell.self)
tableDirector += section
```
Done. Your table is ready. You may want to look at your cell. It has to conform to `ConfigurableCell` protocol:
diff --git a/Sources/ConfigurableCell.swift b/Sources/ConfigurableCell.swift
index 473f7cc..efa3fcb 100644
--- a/Sources/ConfigurableCell.swift
+++ b/Sources/ConfigurableCell.swift
@@ -27,10 +27,10 @@ public protocol ReusableCell {
}
public protocol ConfigurableCell: ReusableCell {
-
+
associatedtype T
-
- static func estimatedHeight() -> CGFloat
+
+ static func estimatedHeight() -> CGFloat?
static func defaultHeight() -> CGFloat?
func configure(_: T, isPrototype: Bool)
}
@@ -47,11 +47,11 @@ public extension ReusableCell where Self: UITableViewCell {
}
public extension ConfigurableCell where Self: UITableViewCell {
-
- static func estimatedHeight() -> CGFloat {
+
+ static func estimatedHeight() -> CGFloat? {
return UITableViewAutomaticDimension
}
-
+
static func defaultHeight() -> CGFloat? {
return nil
}
diff --git a/Sources/TableCellManager.swift b/Sources/TableCellManager.swift
new file mode 100644
index 0000000..5cc29e8
--- /dev/null
+++ b/Sources/TableCellManager.swift
@@ -0,0 +1,58 @@
+//
+// Copyright (c) 2015 Max Sokolov https://twitter.com/max_sokolov
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import UIKit
+
+public class TableCellManager {
+
+ private var registeredIds = Set()
+ private weak var tableView: UITableView?
+
+ public init(tableView: UITableView?) {
+ self.tableView = tableView
+ }
+
+ public func register(cellType cellType: AnyClass, forReusableCellIdentifier reusableIdentifier: String) {
+
+ if registeredIds.contains(reusableIdentifier) {
+ return
+ }
+
+ // check if cell is already registered, probably cell has been registered by storyboard
+ if tableView?.dequeueReusableCellWithIdentifier(reusableIdentifier) != nil {
+
+ registeredIds.insert(reusableIdentifier)
+ return
+ }
+
+ let bundle = NSBundle(forClass: cellType)
+
+ // 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)
+ // otherwise, register cell class
+ } else {
+ tableView?.registerClass(cellType, forCellReuseIdentifier: reusableIdentifier)
+ }
+
+ registeredIds.insert(reusableIdentifier)
+ }
+}
\ No newline at end of file
diff --git a/Sources/TableDirector.swift b/Sources/TableDirector.swift
index 710c77a..b34d62c 100644
--- a/Sources/TableDirector.swift
+++ b/Sources/TableDirector.swift
@@ -24,13 +24,14 @@ import UIKit
Responsible for table view's datasource and delegate.
*/
public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
-
+
public private(set) weak var tableView: UITableView?
public private(set) var sections = [TableSection]()
private weak var scrollDelegate: UIScrollViewDelegate?
private var heightStrategy: CellHeightCalculatable?
-
+ private var cellManager: TableCellManager?
+
public var shouldUsePrototypeCellHeightCalculation: Bool = false {
didSet {
if shouldUsePrototypeCellHeightCalculation {
@@ -38,15 +39,23 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate
}
}
}
-
- public init(tableView: UITableView, scrollDelegate: UIScrollViewDelegate? = nil) {
+
+ public var isEmpty: Bool {
+ return sections.isEmpty
+ }
+
+ public init(tableView: UITableView, scrollDelegate: UIScrollViewDelegate? = nil, shouldUseAutomaticCellRegistration: Bool = true) {
super.init()
-
+
+ if shouldUseAutomaticCellRegistration {
+ self.cellManager = TableCellManager(tableView: tableView)
+ }
+
self.scrollDelegate = scrollDelegate
self.tableView = tableView
self.tableView?.delegate = self
self.tableView?.dataSource = self
-
+
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(didReceiveAction), name: TableKitNotifications.CellAction, object: nil)
}
@@ -58,40 +67,16 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate
tableView?.reloadData()
}
- public func register(cells: T.Type...) {
-
- for cell in cells {
-
- if let nib = cell.nib() {
-
- tableView?.registerNib(nib, forCellReuseIdentifier: cell.reusableIdentifier())
- return
-
- } else {
-
- let resource = cell.reusableIdentifier()
- let bundle = NSBundle(forClass: cell)
-
- if let _ = bundle.pathForResource(resource, ofType: "nib") {
- tableView?.registerNib(UINib(nibName: resource, bundle: bundle), forCellReuseIdentifier: cell.reusableIdentifier())
- return
- }
- }
-
- tableView?.registerClass(cell, forCellReuseIdentifier: cell.reusableIdentifier())
- }
- }
-
// MARK: Public
public func invoke(action action: TableRowActionType, cell: UITableViewCell?, indexPath: NSIndexPath) -> Any? {
return sections[indexPath.section].items[indexPath.row].invoke(action, cell: cell, path: indexPath)
}
-
+
public override func respondsToSelector(selector: Selector) -> Bool {
return super.respondsToSelector(selector) || scrollDelegate?.respondsToSelector(selector) == true
}
-
+
public override func forwardingTargetForSelector(selector: Selector) -> AnyObject? {
return scrollDelegate?.respondsToSelector(selector) == true ? scrollDelegate : super.forwardingTargetForSelector(selector)
}
@@ -103,7 +88,7 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate
}
func didReceiveAction(notification: NSNotification) {
-
+
guard let action = notification.object as? TableCellAction, indexPath = tableView?.indexPathForCell(action.cell) else { return }
invoke(action: .custom(action.key), cell: action.cell, indexPath: indexPath)
}
@@ -111,17 +96,17 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate
// MARK: - Height
public func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
-
+
let row = sections[indexPath.section].items[indexPath.row]
- return heightStrategy?.estimatedHeight(row, path: indexPath) ?? row.estimatedHeight
+ return row.estimatedHeight ?? heightStrategy?.estimatedHeight(row, path: indexPath) ?? UITableViewAutomaticDimension
}
public func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
-
+
let row = sections[indexPath.section].items[indexPath.row]
let rowHeight = invoke(action: .height, cell: nil, indexPath: indexPath) as? CGFloat
- return rowHeight ?? heightStrategy?.height(row, path: indexPath) ?? row.defaultHeight
+ return rowHeight ?? row.defaultHeight ?? heightStrategy?.height(row, path: indexPath) ?? UITableViewAutomaticDimension
}
// MARK: UITableViewDataSource - configuration
@@ -135,17 +120,21 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate
}
public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
-
+
let row = sections[indexPath.section].items[indexPath.row]
+
+ cellManager?.register(cellType: row.cellType, forReusableCellIdentifier: row.reusableIdentifier)
+
let cell = tableView.dequeueReusableCellWithIdentifier(row.reusableIdentifier, forIndexPath: indexPath)
if cell.frame.size.width != tableView.frame.size.width {
cell.frame = CGRectMake(0, 0, tableView.frame.size.width, cell.frame.size.height)
cell.layoutIfNeeded()
}
-
+
row.configure(cell, isPrototype: false)
-
+ invoke(action: .configure, cell: cell, indexPath: indexPath)
+
return cell
}
@@ -170,11 +159,15 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate
}
public func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
- return sections[section].headerView?.frame.size.height ?? UITableViewAutomaticDimension
+
+ let section = sections[section]
+ return section.headerHeight ?? section.headerView?.frame.size.height ?? 0
}
public func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
- return sections[section].footerView?.frame.size.height ?? UITableViewAutomaticDimension
+
+ let section = sections[section]
+ return section.footerHeight ?? section.footerView?.frame.size.height ?? 0
}
// MARK: UITableViewDelegate - actions
@@ -201,9 +194,9 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate
public func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return invoke(action: .shouldHighlight, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath) as? Bool ?? true
}
-
+
public func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
-
+
if hasAction(.willSelect, atIndexPath: indexPath) {
return invoke(action: .willSelect, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath) as? NSIndexPath
}
@@ -213,7 +206,7 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate
// MARK: - Sections manipulation -
public func append(section section: TableSection) -> Self {
-
+
append(sections: [section])
return self
}
@@ -238,13 +231,13 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate
}
public func delete(index index: Int) -> Self {
-
+
sections.removeAtIndex(index)
return self
}
public func clear() -> Self {
-
+
sections.removeAll()
return self
}
diff --git a/Sources/TableRow.swift b/Sources/TableRow.swift
index 7c1e70e..db139b5 100644
--- a/Sources/TableRow.swift
+++ b/Sources/TableRow.swift
@@ -21,12 +21,12 @@
import UIKit
public protocol RowConfigurable {
-
+
func configure(cell: UITableViewCell, isPrototype: Bool)
}
public protocol RowActionable {
-
+
func invoke(action: TableRowActionType, cell: UITableViewCell?, path: NSIndexPath) -> Any?
func hasAction(action: TableRowActionType) -> Bool
}
@@ -37,31 +37,37 @@ public protocol RowHashable {
}
public protocol Row: RowConfigurable, RowActionable, RowHashable {
-
+
var reusableIdentifier: String { get }
- var estimatedHeight: CGFloat { get }
- var defaultHeight: CGFloat { get }
+ var cellType: AnyClass { get }
+
+ var estimatedHeight: CGFloat? { get }
+ var defaultHeight: CGFloat? { get }
}
public class TableRow: Row {
-
+
public let item: ItemType
private lazy var actions = [String: TableRowAction]()
public var hashValue: Int {
return ObjectIdentifier(self).hashValue
}
-
+
public var reusableIdentifier: String {
return CellType.reusableIdentifier()
}
- public var estimatedHeight: CGFloat {
+ public var estimatedHeight: CGFloat? {
return CellType.estimatedHeight()
}
-
- public var defaultHeight: CGFloat {
- return CellType.defaultHeight() ?? UITableViewAutomaticDimension
+
+ public var defaultHeight: CGFloat? {
+ return CellType.defaultHeight()
+ }
+
+ public var cellType: AnyClass {
+ return CellType.self
}
public init(item: ItemType, actions: [TableRowAction]? = nil) {
@@ -69,7 +75,7 @@ public class TableRow Any? {
return actions[action.key]?.invoke(item: item, cell: cell, path: path)
}
-
+
public func hasAction(action: TableRowActionType) -> Bool {
return actions[action.key] != nil
}
@@ -89,7 +95,7 @@ public class TableRow) -> Self {
-
+
actions[action.type.key] = action
return self
}
diff --git a/Sources/TableRowAction.swift b/Sources/TableRowAction.swift
index cd3f0c6..5c639a8 100644
--- a/Sources/TableRowAction.swift
+++ b/Sources/TableRowAction.swift
@@ -29,8 +29,9 @@ public enum TableRowActionType {
case willDisplay
case shouldHighlight
case height
+ case configure
case custom(String)
-
+
var key: String {
switch (self) {
diff --git a/Sources/TableSection.swift b/Sources/TableSection.swift
index e09078e..e42b306 100644
--- a/Sources/TableSection.swift
+++ b/Sources/TableSection.swift
@@ -21,44 +21,51 @@
import UIKit
public class TableSection {
-
+
weak var tableDirector: TableDirector?
-
+
public private(set) var items = [Row]()
-
+
public var headerTitle: String?
public var footerTitle: String?
-
- public private(set) var headerView: UIView?
- public private(set) var footerView: UIView?
-
+
+ public var headerView: UIView?
+ public var footerView: UIView?
+
+ public var headerHeight: CGFloat? = nil
+ public var footerHeight: CGFloat? = nil
+
public var numberOfRows: Int {
return items.count
}
+ public var isEmpty: Bool {
+ return items.isEmpty
+ }
+
public init(rows: [Row]? = nil) {
-
+
if let initialRows = rows {
items.appendContentsOf(initialRows)
}
}
-
+
public convenience init(headerTitle: String?, footerTitle: String?, rows: [Row]? = nil) {
self.init(rows: rows)
self.headerTitle = headerTitle
self.footerTitle = footerTitle
}
-
+
public convenience init(headerView: UIView?, footerView: UIView?, rows: [Row]? = nil) {
self.init(rows: rows)
self.headerView = headerView
self.footerView = footerView
}
-
+
// MARK: - Public -
-
+
public func clear() {
items.removeAll()
}
@@ -70,12 +77,12 @@ public class TableSection {
public func append(rows rows: [Row]) {
items.appendContentsOf(rows)
}
-
+
public func insert(row row: Row, atIndex index: Int) {
items.insert(row, atIndex: index)
}
-
- public func delete(index: Int) {
+
+ public func delete(index index: Int) {
items.removeAtIndex(index)
}
}
\ No newline at end of file
diff --git a/TableKit.podspec b/TableKit.podspec
index 6835141..85230b7 100644
--- a/TableKit.podspec
+++ b/TableKit.podspec
@@ -2,7 +2,7 @@ Pod::Spec.new do |s|
s.name = 'TableKit'
s.module_name = 'TableKit'
- s.version = '0.9.0'
+ s.version = '0.9.1'
s.homepage = 'https://github.com/maxsokolov/TableKit'
s.summary = 'Type-safe declarative table views. Swift 2.2 is required.'
diff --git a/TableKit.xcodeproj/project.pbxproj b/TableKit.xcodeproj/project.pbxproj
index b72ffe3..32e8324 100644
--- a/TableKit.xcodeproj/project.pbxproj
+++ b/TableKit.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
+ 50CF6E6B1D6704FE004746FF /* TableCellManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF6E6A1D6704FE004746FF /* TableCellManager.swift */; };
DA9EA7AF1D0EC2C90021F650 /* ConfigurableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */; };
DA9EA7B01D0EC2C90021F650 /* HeightStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A71D0EC2C90021F650 /* HeightStrategy.swift */; };
DA9EA7B11D0EC2C90021F650 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA7A81D0EC2C90021F650 /* Operators.swift */; };
@@ -30,6 +31,7 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
+ 50CF6E6A1D6704FE004746FF /* TableCellManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableCellManager.swift; sourceTree = ""; };
DA9EA7561D0B679A0021F650 /* TableKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TableKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DA9EA7A61D0EC2C90021F650 /* ConfigurableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurableCell.swift; sourceTree = ""; };
DA9EA7A71D0EC2C90021F650 /* HeightStrategy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeightStrategy.swift; sourceTree = ""; };
@@ -87,6 +89,7 @@
isa = PBXGroup;
children = (
DA9EA7AA1D0EC2C90021F650 /* TableDirector.swift */,
+ 50CF6E6A1D6704FE004746FF /* TableCellManager.swift */,
DA9EA7AB1D0EC2C90021F650 /* TableRow.swift */,
DA9EA7AC1D0EC2C90021F650 /* TableRowAction.swift */,
DA9EA7AE1D0EC2C90021F650 /* TableSection.swift */,
@@ -222,6 +225,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 50CF6E6B1D6704FE004746FF /* TableCellManager.swift in Sources */,
DA9EA7AF1D0EC2C90021F650 /* ConfigurableCell.swift in Sources */,
DA9EA7B31D0EC2C90021F650 /* TableDirector.swift in Sources */,
DA9EA7B71D0EC2C90021F650 /* TableSection.swift in Sources */,
diff --git a/Tests/TableKitTests.swift b/Tests/TableKitTests.swift
index 7a601e1..767f9d0 100644
--- a/Tests/TableKitTests.swift
+++ b/Tests/TableKitTests.swift
@@ -28,7 +28,6 @@ class TestController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableDirector = TableDirector(tableView: tableView)
- tableDirector.register(TestTableViewCell.self)
}
}