diff --git a/.travis.yml b/.travis.yml index f57eb70..00df090 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 diff --git a/Demo/Classes/Presentation/Controllers/MainController.swift b/Demo/Classes/Presentation/Controllers/MainController.swift index d2b4cab..a9694cb 100644 --- a/Demo/Classes/Presentation/Controllers/MainController.swift +++ b/Demo/Classes/Presentation/Controllers/MainController.swift @@ -14,14 +14,14 @@ class MainController: UIViewController { @IBOutlet weak var tableView: UITableView! { didSet { tableDirector = TableDirector(tableView: tableView) + tableDirector.shouldUsePrototypeCellHeightCalculation = true } } var tableDirector: TableDirector! override func viewDidLoad() { super.viewDidLoad() - - + let a = TableRowAction(.click) { (data) in @@ -35,65 +35,22 @@ 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 - + .action(TableRowAction(.shouldHighlight) { (data) -> Bool in + print("1") return false }) - .addAction(TableRowAction(.click) { (data) in + .action(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/Demo/Classes/Presentation/Views/StoryboardImageTableViewCell.swift b/Demo/Classes/Presentation/Views/StoryboardImageTableViewCell.swift index 4cd5631..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" @@ -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/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/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 a691198..e0771b7 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. @@ -13,23 +14,28 @@ It hides a complexity of `UITableViewDataSource` and `UITableViewDelegate` metho ## Features -- [x] Type-safe cells based on generics +- [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.) - [x] Support cells created from code, xib, or storyboard -- [x] Automatic xib/classes registration +- [x] Support different cells height calculation strategies +- [x] Support portrait and landscape orientations - [x] No need to subclass - [x] Extensibility -- [x] Tests -## Usage +## Getting Started + +An example app is included demonstrating TableKit's functionality. + +#### Basic usage 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 @@ -38,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: @@ -46,7 +53,7 @@ class StringTableViewCell: UITableViewCell, ConfigurableCell { typealias T = String - func configure(string: T) { + func configure(string: T, isPrototype: Bool) { titleLabel.text = string } @@ -57,12 +64,17 @@ class StringTableViewCell: UITableViewCell, ConfigurableCell { ``` 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 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]) @@ -70,17 +82,16 @@ 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 }) ``` +You could find all available actions here. -## 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 { @@ -99,6 +110,56 @@ 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 +class StringTableViewCell: UITableViewCell, ConfigurableCell { + + // ... + + static func estimatedHeight() -> CGFloat { + return 44 + } +} +``` +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 for you 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) + } + } + + override func layoutSubviews() { + super.layoutSubviews() + + contentView.layoutIfNeeded() + multilineLabel.preferredMaxLayoutWidth = multilineLabel.bounds.size.width + } +} +``` +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 */ + +let rows: [Row] = users.filter({ $0.state == .active }).map({ TableRow(item: $0.username) }) + +tableDirector += rows +``` +Done, your table is ready. It's just awesome! + ## Installation #### CocoaPods @@ -112,7 +173,9 @@ 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. ## Requirements diff --git a/Sources/ConfigurableCell.swift b/Sources/ConfigurableCell.swift index 4127139..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) + 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/HeightStrategy.swift b/Sources/HeightStrategy.swift index c34bd40..c6e7d25 100644 --- a/Sources/HeightStrategy.swift +++ b/Sources/HeightStrategy.swift @@ -20,29 +20,58 @@ import UIKit -public protocol HeightStrategy { +public protocol CellHeightCalculatable { var tableView: UITableView? { get set } - func height(item: Item, indexPath: NSIndexPath, cell: Cell.Type) -> CGFloat + func height(row: Row, path: NSIndexPath) -> CGFloat + func estimatedHeight(row: Row, path: NSIndexPath) -> CGFloat + + func invalidate() } -public class PrototypeHeightStrategy: HeightStrategy { +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 - public func height(item: Item, indexPath: NSIndexPath, cell: Cell.Type) -> CGFloat { - - guard let cell = tableView?.dequeueReusableCellWithIdentifier(Cell.reusableIdentifier()) as? Cell else { return 0 } - - cell.bounds = CGRectMake(0, 0, tableView?.bounds.size.width ?? 0, cell.bounds.height) - - cell.configure(item) - + init(tableView: UITableView?) { + self.tableView = tableView + } + + public func height(row: Row, path: NSIndexPath) -> CGFloat { + + 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, cell.bounds.height) + + row.configure(cell, isPrototype: true) + cell.setNeedsLayout() cell.layoutIfNeeded() - - return cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1 + + let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + (tableView.separatorStyle != .None ? separatorHeight : 0) + + cachedHeights[hash] = height + + return height + } + + public func estimatedHeight(row: Row, path: NSIndexPath) -> CGFloat { + return UITableViewAutomaticDimension + } + + public func invalidate() { + cachedHeights.removeAll() } } \ No newline at end of file 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/Sources/TableDirector.swift b/Sources/TableDirector.swift index 03545f6..5c1021c 100644 --- a/Sources/TableDirector.swift +++ b/Sources/TableDirector.swift @@ -27,7 +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: CellHeightCalculatable? + + public var shouldUsePrototypeCellHeightCalculation: Bool = false { + didSet { + if shouldUsePrototypeCellHeightCalculation { + heightStrategy = PrototypeHeightStrategy(tableView: tableView) + } + } + } public init(tableView: UITableView, scrollDelegate: UIScrollViewDelegate? = nil) { super.init() @@ -47,6 +57,30 @@ 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()) + 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 @@ -77,11 +111,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 { - return sections[indexPath.section].items[indexPath.row].defaultHeight + + let row = sections[indexPath.section].items[indexPath.row] + return heightStrategy?.height(row, path: indexPath) ?? row.defaultHeight } // MARK: UITableViewDataSource - configuration @@ -104,7 +142,7 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate cell.layoutIfNeeded() } - row.configure(cell) + row.configure(cell, isPrototype: false) return cell } @@ -185,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/Sources/TableRow.swift b/Sources/TableRow.swift index 2b06615..3dac46b 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 { @@ -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() @@ -60,11 +69,11 @@ public class TableRow) -> Self { + public func action(action: TableRowAction) -> Self { actions[action.type.key] = action return self 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 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) } } 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 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.' 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"> + + + + + + + + diff --git a/Tests/TableKitTests.swift b/Tests/TableKitTests.swift index 868e288..7a601e1 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) } } @@ -45,7 +46,7 @@ struct TestTableViewCellOptions { static let EstimatedHeight: Float = 255 } -/*class TestTableViewCell: UITableViewCell, ConfigurableCell { +class TestTableViewCell: UITableViewCell, ConfigurableCell { typealias T = TestData @@ -57,12 +58,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() + TableCellAction(key: TestTableViewCellOptions.CellAction, sender: self, userInfo: nil).invoke() } } @@ -74,6 +75,7 @@ class TabletTests: XCTestCase { super.setUp() testController = TestController() + testController.view.hidden = false } override func tearDown() { @@ -89,60 +91,55 @@ class TabletTests: XCTestCase { XCTAssertNotNil(testController.tableDirector.tableView, "TableDirector should have table view") } - 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 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: [Row] = data.map({ TableRow(item: $0) }) - 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") - for (index, element) in source.enumerate() { - - let cell = testController.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0)) + 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) + XCTAssertTrue(cell?.textLabel?.text == element.title) } } - - func testConfigurableRowBuilderCreatesRowsAndSection() { - - let testData = TestData(title: "title") + + func testTableSectionCreatesSectionWithHeaderAndFooterTitles() { - testController.view.hidden = false - testController.tableDirector += TableRowBuilder(item: testData) - testController.tableView.reloadData() - - let cell = testController.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0)) as? TestTableViewCell + let row = TableRow(item: TestData(title: "title")) - 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") @@ -150,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() @@ -170,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 @@ -197,4 +191,4 @@ class TabletTests: XCTestCase { waitForExpectationsWithTimeout(1.0, handler: nil) } -}*/ \ No newline at end of file +} \ No newline at end of file