commit
874f767528
|
|
@ -0,0 +1,39 @@
|
|||
# Mac OS X
|
||||
.DS_Store
|
||||
|
||||
# Xcode
|
||||
|
||||
## Build generated
|
||||
build/
|
||||
DerivedData
|
||||
|
||||
## Various settings
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
|
||||
## Other
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
*.xcuserstate
|
||||
*.xcscmblueprint
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
*.ipa
|
||||
|
||||
## Playgrounds
|
||||
timeline.xctimeline
|
||||
playground.xcworkspace
|
||||
|
||||
# Swift Package Manager
|
||||
.build/
|
||||
|
||||
# Carthage
|
||||
Carthage/Build
|
||||
99
README.md
99
README.md
|
|
@ -1,10 +1,12 @@
|
|||
#Tablet
|
||||

|
||||
|
||||
#Tablet.swift
|
||||
|
||||
<p align="left">
|
||||
<a href="https://travis-ci.org/maxsokolov/tablet"><img src="https://travis-ci.org/maxsokolov/tablet.svg" alt="Build Status" /></a>
|
||||
<a href="https://developer.apple.com/swift"><img src="https://img.shields.io/badge/Swift2-compatible-4BC51D.svg?style=flat" alt="Swift 2 compatible" /></a>
|
||||
<a href="https://developer.apple.com/swift"><img src="https://img.shields.io/badge/Swift_2.2-compatible-4BC51D.svg?style=flat" alt="Swift 2.2 compatible" /></a>
|
||||
<img src="https://img.shields.io/badge/platform-iOS-blue.svg?style=flat" alt="Platform iOS" />
|
||||
<a href="https://cocoapods.org/pods/tablet"><img src="https://img.shields.io/badge/pod-0.4.0-blue.svg" alt="CocoaPods compatible" /></a>
|
||||
<a href="https://cocoapods.org/pods/tablet"><img src="https://img.shields.io/badge/pod-0.5.0-blue.svg" alt="CocoaPods compatible" /></a>
|
||||
<a href="https://raw.githubusercontent.com/maxsokolov/tablet/master/LICENSE"><img src="http://img.shields.io/badge/license-MIT-blue.svg?style=flat" alt="License: MIT" /></a>
|
||||
</p>
|
||||
|
||||
|
|
@ -15,18 +17,18 @@ Tablet is a super lightweight yet powerful generic library that handles a comple
|
|||
- [x] Type-safe cells based on generics
|
||||
- [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
|
||||
- [x] Chainable cell actions (select/deselect etc.)
|
||||
- [x] Support cells created from code, xib, or storyboard
|
||||
- [x] Automatic xib/classes registration
|
||||
- [x] No need to subclass
|
||||
- [x] Extensibility
|
||||
- [x] Tests
|
||||
|
||||
That's almost all you need in your controller to build a bunch of cells in a section 😘:
|
||||
That's almost all you need to build a bunch of cells in a section:
|
||||
```swift
|
||||
TableConfigurableRowBuilder<String, MyTableViewCell>(items: ["1", "2", "3", "4", "5"])
|
||||
let builder = TableRowBuilder<String, MyTableViewCell>(items: ["1", "2", "3", "4", "5"])
|
||||
```
|
||||
Tablet respects cells reusability feature and built with performace in mind. See the Usage section to learn more.
|
||||
Tablet relies on <a href="https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithSelf-SizingTableViewCells.html" target="_blank">self-sizing table view cells</a>, respects cells reusability feature and also built with performace in mind. You don't have to worry about anything, just create your cells, setup autolayout constraints and be happy. See the Usage section to learn more.
|
||||
|
||||
## Requirements
|
||||
|
||||
|
|
@ -55,30 +57,10 @@ $ pod install
|
|||
|
||||
## Usage
|
||||
|
||||
### Very basic
|
||||
|
||||
You may want to setup a very basic table view, without any custom cells. In that case simply use the `TableRowBuilder`.
|
||||
|
||||
```swift
|
||||
import Tablet
|
||||
|
||||
let rowBuilder = TableRowBuilder<User, UITableViewCell>(items: [user1, user2, user3], id: "reusable_id")
|
||||
.action(.configure) { data -> Void in
|
||||
|
||||
data.cell?.textLabel?.text = data.item.username
|
||||
data.cell?.detailTextLabel?.text = data.item.isActive ? "Active" : "Inactive"
|
||||
}
|
||||
|
||||
let sectionBuilder = TableSectionBuilder(headerTitle: "Users", rows: [rowBuilder])
|
||||
|
||||
director = TableDirector(tableView: tableView)
|
||||
director.appendSections(sectionBuilder)
|
||||
```
|
||||
|
||||
### Type-safe configurable cells
|
||||
|
||||
Let's say you want to put your cell configuration logic into cell itself. Say you want to pass your view model (or even model) to your cell.
|
||||
You could easily do this using the `TableConfigurableRowBuilder`. Your cell should respect the `ConfigurableCell` protocol as you may see in example below:
|
||||
You could easily do this using the `TableRowBuilder`. Your cell should conforms to `ConfigurableCell` protocol as you may see in example below:
|
||||
|
||||
```swift
|
||||
import Tablet
|
||||
|
|
@ -87,6 +69,7 @@ class MyTableViewCell : UITableViewCell, ConfigurableCell {
|
|||
|
||||
typealias T = User
|
||||
|
||||
// this method is not required to be implemented if your cell's id equals to class name
|
||||
static func reusableIdentifier() -> String {
|
||||
return "reusable_id"
|
||||
}
|
||||
|
|
@ -102,16 +85,36 @@ class MyTableViewCell : UITableViewCell, ConfigurableCell {
|
|||
}
|
||||
}
|
||||
```
|
||||
Once you've implemented the protocol, simply use the `TableConfigurableRowBuilder` to build cells:
|
||||
Once you've implemented the protocol, simply use the `TableRowBuilder` to build cells:
|
||||
|
||||
```swift
|
||||
import Tablet
|
||||
|
||||
let rowBuilder = TableConfigurableRowBuilder<User, MyTableViewCell>()
|
||||
rowBuilder.appendItems(users)
|
||||
let rowBuilder = TableRowBuilder<User, MyTableViewCell>()
|
||||
rowBuilder += users
|
||||
|
||||
director = TableDirector(tableView: tableView)
|
||||
tableDirector.appendSection(TableSectionBuilder(rows: [rowBuilder]))
|
||||
tableDirector += TableSectionBuilder(rows: [rowBuilder])
|
||||
```
|
||||
|
||||
### Very basic table view
|
||||
|
||||
You may want to setup a very basic table view, without any custom cells. In that case simply use the `TableBaseRowBuilder`.
|
||||
|
||||
```swift
|
||||
import Tablet
|
||||
|
||||
let rowBuilder = TableBaseRowBuilder<User, UITableViewCell>(items: [user1, user2, user3], id: "reusable_id")
|
||||
.action(.configure) { (data) in
|
||||
|
||||
data.cell?.textLabel?.text = data.item.username
|
||||
data.cell?.detailTextLabel?.text = data.item.isActive ? "Active" : "Inactive"
|
||||
}
|
||||
|
||||
let sectionBuilder = TableSectionBuilder(headerTitle: "Users", footerTitle: nil, rows: [rowBuilder])
|
||||
|
||||
director = TableDirector(tableView: tableView)
|
||||
director += sectionBuilder
|
||||
```
|
||||
|
||||
### Cell actions
|
||||
|
|
@ -122,13 +125,13 @@ Tablet provides a chaining approach to handle actions from your cells:
|
|||
import Tablet
|
||||
|
||||
let rowBuilder = TableRowBuilder<User, MyTableViewCell>(items: [user1, user2, user3], id: "reusable_id")
|
||||
.action(.configure) { data -> Void in
|
||||
.action(.configure) { (data) in
|
||||
|
||||
}
|
||||
.action(.click) { data -> Void in
|
||||
.action(.click) { (data) in
|
||||
|
||||
}
|
||||
.action(.shouldHighlight) { data -> ReturnValue in
|
||||
.valueAction(.shouldHighlight) { (data) in
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
@ -138,13 +141,15 @@ let rowBuilder = TableRowBuilder<User, MyTableViewCell>(items: [user1, user2, us
|
|||
```swift
|
||||
import Tablet
|
||||
|
||||
let kMyAction = "action_key"
|
||||
struct MyCellActions {
|
||||
static let ButtonClicked = "ButtonClicked"
|
||||
}
|
||||
|
||||
class MyTableViewCell : UITableViewCell {
|
||||
|
||||
@IBAction func buttonClicked(sender: UIButton) {
|
||||
|
||||
Action(key: kMyAction, sender: self, userInfo: nil).invoke()
|
||||
Action(key: MyCellActions.ButtonClicked, sender: self, userInfo: nil).invoke()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -152,14 +157,14 @@ And receive this actions with your row builder:
|
|||
```swift
|
||||
import Tablet
|
||||
|
||||
let rowBuilder = TableConfigurableRowBuilder<User, MyTableViewCell>(items: users, id: "reusable_id")
|
||||
.action(.click) { data -> Void in
|
||||
let rowBuilder = TableRowBuilder<User, MyTableViewCell>(items: users)
|
||||
.action(.click) { (data) in
|
||||
|
||||
}
|
||||
.action(.willDisplay) { data -> Void in
|
||||
.action(.willDisplay) { (data) in
|
||||
|
||||
}
|
||||
.action(kMyAction) { data -> Void in
|
||||
.action(MyCellActions.ButtonClicked) { (data) in
|
||||
|
||||
}
|
||||
```
|
||||
|
|
@ -167,24 +172,26 @@ let rowBuilder = TableConfigurableRowBuilder<User, MyTableViewCell>(items: users
|
|||
## Extensibility
|
||||
|
||||
If you find that Tablet is not provide an action you need, for example you need UITableViewDelegate's `didEndDisplayingCell` method and it's not out of the box,
|
||||
simply provide an extension for `TableDirector` as follow:
|
||||
simply provide an extension for `TableDirector`:
|
||||
```swift
|
||||
import Tablet
|
||||
|
||||
let kTableDirectorDidEndDisplayingCell = "enddisplaycell"
|
||||
struct MyTableActions {
|
||||
static let DidEndDisplayingCell = "DidEndDisplayingCell"
|
||||
}
|
||||
|
||||
extension TableDirector {
|
||||
|
||||
public func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
|
||||
|
||||
invokeAction(.custom(kTableDirectorDidEndDisplayingCell), cell: cell, indexPath: indexPath)
|
||||
invoke(action: .custom(MyTableActions.DidEndDisplayingCell), cell: cell, indexPath: indexPath)
|
||||
}
|
||||
}
|
||||
```
|
||||
Catch your action with row builder:
|
||||
```swift
|
||||
let rowBuilder = TableConfigurableRowBuilder<User, MyTableViewCell>(items: users)
|
||||
.action(kTableDirectorDidEndDisplayingCell) { data -> Void in
|
||||
let rowBuilder = TableRowBuilder<User, MyTableViewCell>(items: users)
|
||||
.action(MyTableActions.DidEndDisplayingCell) { (data) -> Void in
|
||||
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'Tablet'
|
||||
s.version = '0.4.1'
|
||||
s.module_name = 'Tablet'
|
||||
|
||||
s.homepage = 'https://github.com/maxsokolov/tablet'
|
||||
s.summary = 'Powerful type-safe tool for UITableView. Swift 2.0 is required.'
|
||||
s.version = '0.5.0'
|
||||
|
||||
s.homepage = 'https://github.com/maxsokolov/Tablet.swift'
|
||||
s.summary = 'Powerful type-safe tool for UITableView. Swift 2.2 is required.'
|
||||
|
||||
s.author = { 'Max Sokolov' => 'i@maxsokolov.net' }
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
@ -11,6 +13,5 @@ Pod::Spec.new do |s|
|
|||
s.ios.deployment_target = '8.0'
|
||||
|
||||
s.source_files = 'Tablet/*.swift'
|
||||
s.module_name = 'Tablet'
|
||||
s.source = { :git => 'https://github.com/maxsokolov/tablet.git', :tag => s.version }
|
||||
s.source = { :git => 'https://github.com/maxsokolov/Tablet.swift.git', :tag => s.version }
|
||||
end
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -26,14 +26,14 @@ import Foundation
|
|||
*/
|
||||
public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
|
||||
|
||||
public private(set) weak var tableView: UITableView!
|
||||
public unowned let tableView: UITableView
|
||||
public weak var scrollDelegate: UIScrollViewDelegate?
|
||||
private var sections = [TableSectionBuilder]()
|
||||
|
||||
public init(tableView: UITableView) {
|
||||
super.init()
|
||||
|
||||
|
||||
self.tableView = tableView
|
||||
super.init()
|
||||
self.tableView.delegate = self
|
||||
self.tableView.dataSource = self
|
||||
|
||||
|
|
@ -41,7 +41,6 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate
|
|||
}
|
||||
|
||||
deinit {
|
||||
|
||||
NSNotificationCenter.defaultCenter().removeObserver(self)
|
||||
}
|
||||
|
||||
|
|
@ -61,10 +60,10 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate
|
|||
|
||||
// MARK: Public
|
||||
|
||||
public func invokeAction(action: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath) -> AnyObject? {
|
||||
public func invoke(action action: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath) -> AnyObject? {
|
||||
|
||||
let builder = builderAtIndexPath(indexPath)
|
||||
return builder.0.invokeAction(action, cell: cell, indexPath: indexPath, itemIndex: builder.1, userInfo: nil)
|
||||
return builder.0.invoke(action: action, cell: cell, indexPath: indexPath, itemIndex: builder.1, userInfo: nil)
|
||||
}
|
||||
|
||||
public override func respondsToSelector(selector: Selector) -> Bool {
|
||||
|
|
@ -75,130 +74,118 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate
|
|||
return scrollDelegate?.respondsToSelector(selector) == true ? scrollDelegate : super.forwardingTargetForSelector(selector)
|
||||
}
|
||||
|
||||
// MARK: Internal
|
||||
// MARK: - Internal -
|
||||
|
||||
func didReceiveAction(notification: NSNotification) {
|
||||
|
||||
if let action = notification.object as? Action, indexPath = tableView.indexPathForCell(action.cell) {
|
||||
|
||||
let builder = builderAtIndexPath(indexPath)
|
||||
builder.0.invokeAction(.custom(action.key), cell: action.cell, indexPath: indexPath, itemIndex: builder.1, userInfo: notification.userInfo)
|
||||
builder.0.invoke(action: .custom(action.key), cell: action.cell, indexPath: indexPath, itemIndex: builder.1, userInfo: notification.userInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension TableDirector {
|
||||
|
||||
|
||||
// MARK: UITableViewDataSource - configuration
|
||||
|
||||
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
|
||||
public func numberOfSectionsInTableView(tableView: UITableView) -> Int {
|
||||
return sections.count
|
||||
}
|
||||
|
||||
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return sections[section].numberOfRowsInSection
|
||||
}
|
||||
|
||||
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
||||
public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
||||
|
||||
let builder = builderAtIndexPath(indexPath)
|
||||
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier(builder.0.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()
|
||||
}
|
||||
|
||||
builder.0.invokeAction(.configure, cell: cell, indexPath: indexPath, itemIndex: builder.1, userInfo: nil)
|
||||
builder.0.invoke(action: .configure, cell: cell, indexPath: indexPath, itemIndex: builder.1, userInfo: nil)
|
||||
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
public extension TableDirector {
|
||||
|
||||
|
||||
// MARK: UITableViewDataSource - section setup
|
||||
|
||||
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
public func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
return sections[section].headerTitle
|
||||
}
|
||||
|
||||
func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
||||
public func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
||||
return sections[section].footerTitle
|
||||
}
|
||||
|
||||
// MARK: UITableViewDelegate - section setup
|
||||
|
||||
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
public func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
return sections[section].headerView
|
||||
}
|
||||
|
||||
func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
||||
public func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
||||
return sections[section].footerView
|
||||
}
|
||||
|
||||
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
return sections[section].headerHeight
|
||||
public func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
return sections[section].headerView?.frame.size.height ?? UITableViewAutomaticDimension
|
||||
}
|
||||
|
||||
func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
|
||||
return sections[section].footerHeight
|
||||
public func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
|
||||
return sections[section].footerView?.frame.size.height ?? UITableViewAutomaticDimension
|
||||
}
|
||||
}
|
||||
|
||||
public extension TableDirector {
|
||||
|
||||
|
||||
// MARK: UITableViewDelegate - actions
|
||||
|
||||
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
|
||||
|
||||
public func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
|
||||
return CGFloat(builderAtIndexPath(indexPath).0.estimatedRowHeight)
|
||||
}
|
||||
|
||||
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
|
||||
return invokeAction(.height, cell: nil, indexPath: indexPath) as? CGFloat ?? UITableViewAutomaticDimension
|
||||
public func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
|
||||
return invoke(action: .height, cell: nil, indexPath: indexPath) as? CGFloat ?? UITableViewAutomaticDimension
|
||||
}
|
||||
|
||||
/*func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
|
||||
public func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
|
||||
|
||||
return invokeAction(.willSelect, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath) as? NSIndexPath
|
||||
}*/
|
||||
|
||||
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
|
||||
|
||||
let cell = tableView.cellForRowAtIndexPath(indexPath)
|
||||
|
||||
if invokeAction(.click, cell: cell, indexPath: indexPath) != nil {
|
||||
if invoke(action: .click, cell: cell, indexPath: indexPath) != nil {
|
||||
tableView.deselectRowAtIndexPath(indexPath, animated: true)
|
||||
} else {
|
||||
invokeAction(.select, cell: cell, indexPath: indexPath)
|
||||
invoke(action: .select, cell: cell, indexPath: indexPath)
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
|
||||
invokeAction(.deselect, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath)
|
||||
public func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
|
||||
invoke(action: .deselect, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath)
|
||||
}
|
||||
|
||||
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
|
||||
invokeAction(.willDisplay, cell: cell, indexPath: indexPath)
|
||||
public func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
|
||||
invoke(action: .willDisplay, cell: cell, indexPath: indexPath)
|
||||
}
|
||||
|
||||
func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool {
|
||||
return invokeAction(.shouldHighlight, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath) as? Bool ?? true
|
||||
public func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool {
|
||||
return invoke(action: .shouldHighlight, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath) as? Bool ?? true
|
||||
}
|
||||
}
|
||||
|
||||
public extension TableDirector {
|
||||
|
||||
// MARK: Sections manipulation
|
||||
|
||||
|
||||
/*func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
|
||||
|
||||
return invokeAction(.willSelect, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath) as? NSIndexPath
|
||||
}*/
|
||||
|
||||
// MARK: - Sections manipulation -
|
||||
|
||||
public func append(section section: TableSectionBuilder) {
|
||||
append(sections: [section])
|
||||
}
|
||||
|
||||
public func append(sections sections: [TableSectionBuilder]) {
|
||||
|
||||
sections.forEach { $0.willMoveToDirector(tableView) }
|
||||
|
||||
sections.forEach { $0.tableDirector = self }
|
||||
self.sections.appendContentsOf(sections)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,157 +23,113 @@ import Foundation
|
|||
|
||||
public typealias ReturnValue = AnyObject?
|
||||
|
||||
enum ActionHandler<I, C> {
|
||||
|
||||
case actionBlock((data: ActionData<I, C>) -> Void)
|
||||
case actionReturnBlock((data: ActionData<I, C>) -> AnyObject?)
|
||||
|
||||
func invoke(data: ActionData<I, C>) -> ReturnValue {
|
||||
|
||||
enum ActionHandler<DataType, CellType> {
|
||||
|
||||
case Handler((data: ActionData<DataType, CellType>) -> Void)
|
||||
case ValueHandler((data: ActionData<DataType, CellType>) -> AnyObject?)
|
||||
|
||||
func invoke(data: ActionData<DataType, CellType>) -> ReturnValue {
|
||||
|
||||
switch (self) {
|
||||
case .actionBlock(let closure):
|
||||
closure(data: data)
|
||||
return true
|
||||
case .actionReturnBlock(let closure):
|
||||
return closure(data: data)
|
||||
case .Handler(let handler):
|
||||
handler(data: data)
|
||||
return nil
|
||||
case .ValueHandler(let handler):
|
||||
return handler(data: data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class RowBuilder : NSObject {
|
||||
public protocol RowBuilder {
|
||||
|
||||
public private(set) var reusableIdentifier: String
|
||||
public var numberOfRows: Int {
|
||||
return 0
|
||||
}
|
||||
public var estimatedRowHeight: Float {
|
||||
return 44
|
||||
}
|
||||
var reusableIdentifier: String { get }
|
||||
var numberOfRows: Int { get }
|
||||
var estimatedRowHeight: Float { get }
|
||||
|
||||
init(id: String) {
|
||||
reusableIdentifier = id
|
||||
}
|
||||
|
||||
// MARK: internal methods, must be overriden in subclass
|
||||
|
||||
func invokeAction(actionType: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int, userInfo: [NSObject: AnyObject]?) -> AnyObject? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerCell(inTableView tableView: UITableView) {
|
||||
}
|
||||
func invoke(action action: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int, userInfo: [NSObject: AnyObject]?) -> AnyObject?
|
||||
func registerCell(inTableView tableView: UITableView)
|
||||
}
|
||||
|
||||
/**
|
||||
Responsible for building cells of given type and passing items to them.
|
||||
*/
|
||||
public class TableRowBuilder<I, C where C: UITableViewCell> : RowBuilder {
|
||||
public class TableBaseRowBuilder<DataType, CellType where CellType: UITableViewCell> : RowBuilder {
|
||||
|
||||
private var actions = Dictionary<String, ActionHandler<I, C>>()
|
||||
private var items = [I]()
|
||||
private var actions = [String: ActionHandler<DataType, CellType>]()
|
||||
private var items = [DataType]()
|
||||
|
||||
public override var numberOfRows: Int {
|
||||
public let reusableIdentifier: String
|
||||
|
||||
public var numberOfRows: Int {
|
||||
return items.count
|
||||
}
|
||||
|
||||
public init(item: I, id: String? = nil) {
|
||||
super.init(id: id ?? NSStringFromClass(C).componentsSeparatedByString(".").last ?? "")
|
||||
|
||||
public var estimatedRowHeight: Float {
|
||||
return 44
|
||||
}
|
||||
|
||||
public init(item: DataType, id: String? = nil) {
|
||||
|
||||
reusableIdentifier = id ?? String(CellType)
|
||||
items.append(item)
|
||||
}
|
||||
|
||||
public init(items: [I]? = nil, id: String? = nil) {
|
||||
super.init(id: id ?? NSStringFromClass(C).componentsSeparatedByString(".").last ?? "")
|
||||
|
||||
if items != nil {
|
||||
self.items.appendContentsOf(items!)
|
||||
public init(items: [DataType]? = nil, id: String? = nil) {
|
||||
|
||||
reusableIdentifier = id ?? String(CellType)
|
||||
|
||||
if let items = items {
|
||||
self.items.appendContentsOf(items)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Chaining actions
|
||||
// MARK: - Chaining actions -
|
||||
|
||||
public func action(key: String, closure: (data: ActionData<I, C>) -> Void) -> Self {
|
||||
public func action(key: String, handler: (data: ActionData<DataType, CellType>) -> Void) -> Self {
|
||||
|
||||
actions[key] = .actionBlock(closure)
|
||||
actions[key] = .Handler(handler)
|
||||
return self
|
||||
}
|
||||
|
||||
public func action(actionType: ActionType, closure: (data: ActionData<I, C>) -> Void) -> Self {
|
||||
|
||||
actions[actionType.key] = .actionBlock(closure)
|
||||
public func action(type: ActionType, handler: (data: ActionData<DataType, CellType>) -> Void) -> Self {
|
||||
|
||||
actions[type.key] = .Handler(handler)
|
||||
return self
|
||||
}
|
||||
|
||||
public func action(actionType: ActionType, closure: (data: ActionData<I, C>) -> ReturnValue) -> Self {
|
||||
public func valueAction(type: ActionType, handler: (data: ActionData<DataType, CellType>) -> ReturnValue) -> Self {
|
||||
|
||||
actions[actionType.key] = .actionReturnBlock(closure)
|
||||
actions[type.key] = .ValueHandler(handler)
|
||||
return self
|
||||
}
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
override func invokeAction(actionType: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int, userInfo: [NSObject: AnyObject]?) -> AnyObject? {
|
||||
|
||||
public func invoke(action action: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int, userInfo: [NSObject: AnyObject]?) -> AnyObject? {
|
||||
|
||||
if let action = actions[actionType.key] {
|
||||
return action.invoke(ActionData(cell: cell as? C, indexPath: indexPath, item: items[itemIndex], itemIndex: itemIndex, userInfo: userInfo))
|
||||
if let action = actions[action.key] {
|
||||
return action.invoke(ActionData(cell: cell as? CellType, indexPath: indexPath, item: items[itemIndex], itemIndex: itemIndex, userInfo: userInfo))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
override func registerCell(inTableView tableView: UITableView) {
|
||||
public func registerCell(inTableView tableView: UITableView) {
|
||||
|
||||
if tableView.dequeueReusableCellWithIdentifier(reusableIdentifier) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
guard let resource = NSStringFromClass(C).componentsSeparatedByString(".").last else { return }
|
||||
|
||||
let bundle = NSBundle(forClass: C.self)
|
||||
let resource = String(CellType)
|
||||
let bundle = NSBundle(forClass: CellType.self)
|
||||
|
||||
if let _ = bundle.pathForResource(resource, ofType: "nib") { // existing cell
|
||||
|
||||
tableView.registerNib(UINib(nibName: resource, bundle: bundle), forCellReuseIdentifier: reusableIdentifier)
|
||||
|
||||
} else {
|
||||
|
||||
tableView.registerClass(C.self, forCellReuseIdentifier: reusableIdentifier)
|
||||
tableView.registerClass(CellType.self, forCellReuseIdentifier: reusableIdentifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Responsible for building configurable cells of given type and passing items to them.
|
||||
*/
|
||||
public class TableConfigurableRowBuilder<I, C: ConfigurableCell where C.T == I, C: UITableViewCell> : TableRowBuilder<I, C> {
|
||||
|
||||
public override var estimatedRowHeight: Float {
|
||||
return C.estimatedHeight()
|
||||
}
|
||||
// MARK: - Items manipulation -
|
||||
|
||||
public init(item: I) {
|
||||
super.init(item: item, id: C.reusableIdentifier())
|
||||
}
|
||||
|
||||
public init(items: [I]? = nil) {
|
||||
super.init(items: items, id: C.reusableIdentifier())
|
||||
}
|
||||
|
||||
override func invokeAction(actionType: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int, userInfo: [NSObject: AnyObject]?) -> AnyObject? {
|
||||
|
||||
switch actionType {
|
||||
case .configure:
|
||||
(cell as? C)?.configure(items[itemIndex])
|
||||
default: break
|
||||
}
|
||||
return super.invokeAction(actionType, cell: cell, indexPath: indexPath, itemIndex: itemIndex, userInfo: userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
public extension TableRowBuilder {
|
||||
|
||||
// MARK: Items manipulation
|
||||
|
||||
public func append(items items: [I]) {
|
||||
public func append(items items: [DataType]) {
|
||||
self.items.appendContentsOf(items)
|
||||
}
|
||||
|
||||
|
|
@ -182,10 +138,36 @@ public extension TableRowBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
public func +=<I, C>(left: TableRowBuilder<I, C>, right: I) {
|
||||
/**
|
||||
Responsible for building configurable cells of given type and passing items to them.
|
||||
*/
|
||||
public class TableRowBuilder<DataType, CellType: ConfigurableCell where CellType.T == DataType, CellType: UITableViewCell> : TableBaseRowBuilder<DataType, CellType> {
|
||||
|
||||
public override var estimatedRowHeight: Float {
|
||||
return CellType.estimatedHeight()
|
||||
}
|
||||
|
||||
public init(item: DataType) {
|
||||
super.init(item: item, id: CellType.reusableIdentifier())
|
||||
}
|
||||
|
||||
public init(items: [DataType]? = nil) {
|
||||
super.init(items: items, id: CellType.reusableIdentifier())
|
||||
}
|
||||
|
||||
public override func invoke(action action: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int, userInfo: [NSObject: AnyObject]?) -> AnyObject? {
|
||||
|
||||
if case .configure = action {
|
||||
(cell as? CellType)?.configure(items[itemIndex])
|
||||
}
|
||||
return super.invoke(action: action, cell: cell, indexPath: indexPath, itemIndex: itemIndex, userInfo: userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
public func +=<DataType, CellType>(left: TableBaseRowBuilder<DataType, CellType>, right: DataType) {
|
||||
left.append(items: [right])
|
||||
}
|
||||
|
||||
public func +=<I, C>(left: TableRowBuilder<I, C>, right: [I]) {
|
||||
public func +=<DataType, CellType>(left: TableBaseRowBuilder<DataType, CellType>, right: [DataType]) {
|
||||
left.append(items: right)
|
||||
}
|
||||
|
|
@ -26,44 +26,49 @@ import Foundation
|
|||
Can host several row builders.
|
||||
*/
|
||||
public class TableSectionBuilder {
|
||||
|
||||
weak var tableView: UITableView?
|
||||
private var builders = [RowBuilder]()
|
||||
|
||||
weak var tableDirector: TableDirector? {
|
||||
didSet {
|
||||
guard let director = tableDirector else { return }
|
||||
builders.forEach { $0.registerCell(inTableView: director.tableView) }
|
||||
}
|
||||
}
|
||||
|
||||
private var builders = [RowBuilder]()
|
||||
|
||||
public var headerTitle: String?
|
||||
public var footerTitle: String?
|
||||
|
||||
public var headerView: UIView?
|
||||
public var headerHeight: CGFloat = UITableViewAutomaticDimension
|
||||
|
||||
public var footerView: UIView?
|
||||
public var footerHeight: CGFloat = UITableViewAutomaticDimension
|
||||
|
||||
public private(set) var headerView: UIView?
|
||||
public private(set) var footerView: UIView?
|
||||
|
||||
/// A total number of rows in section of each row builder.
|
||||
public var numberOfRowsInSection: Int {
|
||||
return builders.reduce(0) { $0 + $1.numberOfRows }
|
||||
}
|
||||
|
||||
public init(headerTitle: String? = nil, footerTitle: String? = nil, rows: [RowBuilder]? = nil) {
|
||||
|
||||
self.headerTitle = headerTitle
|
||||
self.footerTitle = footerTitle
|
||||
|
||||
public init(rows: [RowBuilder]? = nil) {
|
||||
|
||||
if let initialRows = rows {
|
||||
builders.appendContentsOf(initialRows)
|
||||
}
|
||||
}
|
||||
|
||||
public init(headerView: UIView? = nil, headerHeight: CGFloat = UITableViewAutomaticDimension, footerView: UIView? = nil, footerHeight: CGFloat = UITableViewAutomaticDimension) {
|
||||
|
||||
self.headerView = headerView
|
||||
self.headerHeight = headerHeight
|
||||
public convenience init(headerTitle: String?, footerTitle: String?, rows: [RowBuilder]?) {
|
||||
self.init(rows: rows)
|
||||
|
||||
self.footerView = footerView
|
||||
self.footerHeight = footerHeight
|
||||
self.headerTitle = headerTitle
|
||||
self.footerTitle = footerTitle
|
||||
}
|
||||
|
||||
// MARK: Public
|
||||
public convenience init(headerView: UIView?, footerView: UIView?, rows: [RowBuilder]?) {
|
||||
self.init(rows: rows)
|
||||
|
||||
self.headerView = headerView
|
||||
self.footerView = footerView
|
||||
}
|
||||
|
||||
// MARK: - Public -
|
||||
|
||||
public func clear() {
|
||||
builders.removeAll()
|
||||
|
|
@ -75,11 +80,11 @@ public class TableSectionBuilder {
|
|||
|
||||
public func append(rows rows: [RowBuilder]) {
|
||||
|
||||
if let tableView = tableView { rows.forEach { $0.registerCell(inTableView: tableView) } }
|
||||
if let tableView = tableDirector?.tableView { rows.forEach { $0.registerCell(inTableView: tableView) } }
|
||||
builders.appendContentsOf(rows)
|
||||
}
|
||||
|
||||
// MARK: Internal
|
||||
// MARK: - Internal -
|
||||
|
||||
func builderAtIndex(index: Int) -> (RowBuilder, Int)? {
|
||||
|
||||
|
|
@ -93,12 +98,6 @@ public class TableSectionBuilder {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func willMoveToDirector(tableView: UITableView) {
|
||||
|
||||
self.tableView = tableView
|
||||
self.builders.forEach { $0.registerCell(inTableView: tableView) }
|
||||
}
|
||||
}
|
||||
|
||||
public func +=(left: TableSectionBuilder, right: RowBuilder) {
|
||||
|
|
|
|||
|
|
@ -51,15 +51,15 @@ public enum ActionType {
|
|||
}
|
||||
}
|
||||
|
||||
public class ActionData<I, C> {
|
||||
public class ActionData<DataType, CellType> {
|
||||
|
||||
public let cell: C?
|
||||
public let item: I
|
||||
public let cell: CellType?
|
||||
public let item: DataType
|
||||
public let itemIndex: Int
|
||||
public let indexPath: NSIndexPath
|
||||
public let userInfo: [NSObject: AnyObject]?
|
||||
|
||||
init(cell: C?, indexPath: NSIndexPath, item: I, itemIndex: Int, userInfo: [NSObject: AnyObject]?) {
|
||||
init(cell: CellType?, indexPath: NSIndexPath, item: DataType, itemIndex: Int, userInfo: [NSObject: AnyObject]?) {
|
||||
|
||||
self.cell = cell
|
||||
self.indexPath = indexPath
|
||||
|
|
@ -112,6 +112,6 @@ public protocol ConfigurableCell {
|
|||
public extension ConfigurableCell where Self: UITableViewCell {
|
||||
|
||||
static func reusableIdentifier() -> String {
|
||||
return NSStringFromClass(self).componentsSeparatedByString(".").last ?? ""
|
||||
return String(self)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// HeaderFooterController.swift
|
||||
// TabletDemo
|
||||
//
|
||||
// Created by Max Sokolov on 16/04/16.
|
||||
// Copyright © 2016 Tablet. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Tablet
|
||||
|
||||
class HeaderFooterController: UIViewController {
|
||||
|
||||
@IBOutlet weak var tableView: UITableView! {
|
||||
didSet {
|
||||
tableDirector = TableDirector(tableView: tableView)
|
||||
}
|
||||
}
|
||||
var tableDirector: TableDirector!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
let rows = TableRowBuilder<String, StoryboardTableViewCell>(items: ["3", "4", "5"])
|
||||
|
||||
let headerView = UIView(frame: CGRectMake(0, 0, 100, 100))
|
||||
headerView.backgroundColor = UIColor.lightGrayColor()
|
||||
|
||||
let section = TableSectionBuilder(headerView: headerView, footerView: nil, rows: [rows])
|
||||
|
||||
tableDirector += section
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// MainController.swift
|
||||
// TabletDemo
|
||||
//
|
||||
// Created by Max Sokolov on 16/04/16.
|
||||
// Copyright © 2016 Tablet. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Tablet
|
||||
|
||||
class MainController: UIViewController {
|
||||
|
||||
@IBOutlet weak var tableView: UITableView! {
|
||||
didSet {
|
||||
tableDirector = TableDirector(tableView: tableView)
|
||||
}
|
||||
}
|
||||
var tableDirector: TableDirector!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
let rows = TableRowBuilder<String, StoryboardTableViewCell>(items: ["1", "2", "3"])
|
||||
.action(.click) { [unowned self] e in
|
||||
self.performSegueWithIdentifier("headerfooter", sender: nil)
|
||||
}
|
||||
|
||||
tableDirector += rows
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
//
|
||||
// MainViewController.swift
|
||||
// TabletDemo
|
||||
//
|
||||
// Created by Max Sokolov on 19/03/16.
|
||||
// Copyright © 2016 Tablet. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Tablet
|
||||
|
||||
class MainViewController : UITableViewController {
|
||||
|
||||
var tableDirector: TableDirector!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
tableDirector = TableDirector(tableView: tableView)
|
||||
|
||||
tableDirector += TableRowBuilder<Int, UITableViewCell>(items: [1, 2, 3, 4], id: "cell")
|
||||
.action(.configure) { data -> Void in
|
||||
|
||||
data.cell?.accessoryType = .DisclosureIndicator
|
||||
data.cell?.textLabel?.text = "\(data.item)"
|
||||
}
|
||||
.action(.click) { data -> Void in
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// StoryboardTableViewCell.swift
|
||||
// TabletDemo
|
||||
//
|
||||
// Created by Max Sokolov on 16/04/16.
|
||||
// Copyright © 2016 Tablet. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Tablet
|
||||
|
||||
class StoryboardTableViewCell: UITableViewCell, ConfigurableCell {
|
||||
|
||||
typealias T = String
|
||||
|
||||
func configure(value: T) {
|
||||
textLabel?.text = value
|
||||
}
|
||||
|
||||
static func estimatedHeight() -> Float {
|
||||
return 44
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15B42" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="dOU-ON-YYD">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10116" systemVersion="15B42" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="dOU-ON-YYD">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Main View Controller-->
|
||||
<scene sceneID="oP2-EZ-N5W">
|
||||
<objects>
|
||||
<tableViewController id="ZyD-Ww-nfe" customClass="MainViewController" customModule="TabletDemo" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" id="xii-6e-J4f">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="cell" id="eQJ-7O-k03">
|
||||
<rect key="frame" x="0.0" y="92" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="eQJ-7O-k03" id="Ufi-z4-pLt">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="ZyD-Ww-nfe" id="dup-HU-FfF"/>
|
||||
<outlet property="delegate" destination="ZyD-Ww-nfe" id="bRX-uu-KTX"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" id="6ba-xC-prc"/>
|
||||
</tableViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="uxV-eY-exN" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1074" y="287"/>
|
||||
</scene>
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="Nb3-Yi-ik8">
|
||||
<objects>
|
||||
|
|
@ -45,12 +16,101 @@
|
|||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
<segue destination="ZyD-Ww-nfe" kind="relationship" relationship="rootViewController" id="vRk-x4-ylh"/>
|
||||
<segue destination="grv-aL-Qbb" kind="relationship" relationship="rootViewController" id="0SZ-hs-iUi"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="1px-T5-UXL" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="334" y="287"/>
|
||||
</scene>
|
||||
<!--Main Controller-->
|
||||
<scene sceneID="bgC-Xq-OSw">
|
||||
<objects>
|
||||
<viewController id="grv-aL-Qbb" customClass="MainController" customModule="TabletDemo" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="COn-EH-LKP"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="iga-ib-rj1"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="5uw-uC-8lc">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="o67-xJ-fPW">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="StoryboardTableViewCell" id="nE5-Y5-OFf" customClass="StoryboardTableViewCell" customModule="TabletDemo" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="92" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="nE5-Y5-OFf" id="3yF-sl-yNq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
</tableView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="iga-ib-rj1" firstAttribute="top" secondItem="o67-xJ-fPW" secondAttribute="bottom" id="jrJ-xl-S4k"/>
|
||||
<constraint firstItem="o67-xJ-fPW" firstAttribute="top" secondItem="5uw-uC-8lc" secondAttribute="top" id="ntC-Lp-77v"/>
|
||||
<constraint firstAttribute="trailing" secondItem="o67-xJ-fPW" secondAttribute="trailing" id="s2g-E1-S4V"/>
|
||||
<constraint firstItem="o67-xJ-fPW" firstAttribute="leading" secondItem="5uw-uC-8lc" secondAttribute="leading" id="uWa-n0-GSQ"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="HPV-jJ-NPc"/>
|
||||
<connections>
|
||||
<outlet property="tableView" destination="o67-xJ-fPW" id="A8B-MV-tNa"/>
|
||||
<segue destination="sSs-TX-Ch0" kind="show" identifier="headerfooter" id="Nbk-od-yC5"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="xij-Hw-J33" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1042" y="287"/>
|
||||
</scene>
|
||||
<!--Header Footer Controller-->
|
||||
<scene sceneID="Jd0-of-RF9">
|
||||
<objects>
|
||||
<viewController id="sSs-TX-Ch0" customClass="HeaderFooterController" customModule="TabletDemo" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="s9s-Gu-3M6"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="Tux-up-dnH"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="CB5-10-J2D">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="uLr-Ff-utK">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="StoryboardTableViewCell" id="lqW-2N-6mf" customClass="StoryboardTableViewCell" customModule="TabletDemo" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="92" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="lqW-2N-6mf" id="tjN-ow-437">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
</tableView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="Tux-up-dnH" firstAttribute="top" secondItem="uLr-Ff-utK" secondAttribute="bottom" id="0jr-6S-PFB"/>
|
||||
<constraint firstItem="uLr-Ff-utK" firstAttribute="top" secondItem="CB5-10-J2D" secondAttribute="top" id="KYu-01-hlH"/>
|
||||
<constraint firstAttribute="trailing" secondItem="uLr-Ff-utK" secondAttribute="trailing" id="TKR-R1-lBY"/>
|
||||
<constraint firstItem="uLr-Ff-utK" firstAttribute="leading" secondItem="CB5-10-J2D" secondAttribute="leading" id="aG6-R3-QUr"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="ogA-Id-clb"/>
|
||||
<connections>
|
||||
<outlet property="tableView" destination="uLr-Ff-utK" id="olP-LV-VmT"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Gim-O8-I3d" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1763" y="287"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@
|
|||
DAC2D5CA1C9D303E009E9C19 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC2D5C91C9D303E009E9C19 /* AppDelegate.swift */; };
|
||||
DAC2D5CF1C9D30A7009E9C19 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DAC2D5CD1C9D30A7009E9C19 /* Main.storyboard */; };
|
||||
DAC2D5D01C9D30A7009E9C19 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DAC2D5CE1C9D30A7009E9C19 /* LaunchScreen.storyboard */; };
|
||||
DAC2D5D41C9D3118009E9C19 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC2D5D31C9D3118009E9C19 /* MainViewController.swift */; };
|
||||
DAC2D69C1C9E75E3009E9C19 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DAC2D69B1C9E75E3009E9C19 /* Assets.xcassets */; };
|
||||
DACB71761CC2D63D00432BD3 /* MainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DACB71751CC2D63D00432BD3 /* MainController.swift */; };
|
||||
DACB71781CC2D6ED00432BD3 /* StoryboardTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DACB71771CC2D6ED00432BD3 /* StoryboardTableViewCell.swift */; };
|
||||
DACB717A1CC2D89D00432BD3 /* HeaderFooterController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DACB71791CC2D89D00432BD3 /* HeaderFooterController.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
|
@ -19,9 +21,11 @@
|
|||
DAC2D5C91C9D303E009E9C19 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
DAC2D5CD1C9D30A7009E9C19 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
|
||||
DAC2D5CE1C9D30A7009E9C19 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
DAC2D5D31C9D3118009E9C19 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
|
||||
DAC2D69B1C9E75E3009E9C19 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
DAC2D69D1C9E78B5009E9C19 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
DACB71751CC2D63D00432BD3 /* MainController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainController.swift; sourceTree = "<group>"; };
|
||||
DACB71771CC2D6ED00432BD3 /* StoryboardTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoryboardTableViewCell.swift; sourceTree = "<group>"; };
|
||||
DACB71791CC2D89D00432BD3 /* HeaderFooterController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderFooterController.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
|
@ -64,7 +68,8 @@
|
|||
DAC2D5C71C9D3005009E9C19 /* Presentation */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DAC2D5D11C9D30D8009E9C19 /* Main */,
|
||||
DACB71731CC2D5ED00432BD3 /* Controllers */,
|
||||
DACB71741CC2D5FD00432BD3 /* Views */,
|
||||
);
|
||||
path = Presentation;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -96,22 +101,6 @@
|
|||
path = Storyboards;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DAC2D5D11C9D30D8009E9C19 /* Main */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DAC2D5D21C9D30E4009E9C19 /* ViewControllers */,
|
||||
);
|
||||
path = Main;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DAC2D5D21C9D30E4009E9C19 /* ViewControllers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DAC2D5D31C9D3118009E9C19 /* MainViewController.swift */,
|
||||
);
|
||||
path = ViewControllers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DAC2D69A1C9E75BE009E9C19 /* Assets */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -120,6 +109,23 @@
|
|||
path = Assets;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DACB71731CC2D5ED00432BD3 /* Controllers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DACB71751CC2D63D00432BD3 /* MainController.swift */,
|
||||
DACB71791CC2D89D00432BD3 /* HeaderFooterController.swift */,
|
||||
);
|
||||
path = Controllers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DACB71741CC2D5FD00432BD3 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DACB71771CC2D6ED00432BD3 /* StoryboardTableViewCell.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
|
|
@ -192,8 +198,10 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DAC2D5D41C9D3118009E9C19 /* MainViewController.swift in Sources */,
|
||||
DACB71781CC2D6ED00432BD3 /* StoryboardTableViewCell.swift in Sources */,
|
||||
DACB71761CC2D63D00432BD3 /* MainController.swift in Sources */,
|
||||
DAC2D5CA1C9D303E009E9C19 /* AppDelegate.swift in Sources */,
|
||||
DACB717A1CC2D89D00432BD3 /* HeaderFooterController.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ class TabletTests: XCTestCase {
|
|||
|
||||
let source = ["1", "2", "3"]
|
||||
|
||||
let rows = TableRowBuilder<String, UITableViewCell>(items: source)
|
||||
let rows = TableBaseRowBuilder<String, UITableViewCell>(items: source)
|
||||
.action(.configure) { data -> Void in
|
||||
|
||||
XCTAssertNotNil(data.cell, "Action should have a cell")
|
||||
|
|
@ -121,7 +121,7 @@ class TabletTests: XCTestCase {
|
|||
let testData = TestData(title: "title")
|
||||
|
||||
testController.view.hidden = false
|
||||
testController.tableDirector += TableConfigurableRowBuilder<TestData, TestTableViewCell>(item: testData)
|
||||
testController.tableDirector += TableRowBuilder<TestData, TestTableViewCell>(item: testData)
|
||||
testController.tableView.reloadData()
|
||||
|
||||
let cell = testController.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0)) as? TestTableViewCell
|
||||
|
|
@ -132,7 +132,7 @@ class TabletTests: XCTestCase {
|
|||
|
||||
func testSectionBuilderCreatesSectionWithHeaderAndFooterTitles() {
|
||||
|
||||
let row = TableConfigurableRowBuilder<TestData, TestTableViewCell>(items: [TestData(title: "title")])
|
||||
let row = TableRowBuilder<TestData, TestTableViewCell>(items: [TestData(title: "title")])
|
||||
|
||||
let sectionHeaderTitle = "Header Title"
|
||||
let sectionFooterTitle = "Footer Title"
|
||||
|
|
@ -152,12 +152,12 @@ class TabletTests: XCTestCase {
|
|||
|
||||
func testSectionBuilderCreatesSectionWithHeaderAndFooterViews() {
|
||||
|
||||
let row = TableConfigurableRowBuilder<TestData, TestTableViewCell>(items: [TestData(title: "title")])
|
||||
let row = TableRowBuilder<TestData, TestTableViewCell>(items: [TestData(title: "title")])
|
||||
|
||||
let sectionHeaderView = UIView()
|
||||
let sectionFooterView = UIView()
|
||||
|
||||
let section = TableSectionBuilder(headerView: sectionHeaderView, headerHeight: 44, footerView: sectionFooterView, footerHeight: 44)
|
||||
let section = TableSectionBuilder(headerView: sectionHeaderView, footerView: sectionFooterView, rows: nil)
|
||||
section += row
|
||||
|
||||
testController.view.hidden = false
|
||||
|
|
@ -175,7 +175,7 @@ class TabletTests: XCTestCase {
|
|||
|
||||
let expectation = expectationWithDescription("cell action")
|
||||
|
||||
let row = TableConfigurableRowBuilder<TestData, TestTableViewCell>(items: [TestData(title: "title")])
|
||||
let row = TableRowBuilder<TestData, TestTableViewCell>(items: [TestData(title: "title")])
|
||||
.action(TestTableViewCellOptions.CellAction) { data -> Void in
|
||||
|
||||
XCTAssertNotNil(data.cell, "Action data should have a cell")
|
||||
|
|
|
|||
Loading…
Reference in New Issue