From 8b5ad2fcd1a4c50fb56dcb1ce0dfaceaa8862e15 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Mon, 13 Jun 2016 16:07:12 +0300 Subject: [PATCH 01/29] update readme --- README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a691198..c3c8d7e 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ #TableKit

-Build Status -Swift 2.2 compatible -Platform iOS -CocoaPods compatible -License: MIT + Build Status + Swift 2.2 compatible + Carthage compatible + CocoaPods compatible + Platform iOS + License: MIT

TableKit is a super lightweight yet powerful generic library that allows you to build complex table views in a declarative type-safe manner. @@ -113,6 +114,8 @@ pod 'TableKit' ``` #### Carthage Add the line `github "maxsokolov/tablekit"` to your `Cartfile` +#### Manual +Clone the repo and drag files from `Sources` folder into your Xcode project. ## Requirements From 1ed3db1cb1ab7741bbe8b561a358c75e299eb642 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Mon, 13 Jun 2016 16:21:16 +0300 Subject: [PATCH 02/29] update readme --- Demo/Classes/Presentation/Controllers/MainController.swift | 4 ++++ README.md | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Demo/Classes/Presentation/Controllers/MainController.swift b/Demo/Classes/Presentation/Controllers/MainController.swift index d2b4cab..b5c4881 100644 --- a/Demo/Classes/Presentation/Controllers/MainController.swift +++ b/Demo/Classes/Presentation/Controllers/MainController.swift @@ -41,6 +41,10 @@ class MainController: UIViewController { row1 .addAction(TableRowAction(.shouldHighlight) { (data) -> Bool in + data.cell + data.item + data.path + print("1") return false diff --git a/README.md b/README.md index c3c8d7e..ab571c6 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,10 @@ It nice to have some actions that related to your cells: ```swift let action = TableRowAction(.click) { (data) in + // you could access any useful information that relates to the action + // data.cell - StringTableViewCell? + // data.item - String + // data.path - NSIndexPath } let row = TableRow(item: "some", actions: [action]) @@ -113,7 +117,7 @@ use_frameworks! pod 'TableKit' ``` #### Carthage -Add the line `github "maxsokolov/tablekit"` to your `Cartfile` +Add the line `github "maxsokolov/tablekit"` to your `Cartfile`. #### Manual Clone the repo and drag files from `Sources` folder into your Xcode project. From 3e4f7d81ff6f52a57d07d10b72abe861416036d9 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Mon, 13 Jun 2016 16:34:57 +0300 Subject: [PATCH 03/29] remove configure action --- Demo/Classes/Presentation/Controllers/MainController.swift | 6 +----- Sources/TableRowAction.swift | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Demo/Classes/Presentation/Controllers/MainController.swift b/Demo/Classes/Presentation/Controllers/MainController.swift index b5c4881..bec8ab6 100644 --- a/Demo/Classes/Presentation/Controllers/MainController.swift +++ b/Demo/Classes/Presentation/Controllers/MainController.swift @@ -40,11 +40,7 @@ class MainController: UIViewController { row1 .addAction(TableRowAction(.shouldHighlight) { (data) -> Bool in - - data.cell - data.item - data.path - + print("1") return false diff --git a/Sources/TableRowAction.swift b/Sources/TableRowAction.swift index d4d10b7..cd3f0c6 100644 --- a/Sources/TableRowAction.swift +++ b/Sources/TableRowAction.swift @@ -26,7 +26,6 @@ public enum TableRowActionType { case select case deselect case willSelect - case configure case willDisplay case shouldHighlight case height From 7cc572c7aafbc658db198a60833c2d278b501eaa Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Tue, 14 Jun 2016 16:07:49 +0300 Subject: [PATCH 04/29] update readme --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ab571c6..170adf3 100644 --- a/README.md +++ b/README.md @@ -22,15 +22,14 @@ It hides a complexity of `UITableViewDataSource` and `UITableViewDelegate` metho - [x] Automatic xib/classes registration - [x] No need to subclass - [x] Extensibility -- [x] Tests -## Usage +## Getting Started Create your rows: ```swift let row1 = TableRow(item: "1") -let row2 = TableRow(item: 2) -let row3 = TableRow(item: 3.0) +let row2 = TableRow(item: 2) +let row3 = TableRow(item: 3.0) ``` Put rows into section: ```swift From cc75e84f5839697a264c679a22b4352bda9a6675 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Tue, 14 Jun 2016 20:47:39 +0300 Subject: [PATCH 05/29] try height strategy --- Sources/HeightStrategy.swift | 12 ++++++------ Sources/TableDirector.swift | 8 +++++++- Sources/TableRow.swift | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Sources/HeightStrategy.swift b/Sources/HeightStrategy.swift index c34bd40..9072767 100644 --- a/Sources/HeightStrategy.swift +++ b/Sources/HeightStrategy.swift @@ -20,25 +20,25 @@ import UIKit -public protocol HeightStrategy { +public protocol HeightCalculatingStrategy { var tableView: UITableView? { get set } - func height(item: Item, indexPath: NSIndexPath, cell: Cell.Type) -> CGFloat + func height(indexPath: NSIndexPath, reusableIdentifier: String, configure: (cell: UITableViewCell) -> Void) -> CGFloat } -public class PrototypeHeightStrategy: HeightStrategy { +public class PrototypeHeightStrategy { public weak var tableView: UITableView? private var cachedHeights = [Int: CGFloat]() - public func height(item: Item, indexPath: NSIndexPath, cell: Cell.Type) -> CGFloat { + public func height(indexPath: NSIndexPath, reusableIdentifier: String, configure: (cell: UITableViewCell) -> Void) -> CGFloat { - guard let cell = tableView?.dequeueReusableCellWithIdentifier(Cell.reusableIdentifier()) as? Cell else { return 0 } + guard let cell = tableView?.dequeueReusableCellWithIdentifier(reusableIdentifier) else { return 0 } cell.bounds = CGRectMake(0, 0, tableView?.bounds.size.width ?? 0, cell.bounds.height) - cell.configure(item) + configure(cell: cell) cell.setNeedsLayout() cell.layoutIfNeeded() diff --git a/Sources/TableDirector.swift b/Sources/TableDirector.swift index 03545f6..3e8075f 100644 --- a/Sources/TableDirector.swift +++ b/Sources/TableDirector.swift @@ -28,6 +28,7 @@ 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: HeightCalculatingStrategy? public init(tableView: UITableView, scrollDelegate: UIScrollViewDelegate? = nil) { super.init() @@ -81,7 +82,12 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate } public func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { - return sections[indexPath.section].items[indexPath.row].defaultHeight + + let row = sections[indexPath.section].items[indexPath.row] + + return heightStrategy?.height(indexPath, reusableIdentifier: row.reusableIdentifier, configure: { (cell) in + row.configure(cell) + }) ?? sections[indexPath.section].items[indexPath.row].defaultHeight } // MARK: UITableViewDataSource - configuration diff --git a/Sources/TableRow.swift b/Sources/TableRow.swift index 2b06615..62fd90b 100644 --- a/Sources/TableRow.swift +++ b/Sources/TableRow.swift @@ -60,7 +60,7 @@ public class TableRow Date: Tue, 14 Jun 2016 23:50:30 +0300 Subject: [PATCH 06/29] add RowHashable protocol --- .../Presentation/Controllers/MainController.swift | 4 +--- Sources/HeightStrategy.swift | 2 +- Sources/TableRow.swift | 11 ++++++++++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Demo/Classes/Presentation/Controllers/MainController.swift b/Demo/Classes/Presentation/Controllers/MainController.swift index bec8ab6..91b855d 100644 --- a/Demo/Classes/Presentation/Controllers/MainController.swift +++ b/Demo/Classes/Presentation/Controllers/MainController.swift @@ -35,9 +35,7 @@ class MainController: UIViewController { let row1 = TableRow(item: "1") let row2 = TableRow(item: "2") let row3 = TableRow(item: "3", actions: [a]) - - - + row1 .addAction(TableRowAction(.shouldHighlight) { (data) -> Bool in diff --git a/Sources/HeightStrategy.swift b/Sources/HeightStrategy.swift index 9072767..72eef2e 100644 --- a/Sources/HeightStrategy.swift +++ b/Sources/HeightStrategy.swift @@ -27,7 +27,7 @@ public protocol HeightCalculatingStrategy { func height(indexPath: NSIndexPath, reusableIdentifier: String, configure: (cell: UITableViewCell) -> Void) -> CGFloat } -public class PrototypeHeightStrategy { +public class PrototypeHeightStrategy: HeightCalculatingStrategy { public weak var tableView: UITableView? private var cachedHeights = [Int: CGFloat]() diff --git a/Sources/TableRow.swift b/Sources/TableRow.swift index 62fd90b..e4b3fbb 100644 --- a/Sources/TableRow.swift +++ b/Sources/TableRow.swift @@ -31,7 +31,12 @@ public protocol RowActionable { func hasAction(action: TableRowActionType) -> Bool } -public protocol Row: RowConfigurable, RowActionable { +public protocol RowHashable { + + var hashValue: Int { get } +} + +public protocol Row: RowConfigurable, RowActionable, RowHashable { var reusableIdentifier: String { get } var estimatedHeight: CGFloat { get } @@ -42,6 +47,10 @@ public class TableRow]() + + public var hashValue: Int { + return ObjectIdentifier(self).hashValue + } public var reusableIdentifier: String { return CellType.reusableIdentifier() From 9d0c278a347d1b1daa43767772b6e18d8728b9a8 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Wed, 15 Jun 2016 01:05:04 +0300 Subject: [PATCH 07/29] add shouldUsePrototypeCellHeightCalculation property --- .../Controllers/MainController.swift | 1 + .../Views/StoryboardImageTableViewCell.swift | 4 +-- Sources/HeightStrategy.swift | 33 ++++++++++++++----- Sources/TableDirector.swift | 20 +++++++---- 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/Demo/Classes/Presentation/Controllers/MainController.swift b/Demo/Classes/Presentation/Controllers/MainController.swift index 91b855d..4cc8203 100644 --- a/Demo/Classes/Presentation/Controllers/MainController.swift +++ b/Demo/Classes/Presentation/Controllers/MainController.swift @@ -14,6 +14,7 @@ class MainController: UIViewController { @IBOutlet weak var tableView: UITableView! { didSet { tableDirector = TableDirector(tableView: tableView) + tableDirector.shouldUsePrototypeCellHeightCalculation = true } } var tableDirector: TableDirector! diff --git a/Demo/Classes/Presentation/Views/StoryboardImageTableViewCell.swift b/Demo/Classes/Presentation/Views/StoryboardImageTableViewCell.swift index 4cd5631..a466f9a 100644 --- a/Demo/Classes/Presentation/Views/StoryboardImageTableViewCell.swift +++ b/Demo/Classes/Presentation/Views/StoryboardImageTableViewCell.swift @@ -30,7 +30,7 @@ class StoryboardImageTableViewCell: UITableViewCell, ConfigurableCell { override func layoutSubviews() { super.layoutSubviews() - //contentView.layoutIfNeeded() - //subtitleLabel.preferredMaxLayoutWidth = subtitleLabel.bounds.size.width + contentView.layoutIfNeeded() + subtitleLabel.preferredMaxLayoutWidth = subtitleLabel.bounds.size.width } } \ No newline at end of file diff --git a/Sources/HeightStrategy.swift b/Sources/HeightStrategy.swift index 72eef2e..469542e 100644 --- a/Sources/HeightStrategy.swift +++ b/Sources/HeightStrategy.swift @@ -20,29 +20,46 @@ import UIKit -public protocol HeightCalculatingStrategy { +public protocol CellHeightCalculatable { var tableView: UITableView? { get set } - func height(indexPath: NSIndexPath, reusableIdentifier: String, configure: (cell: UITableViewCell) -> Void) -> CGFloat + func height(row: Row, path: NSIndexPath) -> CGFloat + func estimatedHeight(row: Row, path: NSIndexPath) -> CGFloat } -public class PrototypeHeightStrategy: HeightCalculatingStrategy { +public class PrototypeHeightStrategy: CellHeightCalculatable { public weak var tableView: UITableView? private var cachedHeights = [Int: CGFloat]() - public func height(indexPath: NSIndexPath, reusableIdentifier: String, configure: (cell: UITableViewCell) -> Void) -> CGFloat { + init(tableView: UITableView?) { + self.tableView = tableView + } + + public func height(row: Row, path: NSIndexPath) -> CGFloat { - guard let cell = tableView?.dequeueReusableCellWithIdentifier(reusableIdentifier) else { return 0 } + if let height = cachedHeights[row.hashValue] { + return height + } + + guard let cell = tableView?.dequeueReusableCellWithIdentifier(row.reusableIdentifier) else { return 0 } cell.bounds = CGRectMake(0, 0, tableView?.bounds.size.width ?? 0, cell.bounds.height) - - configure(cell: cell) + + row.configure(cell) cell.setNeedsLayout() cell.layoutIfNeeded() - return cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1 + let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1 + + cachedHeights[row.hashValue] = height + + return height + } + + public func estimatedHeight(row: Row, path: NSIndexPath) -> CGFloat { + return UITableViewAutomaticDimension } } \ No newline at end of file diff --git a/Sources/TableDirector.swift b/Sources/TableDirector.swift index 3e8075f..42f018c 100644 --- a/Sources/TableDirector.swift +++ b/Sources/TableDirector.swift @@ -27,8 +27,17 @@ 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: HeightCalculatingStrategy? + private var heightStrategy: CellHeightCalculatable? + + public var shouldUsePrototypeCellHeightCalculation: Bool = false { + didSet { + if shouldUsePrototypeCellHeightCalculation { + heightStrategy = PrototypeHeightStrategy(tableView: tableView) + } + } + } public init(tableView: UITableView, scrollDelegate: UIScrollViewDelegate? = nil) { super.init() @@ -78,16 +87,15 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate // MARK: - Height public func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { - return sections[indexPath.section].items[indexPath.row].estimatedHeight + + let row = sections[indexPath.section].items[indexPath.row] + return heightStrategy?.estimatedHeight(row, path: indexPath) ?? row.estimatedHeight } public func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { let row = sections[indexPath.section].items[indexPath.row] - - return heightStrategy?.height(indexPath, reusableIdentifier: row.reusableIdentifier, configure: { (cell) in - row.configure(cell) - }) ?? sections[indexPath.section].items[indexPath.row].defaultHeight + return heightStrategy?.height(row, path: indexPath) ?? row.defaultHeight } // MARK: UITableViewDataSource - configuration From 98c481be71c12682ecf341968cb6ae64f39f96f2 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Wed, 15 Jun 2016 01:21:12 +0300 Subject: [PATCH 08/29] handle separator height --- Demo/Classes/Presentation/Controllers/MainController.swift | 3 +-- README.md | 2 +- Sources/HeightStrategy.swift | 5 +++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Demo/Classes/Presentation/Controllers/MainController.swift b/Demo/Classes/Presentation/Controllers/MainController.swift index 4cc8203..e8812d1 100644 --- a/Demo/Classes/Presentation/Controllers/MainController.swift +++ b/Demo/Classes/Presentation/Controllers/MainController.swift @@ -21,8 +21,7 @@ class MainController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - - + let a = TableRowAction(.click) { (data) in diff --git a/README.md b/README.md index 170adf3..0f02666 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ It hides a complexity of `UITableViewDataSource` and `UITableViewDelegate` metho - [x] Correctly handles autolayout cells with multiline labels - [x] Chainable cell actions (select/deselect etc.) - [x] Support cells created from code, xib, or storyboard -- [x] Automatic xib/classes registration +- [x] Support different cells height calculation strategies - [x] No need to subclass - [x] Extensibility diff --git a/Sources/HeightStrategy.swift b/Sources/HeightStrategy.swift index 469542e..43243ff 100644 --- a/Sources/HeightStrategy.swift +++ b/Sources/HeightStrategy.swift @@ -32,6 +32,7 @@ public class PrototypeHeightStrategy: CellHeightCalculatable { public weak var tableView: UITableView? private var cachedHeights = [Int: CGFloat]() + private var separatorHeight = 1 / UIScreen.mainScreen().scale init(tableView: UITableView?) { self.tableView = tableView @@ -51,8 +52,8 @@ public class PrototypeHeightStrategy: CellHeightCalculatable { cell.setNeedsLayout() cell.layoutIfNeeded() - - let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1 + + let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + (tableView?.separatorStyle != .None ? separatorHeight : 0) cachedHeights[row.hashValue] = height From a2f8bdde307b91a77f429ef6619017c1bd539ef2 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Wed, 15 Jun 2016 20:42:10 +0300 Subject: [PATCH 09/29] add isPrototype property --- .../Presentation/Views/StoryboardImageTableViewCell.swift | 2 +- .../Presentation/Views/StoryboardTableViewCell.swift | 2 +- Sources/ConfigurableCell.swift | 2 +- Sources/HeightStrategy.swift | 2 +- Sources/TableDirector.swift | 2 +- Sources/TableRow.swift | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Demo/Classes/Presentation/Views/StoryboardImageTableViewCell.swift b/Demo/Classes/Presentation/Views/StoryboardImageTableViewCell.swift index a466f9a..6d0df90 100644 --- a/Demo/Classes/Presentation/Views/StoryboardImageTableViewCell.swift +++ b/Demo/Classes/Presentation/Views/StoryboardImageTableViewCell.swift @@ -17,7 +17,7 @@ class StoryboardImageTableViewCell: UITableViewCell, ConfigurableCell { @IBOutlet var subtitleLabel: UILabel! @IBOutlet var customImageView: UIImageView! - func configure(string: T) { + func configure(string: T, isPrototype: Bool) { titleLabel.text = string subtitleLabel.text = "Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.Copyright © 2016 Tablet. All rights reserved.1" diff --git a/Demo/Classes/Presentation/Views/StoryboardTableViewCell.swift b/Demo/Classes/Presentation/Views/StoryboardTableViewCell.swift index 8180860..a8b84c0 100644 --- a/Demo/Classes/Presentation/Views/StoryboardTableViewCell.swift +++ b/Demo/Classes/Presentation/Views/StoryboardTableViewCell.swift @@ -13,7 +13,7 @@ class StoryboardTableViewCell: UITableViewCell, ConfigurableCell { typealias T = String - func configure(value: T) { + func configure(value: T, isPrototype: Bool) { textLabel?.text = value } } \ No newline at end of file diff --git a/Sources/ConfigurableCell.swift b/Sources/ConfigurableCell.swift index 4127139..3e42f17 100644 --- a/Sources/ConfigurableCell.swift +++ b/Sources/ConfigurableCell.swift @@ -27,7 +27,7 @@ public protocol ConfigurableCell { static func reusableIdentifier() -> String static func estimatedHeight() -> CGFloat static func defaultHeight() -> CGFloat? - func configure(_: T) + func configure(_: T, isPrototype: Bool) } public extension ConfigurableCell where Self: UITableViewCell { diff --git a/Sources/HeightStrategy.swift b/Sources/HeightStrategy.swift index 43243ff..084018c 100644 --- a/Sources/HeightStrategy.swift +++ b/Sources/HeightStrategy.swift @@ -48,7 +48,7 @@ public class PrototypeHeightStrategy: CellHeightCalculatable { cell.bounds = CGRectMake(0, 0, tableView?.bounds.size.width ?? 0, cell.bounds.height) - row.configure(cell) + row.configure(cell, isPrototype: true) cell.setNeedsLayout() cell.layoutIfNeeded() diff --git a/Sources/TableDirector.swift b/Sources/TableDirector.swift index 42f018c..72b8fb1 100644 --- a/Sources/TableDirector.swift +++ b/Sources/TableDirector.swift @@ -118,7 +118,7 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate cell.layoutIfNeeded() } - row.configure(cell) + row.configure(cell, isPrototype: false) return cell } diff --git a/Sources/TableRow.swift b/Sources/TableRow.swift index e4b3fbb..ff33a2e 100644 --- a/Sources/TableRow.swift +++ b/Sources/TableRow.swift @@ -22,7 +22,7 @@ import UIKit public protocol RowConfigurable { - func configure(cell: UITableViewCell) + func configure(cell: UITableViewCell, isPrototype: Bool) } public protocol RowActionable { @@ -72,8 +72,8 @@ public class TableRow Date: Wed, 15 Jun 2016 20:42:53 +0300 Subject: [PATCH 10/29] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f02666..3b56f80 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ class StringTableViewCell: UITableViewCell, ConfigurableCell { typealias T = String - func configure(string: T) { + func configure(string: T, isPrototype: Bool) { titleLabel.text = string } From 1bd049e276042b72b3d434c62364843082b915ae Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Wed, 15 Jun 2016 20:56:17 +0300 Subject: [PATCH 11/29] add delete and insert methods --- .../Controllers/MainController.swift | 41 ------------------- Sources/TableSection.swift | 10 ++++- 2 files changed, 9 insertions(+), 42 deletions(-) diff --git a/Demo/Classes/Presentation/Controllers/MainController.swift b/Demo/Classes/Presentation/Controllers/MainController.swift index e8812d1..3569741 100644 --- a/Demo/Classes/Presentation/Controllers/MainController.swift +++ b/Demo/Classes/Presentation/Controllers/MainController.swift @@ -46,52 +46,11 @@ class MainController: UIViewController { .addAction(TableRowAction(.click) { (data) in print("2") - - }) - let section = TableSection() section.append(builder: b) - //let section = TableSection(headerTitle: "", footerTitle: "", rows: [row1, row2, row3]) - tableDirector += [section] - - //tableDirector.append(section: section) - - - - - - - /*rowBuilder - .addAction(TableRowAction(type: .Click) { (data) in - - - }) - - rowBuilder - .delete(indexes: [0], animated: .None) - .insert(["2"], atIndex: 0, animated: .None) - .update(index: 0, item: "", animated: .None) - .move([1, 2], toIndexes: [3, 4]) - - - - //tableView.moveRowAtIndexPath(<#T##indexPath: NSIndexPath##NSIndexPath#>, toIndexPath: <#T##NSIndexPath#>) - //tableView.deleteRowsAtIndexPaths(<#T##indexPaths: [NSIndexPath]##[NSIndexPath]#>, withRowAnimation: <#T##UITableViewRowAnimation#>) - //tableView.insertRowsAtIndexPaths(<#T##indexPaths: [NSIndexPath]##[NSIndexPath]#>, withRowAnimation: <#T##UITableViewRowAnimation#>) - //tableView.reloadRowsAtIndexPaths(<#T##indexPaths: [NSIndexPath]##[NSIndexPath]#>, withRowAnimation: <#T##UITableViewRowAnimation#>) - - //tableView.moveSection(0, toSection: 0) - //tableView.reloadSections([], withRowAnimation: .None) - //tableView.deleteSections([], withRowAnimation: .None) - //tableView.insertSections([], withRowAnimation: .None) - - //tableDirector.performBatchUpdates { - //}*/ - - //tableDirector.append(section: section) } } \ No newline at end of file diff --git a/Sources/TableSection.swift b/Sources/TableSection.swift index 71fe404..e09078e 100644 --- a/Sources/TableSection.swift +++ b/Sources/TableSection.swift @@ -66,8 +66,16 @@ public class TableSection { public func append(row row: Row) { append(rows: [row]) } - + 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) { + items.removeAtIndex(index) + } } \ No newline at end of file From dd78ffa41e8b1ef18e21aa9e1d5b2435f65dada2 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Wed, 15 Jun 2016 21:13:34 +0300 Subject: [PATCH 12/29] rowItems -> rows --- Sources/TableRowBuilder.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/TableRowBuilder.swift b/Sources/TableRowBuilder.swift index 4b37799..23158c2 100644 --- a/Sources/TableRowBuilder.swift +++ b/Sources/TableRowBuilder.swift @@ -22,7 +22,7 @@ import UIKit public protocol RowBuilder { - func rowItems() -> [Row]? + func rows() -> [Row]? } public class TableRowBuilder: RowBuilder { @@ -42,7 +42,7 @@ public class TableRowBuilder [Row]? { + public func rows() -> [Row]? { return items?.map { TableRow(item: $0, actions: actions) } } } @@ -51,7 +51,7 @@ public extension TableSection { public func append(builder builder: RowBuilder) { - if let rows = builder.rowItems() { + if let rows = builder.rows() { append(rows: rows) } } From 23378e2de40b376c8216f3ae6c0d3e40c98da1ef Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Wed, 15 Jun 2016 21:23:41 +0300 Subject: [PATCH 13/29] add invalidate --- Sources/HeightStrategy.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/HeightStrategy.swift b/Sources/HeightStrategy.swift index 084018c..f873906 100644 --- a/Sources/HeightStrategy.swift +++ b/Sources/HeightStrategy.swift @@ -26,6 +26,8 @@ public protocol CellHeightCalculatable { func height(row: Row, path: NSIndexPath) -> CGFloat func estimatedHeight(row: Row, path: NSIndexPath) -> CGFloat + + func invalidate() } public class PrototypeHeightStrategy: CellHeightCalculatable { @@ -63,4 +65,8 @@ public class PrototypeHeightStrategy: CellHeightCalculatable { public func estimatedHeight(row: Row, path: NSIndexPath) -> CGFloat { return UITableViewAutomaticDimension } + + public func invalidate() { + cachedHeights.removeAll() + } } \ No newline at end of file From 09e5c4b8a44497c71e19abd8e923d2d57464a9e8 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Wed, 15 Jun 2016 21:25:28 +0300 Subject: [PATCH 14/29] update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3b56f80..9e100ea 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ It hides a complexity of `UITableViewDataSource` and `UITableViewDelegate` metho - [x] Chainable cell actions (select/deselect etc.) - [x] Support cells created from code, xib, or storyboard - [x] Support different cells height calculation strategies +- [x] Support portrait and landscape orientations - [x] No need to subclass - [x] Extensibility From 03b67c51783a573734f2b62d7318450e1aea7c51 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Thu, 16 Jun 2016 00:54:22 +0300 Subject: [PATCH 15/29] update readme --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9e100ea..1055f80 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,10 @@ It hides a complexity of `UITableViewDataSource` and `UITableViewDelegate` metho ## Getting Started +An example app is included demonstrating TableKit's functionality. + +#### Basic usage + Create your rows: ```swift let row1 = TableRow(item: "1") @@ -58,7 +62,9 @@ class StringTableViewCell: UITableViewCell, ConfigurableCell { ``` You could have as many rows and sections as you need. -## Row actions +## Advanced + +#### Row actions It nice to have some actions that related to your cells: ```swift @@ -85,7 +91,7 @@ row }) ``` -## Batch rows +#### Batch rows You could have a situation when you need a lot of cells with the same type. In that case it's better to use `TableRowBuilder`: ```swift let builder = TableRowBuilder { From 018c35ef5317614b8e00ff260fd4b2102ce40100 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Thu, 16 Jun 2016 01:16:53 +0300 Subject: [PATCH 16/29] update readme --- README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/README.md b/README.md index 1055f80..2933cde 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ It nice to have some actions that related to your cells: let action = TableRowAction(.click) { (data) in // you could access any useful information that relates to the action + // data.cell - StringTableViewCell? // data.item - String // data.path - NSIndexPath @@ -90,6 +91,7 @@ row return false }) ``` +You could find all available actions here. #### Batch rows You could have a situation when you need a lot of cells with the same type. In that case it's better to use `TableRowBuilder`: @@ -110,6 +112,37 @@ let builder = TableRowBuilder(items: ["1", "2", "3" section.append(builder: builder) ``` +#### 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 + } +} +``` +It's enough for most cases. But you may not be happy with this. So you could use +```swift +tableDirector.shouldUsePrototypeCellHeightCalculation = true +``` +It does all dirty work with prototypes behind the scene, so you don't have to worry about anything except of your cell configuration: +```swift +class ImageTableViewCell: UITableViewCell, ConfigurableCell { + + func configure(url: NSURL, isPrototype: Bool) { + + if !isPrototype { + loadImageAsync(url: url, imageView: imageView) + } + } +} +``` +As you can see you don't have to load any remote images to your cell, cause it's not visible. It's a prototype cell, just to calculate apropriate height. + + ## Installation #### CocoaPods From 0d7d97db57c09b5b34d82a233c0eeed8c97f419d Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Thu, 16 Jun 2016 01:42:24 +0300 Subject: [PATCH 17/29] update readme --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2933cde..71eae3a 100644 --- a/README.md +++ b/README.md @@ -124,11 +124,11 @@ class StringTableViewCell: UITableViewCell, ConfigurableCell { } } ``` -It's enough for most cases. But you may not be happy with this. So you could use +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: ```swift tableDirector.shouldUsePrototypeCellHeightCalculation = true ``` -It does all dirty work with prototypes behind the scene, so you don't have to worry about anything except of your cell configuration: +It does all dirty work with prototypes for you behind the scene, so you don't have to worry about anything except of your cell configuration: ```swift class ImageTableViewCell: UITableViewCell, ConfigurableCell { @@ -138,10 +138,16 @@ class ImageTableViewCell: UITableViewCell, ConfigurableCell { loadImageAsync(url: url, imageView: imageView) } } + + override func layoutSubviews() { + super.layoutSubviews() + + contentView.layoutIfNeeded() + multilineLabel.preferredMaxLayoutWidth = multilineLabel.bounds.size.width + } } ``` -As you can see you don't have to load any remote images to your cell, cause it's not visible. It's a prototype cell, just to calculate apropriate height. - +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. ## Installation From d4efb6a679665f9a899a13fe5a9949a459e9f853 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Thu, 16 Jun 2016 22:45:33 +0300 Subject: [PATCH 18/29] improve hash --- Sources/HeightStrategy.swift | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Sources/HeightStrategy.swift b/Sources/HeightStrategy.swift index f873906..f6a5ad6 100644 --- a/Sources/HeightStrategy.swift +++ b/Sources/HeightStrategy.swift @@ -41,23 +41,27 @@ public class PrototypeHeightStrategy: CellHeightCalculatable { } public func height(row: Row, path: NSIndexPath) -> CGFloat { - - if let height = cachedHeights[row.hashValue] { + + guard let tableView = tableView else { return 0 } + + let hash = row.hashValue ^ Int(tableView.bounds.size.width).hashValue + + if let height = cachedHeights[hash] { return height } - guard let cell = tableView?.dequeueReusableCellWithIdentifier(row.reusableIdentifier) else { return 0 } - - cell.bounds = CGRectMake(0, 0, tableView?.bounds.size.width ?? 0, cell.bounds.height) + guard let cell = tableView.dequeueReusableCellWithIdentifier(row.reusableIdentifier) else { return 0 } + + cell.bounds = CGRectMake(0, 0, tableView.bounds.size.width, cell.bounds.height) row.configure(cell, isPrototype: true) - + cell.setNeedsLayout() cell.layoutIfNeeded() - let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + (tableView?.separatorStyle != .None ? separatorHeight : 0) + let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + (tableView.separatorStyle != .None ? separatorHeight : 0) - cachedHeights[row.hashValue] = height + cachedHeights[hash] = height return height } From 1266a9144649a396e273fd2dd91f9f2e48244b56 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Thu, 16 Jun 2016 23:59:25 +0300 Subject: [PATCH 19/29] addAction -> action --- .../Presentation/Controllers/MainController.swift | 4 ++-- Demo/Resources/Storyboards/LaunchScreen.storyboard | 2 +- Demo/Resources/Storyboards/Main.storyboard | 12 ++++++------ README.md | 6 ++---- Sources/HeightStrategy.swift | 1 + Sources/TableRow.swift | 2 +- 6 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Demo/Classes/Presentation/Controllers/MainController.swift b/Demo/Classes/Presentation/Controllers/MainController.swift index 3569741..a9694cb 100644 --- a/Demo/Classes/Presentation/Controllers/MainController.swift +++ b/Demo/Classes/Presentation/Controllers/MainController.swift @@ -37,13 +37,13 @@ class MainController: UIViewController { let row3 = TableRow(item: "3", actions: [a]) row1 - .addAction(TableRowAction(.shouldHighlight) { (data) -> Bool in + .action(TableRowAction(.shouldHighlight) { (data) -> Bool in print("1") return false }) - .addAction(TableRowAction(.click) { (data) in + .action(TableRowAction(.click) { (data) in print("2") }) diff --git a/Demo/Resources/Storyboards/LaunchScreen.storyboard b/Demo/Resources/Storyboards/LaunchScreen.storyboard index 0a546bb..580ceb8 100644 --- a/Demo/Resources/Storyboards/LaunchScreen.storyboard +++ b/Demo/Resources/Storyboards/LaunchScreen.storyboard @@ -1,5 +1,5 @@ - + diff --git a/Demo/Resources/Storyboards/Main.storyboard b/Demo/Resources/Storyboards/Main.storyboard index 4edf1ae..4d18da9 100644 --- a/Demo/Resources/Storyboards/Main.storyboard +++ b/Demo/Resources/Storyboards/Main.storyboard @@ -1,5 +1,5 @@ - + @@ -26,7 +26,7 @@ - + @@ -39,7 +39,7 @@ - + @@ -85,7 +85,7 @@ - + @@ -117,7 +117,7 @@ - + @@ -130,7 +130,7 @@ - + diff --git a/README.md b/README.md index 71eae3a..439f178 100644 --- a/README.md +++ b/README.md @@ -82,12 +82,10 @@ let row = TableRow(item: "some", actions: [action]) Or, using nice chaining approach: ```swift let row = TableRow(item: "some") - -row - .addAction(TableRowAction(.click) { (data) in + .action(TableRowAction(.click) { (data) in }) - .addAction(TableRowAction(.shouldHighlight) { (data) -> Bool in + .action(TableRowAction(.shouldHighlight) { (data) -> Bool in return false }) ``` diff --git a/Sources/HeightStrategy.swift b/Sources/HeightStrategy.swift index f6a5ad6..c6e7d25 100644 --- a/Sources/HeightStrategy.swift +++ b/Sources/HeightStrategy.swift @@ -33,6 +33,7 @@ public protocol CellHeightCalculatable { public class PrototypeHeightStrategy: CellHeightCalculatable { public weak var tableView: UITableView? + private var prototypes = [String: UITableViewCell]() private var cachedHeights = [Int: CGFloat]() private var separatorHeight = 1 / UIScreen.mainScreen().scale diff --git a/Sources/TableRow.swift b/Sources/TableRow.swift index ff33a2e..3dac46b 100644 --- a/Sources/TableRow.swift +++ b/Sources/TableRow.swift @@ -88,7 +88,7 @@ public class TableRow) -> Self { + public func action(action: TableRowAction) -> Self { actions[action.type.key] = action return self From 86b93ae78d5906a8f5796ae44069f40d04901d26 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Fri, 17 Jun 2016 00:01:55 +0300 Subject: [PATCH 20/29] update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 439f178..1e4259f 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,6 @@ class StringTableViewCell: UITableViewCell, ConfigurableCell { ``` You could have as many rows and sections as you need. -## Advanced - #### Row actions It nice to have some actions that related to your cells: @@ -110,6 +108,8 @@ let builder = TableRowBuilder(items: ["1", "2", "3" section.append(builder: builder) ``` +## 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 From 1bc7301c7e090664f7a739f48c8c8ee6ba73b796 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Sat, 18 Jun 2016 04:17:58 +0300 Subject: [PATCH 21/29] add operators --- README.md | 2 +- Sources/Operators.swift | 9 +++++++++ Tests/TableKitTests.swift | 28 ++++++++++++++++++++++------ 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1e4259f..6700590 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ It hides a complexity of `UITableViewDataSource` and `UITableViewDelegate` metho ## Features -- [x] Type-safe cells based on generics +- [x] Type-safe generic cells - [x] The easiest way to map your models or view models to cells - [x] Correctly handles autolayout cells with multiline labels - [x] Chainable cell actions (select/deselect etc.) diff --git a/Sources/Operators.swift b/Sources/Operators.swift index 14a4326..3f0f555 100644 --- a/Sources/Operators.swift +++ b/Sources/Operators.swift @@ -27,6 +27,15 @@ public func +=(left: TableDirector, right: [TableSection]) { left.append(sections: right) } +// -- +public func +=(left: TableDirector, right: Row) { + left.append(sections: [TableSection(rows: [right])]) +} + +public func +=(left: TableDirector, right: [Row]) { + left.append(sections: [TableSection(rows: right)]) +} + // -- public func +=(left: TableSection, right: Row) { left.append(row: right) diff --git a/Tests/TableKitTests.swift b/Tests/TableKitTests.swift index 868e288..2a26050 100644 --- a/Tests/TableKitTests.swift +++ b/Tests/TableKitTests.swift @@ -45,7 +45,7 @@ struct TestTableViewCellOptions { static let EstimatedHeight: Float = 255 } -/*class TestTableViewCell: UITableViewCell, ConfigurableCell { +class TestTableViewCell: UITableViewCell, ConfigurableCell { typealias T = TestData @@ -57,12 +57,12 @@ struct TestTableViewCellOptions { return TestTableViewCellOptions.EstimatedHeight } - func configure(item: T) { + func configure(item: T, isPrototype: Bool) { textLabel?.text = item.title } func raiseAction() { - Action(key: TestTableViewCellOptions.CellAction, sender: self, userInfo: [TestTableViewCellOptions.CellActionUserInfoKey: TestTableViewCellOptions.CellActionUserInfoValue]).invoke() + //Action(key: TestTableViewCellOptions.CellAction, sender: self, userInfo: [TestTableViewCellOptions.CellActionUserInfoKey: TestTableViewCellOptions.CellActionUserInfoValue]).invoke() } } @@ -74,6 +74,7 @@ class TabletTests: XCTestCase { super.setUp() testController = TestController() + let _ = testController.view } override func tearDown() { @@ -89,7 +90,22 @@ class TabletTests: XCTestCase { XCTAssertNotNil(testController.tableDirector.tableView, "TableDirector should have table view") } - func testSimpleRowBuilderCreatesRowsAndSection() { + func testRow() { + + let data = TestData(title: "title") + + let row = TableRow(item: data) + + testController.tableDirector += row + + XCTAssertTrue(testController.tableView.dataSource?.numberOfSectionsInTableView?(testController.tableView) == 1, "Table view should have a section") + XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == 1, "Table view should have certain number of rows in a section") + + let cell = testController.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0)) as? TestTableViewCell + XCTAssertNotNil(cell) + } + + /*func testSimpleRowBuilderCreatesRowsAndSection() { let source = ["1", "2", "3"] @@ -196,5 +212,5 @@ class TabletTests: XCTestCase { cell?.raiseAction() waitForExpectationsWithTimeout(1.0, handler: nil) - } -}*/ \ No newline at end of file + }*/ +} \ No newline at end of file From 23cceddfe2e256de54935ee26cdf7d9891c6b849 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Sat, 18 Jun 2016 04:31:19 +0300 Subject: [PATCH 22/29] functional programming example --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 6700590..a86fc2c 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ It hides a complexity of `UITableViewDataSource` and `UITableViewDelegate` metho ## 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] Correctly handles autolayout cells with multiline labels - [x] Chainable cell actions (select/deselect etc.) @@ -147,6 +148,15 @@ 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. +#### Functional programming +It's never been so easy to deal with table views. +```swift +let users = /* some users array */ + +tableDirector += users.filter({ $0.state == .active }).map({ TableRow(item: $0.username) }) +``` +Done, your table is ready. It's just awesome! + ## Installation #### CocoaPods From 221efc00abc4779fc5aa0f11ec89abef3c681c1e Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Sat, 18 Jun 2016 05:42:49 +0300 Subject: [PATCH 23/29] add register cell method --- README.md | 1 + Sources/ConfigurableCell.swift | 20 ++++++++++++++++---- Sources/TableDirector.swift | 15 +++++++++++++++ Tests/TableKitTests.swift | 1 + 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a86fc2c..4f053bd 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ 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 3e42f17..473f7cc 100644 --- a/Sources/ConfigurableCell.swift +++ b/Sources/ConfigurableCell.swift @@ -20,21 +20,33 @@ import UIKit -public protocol ConfigurableCell { +public protocol ReusableCell { + + static func reusableIdentifier() -> String + static func nib() -> UINib? +} + +public protocol ConfigurableCell: ReusableCell { associatedtype T - static func reusableIdentifier() -> String static func estimatedHeight() -> CGFloat static func defaultHeight() -> CGFloat? func configure(_: T, isPrototype: Bool) } -public extension ConfigurableCell where Self: UITableViewCell { - +public extension ReusableCell where Self: UITableViewCell { + static func reusableIdentifier() -> String { return String(self) } + + static func nib() -> UINib? { + return nil + } +} + +public extension ConfigurableCell where Self: UITableViewCell { static func estimatedHeight() -> CGFloat { return UITableViewAutomaticDimension diff --git a/Sources/TableDirector.swift b/Sources/TableDirector.swift index 72b8fb1..3d67049 100644 --- a/Sources/TableDirector.swift +++ b/Sources/TableDirector.swift @@ -57,6 +57,21 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate public func reload() { tableView?.reloadData() } + + public func register(cells: T.Type...) { + + for cell in cells { + if let nib = cell.nib() { + tableView?.registerNib(nib, forCellReuseIdentifier: cell.reusableIdentifier()) + } else { + if let nib = NSBundle(forClass: cell).loadNibNamed(cell.reusableIdentifier(), owner: nil, options: nil).first as? UINib { + tableView?.registerNib(nib, forCellReuseIdentifier: cell.reusableIdentifier()) + } else { + tableView?.registerClass(cell, forCellReuseIdentifier: cell.reusableIdentifier()) + } + } + } + } // MARK: Public diff --git a/Tests/TableKitTests.swift b/Tests/TableKitTests.swift index 2a26050..bb2d8f8 100644 --- a/Tests/TableKitTests.swift +++ b/Tests/TableKitTests.swift @@ -28,6 +28,7 @@ class TestController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() tableDirector = TableDirector(tableView: tableView) + tableDirector.register([TestTableViewCell.self]) } } From dd6092f87fed92657379171e9b1d72644fccd18e Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Sat, 18 Jun 2016 06:03:29 +0300 Subject: [PATCH 24/29] fix register cells --- Sources/TableDirector.swift | 17 +++++++++++++---- Tests/TableKitTests.swift | 32 +++++++++++++++++++++++++++++--- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/Sources/TableDirector.swift b/Sources/TableDirector.swift index 3d67049..170e06d 100644 --- a/Sources/TableDirector.swift +++ b/Sources/TableDirector.swift @@ -61,15 +61,24 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate public func register(cells: T.Type...) { for cell in cells { + if let nib = cell.nib() { + tableView?.registerNib(nib, forCellReuseIdentifier: cell.reusableIdentifier()) + return + } else { - if let nib = NSBundle(forClass: cell).loadNibNamed(cell.reusableIdentifier(), owner: nil, options: nil).first as? UINib { - tableView?.registerNib(nib, forCellReuseIdentifier: cell.reusableIdentifier()) - } else { - tableView?.registerClass(cell, forCellReuseIdentifier: cell.reusableIdentifier()) + + 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()) } } diff --git a/Tests/TableKitTests.swift b/Tests/TableKitTests.swift index bb2d8f8..a3e3ea3 100644 --- a/Tests/TableKitTests.swift +++ b/Tests/TableKitTests.swift @@ -28,7 +28,7 @@ class TestController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() tableDirector = TableDirector(tableView: tableView) - tableDirector.register([TestTableViewCell.self]) + tableDirector.register(TestTableViewCell.self) } } @@ -75,7 +75,7 @@ class TabletTests: XCTestCase { super.setUp() testController = TestController() - let _ = testController.view + testController.view.hidden = false } override func tearDown() { @@ -91,21 +91,47 @@ class TabletTests: XCTestCase { XCTAssertNotNil(testController.tableDirector.tableView, "TableDirector should have table view") } - func testRow() { + func testRowInSection() { let data = TestData(title: "title") let row = TableRow(item: data) testController.tableDirector += row + testController.tableView.reloadData() XCTAssertTrue(testController.tableView.dataSource?.numberOfSectionsInTableView?(testController.tableView) == 1, "Table view should have a section") XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == 1, "Table view should have certain number of rows in a section") let cell = testController.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0)) as? TestTableViewCell XCTAssertNotNil(cell) + XCTAssertTrue(cell?.textLabel?.text == data.title) } + func testManyRowsInSection() { + + let data = [TestData(title: "1"), TestData(title: "2"), TestData(title: "3")] + + let rows: [TableRow] = data.map { TableRow(item: $0) } + + testController.tableDirector += rows + testController.tableView.reloadData() + + XCTAssertTrue(testController.tableView.dataSource?.numberOfSectionsInTableView?(testController.tableView) == 1, "Table view should have a section") + XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == data.count, "Table view should have certain number of rows in a section") + + for (index, element) in data.enumerate() { + + let cell = testController.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0)) as? TestTableViewCell + XCTAssertNotNil(cell) + XCTAssertTrue(cell?.textLabel?.text == element.title) + } + } + + + + + /*func testSimpleRowBuilderCreatesRowsAndSection() { let source = ["1", "2", "3"] From 7e3d52fb92cb6294da9b92dbcb591889c6848d56 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Sat, 18 Jun 2016 06:20:29 +0300 Subject: [PATCH 25/29] add tests --- Sources/TableDirector.swift | 6 +++ Tests/TableKitTests.swift | 89 +++++++++---------------------------- 2 files changed, 26 insertions(+), 69 deletions(-) diff --git a/Sources/TableDirector.swift b/Sources/TableDirector.swift index 170e06d..5c1021c 100644 --- a/Sources/TableDirector.swift +++ b/Sources/TableDirector.swift @@ -223,6 +223,12 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate return self } + public func append(rows rows: [Row]) -> Self { + + append(section: TableSection(rows: rows)) + return self + } + public func clear() -> Self { sections.removeAll() diff --git a/Tests/TableKitTests.swift b/Tests/TableKitTests.swift index a3e3ea3..7a601e1 100644 --- a/Tests/TableKitTests.swift +++ b/Tests/TableKitTests.swift @@ -63,7 +63,7 @@ class TestTableViewCell: UITableViewCell, ConfigurableCell { } func raiseAction() { - //Action(key: TestTableViewCellOptions.CellAction, sender: self, userInfo: [TestTableViewCellOptions.CellActionUserInfoKey: TestTableViewCellOptions.CellActionUserInfoValue]).invoke() + TableCellAction(key: TestTableViewCellOptions.CellAction, sender: self, userInfo: nil).invoke() } } @@ -112,7 +112,7 @@ class TabletTests: XCTestCase { let data = [TestData(title: "1"), TestData(title: "2"), TestData(title: "3")] - let rows: [TableRow] = data.map { TableRow(item: $0) } + let rows: [Row] = data.map({ TableRow(item: $0) }) testController.tableDirector += rows testController.tableView.reloadData() @@ -128,64 +128,18 @@ class TabletTests: XCTestCase { } } - - - - - /*func testSimpleRowBuilderCreatesRowsAndSection() { - - let source = ["1", "2", "3"] - - let rows = TableBaseRowBuilder(items: source) - .action(.configure) { data -> Void in - - XCTAssertNotNil(data.cell, "Action should have a cell") - data.cell?.textLabel?.text = "\(data.item)" - } + func testTableSectionCreatesSectionWithHeaderAndFooterTitles() { - testController.view.hidden = false - testController.tableDirector += rows - testController.tableView.reloadData() - - XCTAssertTrue(testController.tableView.dataSource?.numberOfSectionsInTableView?(testController.tableView) == 1, "Table view should have a section") - XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == source.count, "Table view should have certain number of rows in a section") + let row = TableRow(item: TestData(title: "title")) - for (index, element) in source.enumerate() { - - let cell = testController.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0)) - - XCTAssertNotNil(cell) - XCTAssertTrue(cell?.textLabel?.text == element) - } - } - - func testConfigurableRowBuilderCreatesRowsAndSection() { - - let testData = TestData(title: "title") - - testController.view.hidden = false - testController.tableDirector += TableRowBuilder(item: testData) - testController.tableView.reloadData() - - let cell = testController.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0)) as? TestTableViewCell - - XCTAssertNotNil(cell, "Cell should exists and should be TestTableViewCell") - XCTAssertTrue(cell?.textLabel?.text == testData.title, "Cell's textLabel.text should equal to testData's title") - } - - func testSectionBuilderCreatesSectionWithHeaderAndFooterTitles() { - - let row = TableRowBuilder(items: [TestData(title: "title")]) - let sectionHeaderTitle = "Header Title" let sectionFooterTitle = "Footer Title" - - let section = TableSectionBuilder(headerTitle: sectionHeaderTitle, footerTitle: sectionFooterTitle, rows: [row]) - - testController.view.hidden = false + + let section = TableSection(headerTitle: sectionHeaderTitle, footerTitle: sectionFooterTitle, rows: [row]) + testController.tableDirector += section testController.tableView.reloadData() - + XCTAssertTrue(testController.tableView.dataSource?.numberOfSectionsInTableView?(testController.tableView) == 1, "Table view should have a section") XCTAssertTrue(testController.tableView.dataSource?.tableView(testController.tableView, numberOfRowsInSection: 0) == 1, "Table view should have certain number of rows in a section") @@ -193,17 +147,16 @@ class TabletTests: XCTestCase { XCTAssertTrue(testController.tableView.dataSource?.tableView?(testController.tableView, titleForFooterInSection: 0) == sectionFooterTitle) } - func testSectionBuilderCreatesSectionWithHeaderAndFooterViews() { - - let row = TableRowBuilder(items: [TestData(title: "title")]) + func testTableSectionCreatesSectionWithHeaderAndFooterViews() { + + let row = TableRow(item: TestData(title: "title")) let sectionHeaderView = UIView() let sectionFooterView = UIView() - - let section = TableSectionBuilder(headerView: sectionHeaderView, footerView: sectionFooterView, rows: nil) + + let section = TableSection(headerView: sectionHeaderView, footerView: sectionFooterView, rows: nil) section += row - testController.view.hidden = false testController.tableDirector += section testController.tableView.reloadData() @@ -213,20 +166,18 @@ class TabletTests: XCTestCase { XCTAssertTrue(testController.tableView.delegate?.tableView?(testController.tableView, viewForHeaderInSection: 0) == sectionHeaderView) XCTAssertTrue(testController.tableView.delegate?.tableView?(testController.tableView, viewForFooterInSection: 0) == sectionFooterView) } - + func testRowBuilderCustomActionInvokedAndSentUserInfo() { let expectation = expectationWithDescription("cell action") - let row = TableRowBuilder(items: [TestData(title: "title")]) - .action(TestTableViewCellOptions.CellAction) { data -> Void in - + let row = TableRow(item: TestData(title: "title")) + .action(TableRowAction(.custom(TestTableViewCellOptions.CellAction)) { (data) in + XCTAssertNotNil(data.cell, "Action data should have a cell") - XCTAssertNotNil(data.userInfo, "Action data should have a user info dictionary") - XCTAssertTrue(data.userInfo?[TestTableViewCellOptions.CellActionUserInfoKey] as? String == TestTableViewCellOptions.CellActionUserInfoValue, "UserInfo should have correct value for key") - + expectation.fulfill() - } + }) testController.view.hidden = false testController.tableDirector += row @@ -239,5 +190,5 @@ class TabletTests: XCTestCase { cell?.raiseAction() waitForExpectationsWithTimeout(1.0, handler: nil) - }*/ + } } \ No newline at end of file From f74d35cc8c6b55809d11d6ad6dcb575931213b76 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Sat, 18 Jun 2016 06:21:24 +0300 Subject: [PATCH 26/29] update readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4f053bd..7c0143e 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,9 @@ It's never been so easy to deal with table views. ```swift let users = /* some users array */ -tableDirector += users.filter({ $0.state == .active }).map({ TableRow(item: $0.username) }) +let rows: [Row] = users.filter({ $0.state == .active }).map({ TableRow(item: $0.username) }) + +tableDirector += rows ``` Done, your table is ready. It's just awesome! From 89e3113a35a9d9ef4005748bbb731d9e99221521 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Sat, 18 Jun 2016 06:23:20 +0300 Subject: [PATCH 27/29] fix travis-ci config --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index f57eb70..9e36ee3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,5 @@ branches: before_install: - brew update - brew reinstall --HEAD xctool - - cd Tablet script: - - xctool clean build test -project TableKit.xcodeproj -scheme TableKit -sdk iphonesimulator \ No newline at end of file + - xctool clean build test -project TableKit.xcodeproj -scheme TableKitTests -sdk iphonesimulator \ No newline at end of file From b68bd46ca8152d7938c59e01ee109ac6a8823c4c Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Sat, 18 Jun 2016 06:23:57 +0300 Subject: [PATCH 28/29] bump version --- README.md | 2 +- TableKit.podspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7c0143e..e0771b7 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

diff --git a/TableKit.podspec b/TableKit.podspec index ffb675c..58f4a5c 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.7.0' + s.version = '0.8.0' s.homepage = 'https://github.com/maxsokolov/TableKit' s.summary = 'Type-safe declarative table views. Swift 2.2 is required.' From 1137b47af1bf7f676b371681b9c9770065469868 Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Sat, 18 Jun 2016 06:44:34 +0300 Subject: [PATCH 29/29] fix ci --- .travis.yml | 2 +- .../xcshareddata/xcschemes/TableKit.xcscheme | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9e36ee3..00df090 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,4 +7,4 @@ before_install: - brew update - brew reinstall --HEAD xctool script: - - xctool clean build test -project TableKit.xcodeproj -scheme TableKitTests -sdk iphonesimulator \ No newline at end of file + - xctool clean build test -project TableKit.xcodeproj -scheme TableKit -sdk iphonesimulator \ No newline at end of file diff --git a/TableKit.xcodeproj/xcshareddata/xcschemes/TableKit.xcscheme b/TableKit.xcodeproj/xcshareddata/xcschemes/TableKit.xcscheme index 9f7535f..6bd5e4d 100644 --- a/TableKit.xcodeproj/xcshareddata/xcschemes/TableKit.xcscheme +++ b/TableKit.xcodeproj/xcshareddata/xcschemes/TableKit.xcscheme @@ -28,7 +28,26 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + + + + +