diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..771048b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015 Max Sokolov https://twitter.com/max_sokolov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
index 240f65f..d23abe9 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,177 @@
-# Tablet
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+Tablet is a super lightweight yet powerful generic library that handles a complexity of UITableView's datasource and delegate methods in a Swift environment. Tablet's goal is to provide an easiest way to create complex table views. With Tablet you don't have to write a messy code of `switch` or `if` statements when you deal with bunch of different cells in different sections.
+
+That's almost all you need in your controller to build a bunch of cells in a section:
+```swift
+TableConfigurableRowBuilder(items: ["1", "2", "3", "4", "5"], estimatedRowHeight: 42)
+```
+Tablet respects cells reusability feature and it's type-safe. See the Usage section to learn more.
+
+## Requirements
+
+- iOS 8.0+
+- Xcode 7.1+
+
+## Installation
+
+### CocoaPods
+To integrate Tablet into your Xcode project using CocoaPods, specify it in your `Podfile`:
+
+```ruby
+source 'https://github.com/CocoaPods/Specs.git'
+platform :ios, '8.0'
+use_frameworks!
+
+pod 'Tablet'
+```
+
+Then, run the following command:
+
+```bash
+$ 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(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", rowBuilders: [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:
+
+```swift
+import Tablet
+
+class MyTableViewCell : UITableViewCell, ConfigurableCell {
+
+ typealias Item = User
+
+ static func reusableIdentifier() -> String {
+ return "reusable_id"
+ }
+
+ func configureWithItem(item: Item) { // item is user here
+
+ textLabel?.text = item.username
+ detailTextLabel?.text = item.isActive ? "Active" : "Inactive"
+ }
+}
+```
+Once you've implemented the protocol, simply use the `TableConfigurableRowBuilder` to build cells:
+
+```swift
+import Tablet
+
+let rowBuilder = TableConfigurableRowBuilder(estimatedRowHeight: 42)
+rowBuilder.appendItems(users)
+
+director = TableDirector(tableView: tableView)
+tableDirector.appendSection(TableSectionBuilder(rowBuilders: [rowBuilder]))
+```
+
+### Cell actions
+
+Tablet provides a chaining approach to handle actions from your cells:
+
+```swift
+import Tablet
+
+let rowBuilder = TableRowBuilder(items: [user1, user2, user3], id: "reusable_id")
+ .action(.configure) { data in
+
+ }
+ .action(.click) { data in
+
+ }
+ .action(.shouldHighlight) { data in
+
+ return false
+ }
+```
+
+### Custom cell actions
+```swift
+import Tablet
+
+let kMyAction = "action_key"
+
+class MyTableViewCell : UITableViewCell {
+
+ @IBAction func buttonClicked(sender: UIButton) {
+
+ Action(key: kMyAction, sender: self, userInfo: nil).perform()
+ }
+}
+```
+And receive this actions with your row builder:
+```swift
+import Tablet
+
+let rowBuilder = TableConfigurableRowBuilder(items: users, id: "reusable_id", estimatedRowHeight: 42)
+ .action(.click) { data in
+
+ }
+ .action(.willDisplay) { data in
+
+ }
+ .action(kMyAction) { data in
+
+ }
+```
+
+## 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:
+```swift
+import Tablet
+
+let kTableDirectorDidEndDisplayingCell = "enddisplaycell"
+
+extension TableDirector {
+
+ public func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
+
+ performAction(.custom(kTableDirectorDidEndDisplayingCell), cell: cell, indexPath: indexPath)
+ }
+}
+```
+Catch your action with row builder:
+```swift
+let rowBuilder = TableConfigurableRowBuilder(items: users, estimatedRowHeight: 42)
+ .action(kTableDirectorDidEndDisplayingCell) { data in
+
+ }
+```
+You could also perform an action that returns a value.
+
+## License
+
+Tablet is available under the MIT license. See LICENSE for details.
\ No newline at end of file
diff --git a/Tablet.podspec b/Tablet.podspec
new file mode 100644
index 0000000..de7a9f9
--- /dev/null
+++ b/Tablet.podspec
@@ -0,0 +1,16 @@
+Pod::Spec.new do |s|
+ s.name = 'Tablet'
+ s.version = '0.1.0'
+
+ s.homepage = 'https://github.com/maxsokolov/tablet'
+ s.summary = 'Powerful type-safe tool for UITableView. Swift 2.0 is required.'
+
+ s.author = { 'Max Sokolov' => 'i@maxsokolov.net' }
+ s.license = { :type => 'MIT', :file => 'LICENSE' }
+ s.platforms = { :ios => '8.0' }
+ 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 }
+end
\ No newline at end of file
diff --git a/Tablet/TableDirector.swift b/Tablet/TableDirector.swift
new file mode 100644
index 0000000..30c6daf
--- /dev/null
+++ b/Tablet/TableDirector.swift
@@ -0,0 +1,187 @@
+//
+// Copyright (c) 2015 Max Sokolov https://twitter.com/max_sokolov
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import UIKit
+import Foundation
+
+/**
+ Responsible for table view's datasource and delegate.
+ */
+public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
+
+ private weak var tableView: UITableView!
+ private var sections = [TableSectionBuilder]()
+
+ public init(tableView: UITableView) {
+ super.init()
+
+ self.tableView = tableView
+ self.tableView.delegate = self
+ self.tableView.dataSource = self
+
+ NSNotificationCenter.defaultCenter().addObserver(self, selector: "didReceiveAction:", name: kActionPerformedNotificationKey, object: nil)
+ }
+
+ deinit {
+
+ NSNotificationCenter.defaultCenter().removeObserver(self)
+ }
+
+ // MARK: Sections manipulation
+
+ public func appendSection(section: TableSectionBuilder) {
+ sections.append(section)
+ }
+
+ public func appendSections(sections: [TableSectionBuilder]) {
+ self.sections.appendContentsOf(sections)
+ }
+
+ // MARK: Private methods
+
+ /**
+ Find a row builder that responsible for building a row from cell with given item type.
+
+ - Parameters:
+ - indexPath: path of cell to dequeue
+
+ - Returns: A touple - (builder, builderItemIndex)
+ */
+ private func builderAtIndexPath(indexPath: NSIndexPath) -> (RowBuilder, Int) {
+
+ return sections[indexPath.section].builderAtIndex(indexPath.row)!
+ }
+
+ public func performAction(action: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath) -> AnyObject? {
+
+ let builder = builderAtIndexPath(indexPath)
+ return builder.0.performAction(action, cell: cell, indexPath: indexPath, itemIndex: builder.1)
+ }
+
+ internal func didReceiveAction(notification: NSNotification) {
+
+ if let action = notification.object as? Action, indexPath = tableView.indexPathForCell(action.cell) {
+
+ let builder = builderAtIndexPath(indexPath)
+ builder.0.performAction(.custom(action.key), cell: action.cell, indexPath: indexPath, itemIndex: builder.1)
+ }
+ }
+
+ // MARK: UITableViewDataSource - configuration
+
+ public func numberOfSectionsInTableView(tableView: UITableView) -> Int {
+
+ return sections.count
+ }
+
+ public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ return sections[section].numberOfRowsInSection
+ }
+
+ public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
+
+ let builder = builderAtIndexPath(indexPath)
+
+ let cell = tableView.dequeueReusableCellWithIdentifier(builder.0.reusableIdentifier, forIndexPath: indexPath)
+
+ builder.0.performAction(.configure, cell: cell, indexPath: indexPath, itemIndex: builder.1)
+
+ return cell
+ }
+}
+
+extension TableDirector {
+
+ // MARK: UITableViewDataSource - section setup
+
+ public func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+
+ return sections[section].headerTitle
+ }
+
+ public func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? {
+
+ return sections[section].footerTitle
+ }
+
+ // MARK: UITableViewDelegate - section setup
+
+ public func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+
+ return sections[section].headerView
+ }
+
+ public func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
+
+ return sections[section].footerView
+ }
+
+ public func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+
+ return sections[section].headerHeight
+ }
+
+ public func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
+
+ return sections[section].footerHeight
+ }
+}
+
+extension TableDirector {
+
+ // MARK: UITableViewDelegate - actions
+
+ public func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
+
+ return builderAtIndexPath(indexPath).0.estimatedRowHeight
+ }
+
+ public func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
+
+ return performAction(.height, cell: nil, indexPath: indexPath) as? CGFloat ?? UITableViewAutomaticDimension
+ }
+
+ public func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
+
+ let cell = tableView.cellForRowAtIndexPath(indexPath)
+
+ if performAction(.click, cell: cell, indexPath: indexPath) != nil {
+ tableView.deselectRowAtIndexPath(indexPath, animated: true)
+ } else {
+ performAction(.select, cell: cell, indexPath: indexPath)
+ }
+ }
+
+ public func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
+
+ performAction(.deselect, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath)
+ }
+
+ public func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
+
+ performAction(.willDisplay, cell: cell, indexPath: indexPath)
+ }
+
+ public func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool {
+
+ return performAction(.shouldHighlight, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath) as? Bool ?? true
+ }
+}
\ No newline at end of file
diff --git a/Tablet/TableRowBuilder.swift b/Tablet/TableRowBuilder.swift
new file mode 100644
index 0000000..edd2854
--- /dev/null
+++ b/Tablet/TableRowBuilder.swift
@@ -0,0 +1,141 @@
+//
+// Copyright (c) 2015 Max Sokolov https://twitter.com/max_sokolov
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import UIKit
+import Foundation
+
+public typealias ReturnValue = AnyObject?
+
+internal enum ActionHandler {
+
+ case actionBlock((data: ActionData) -> Void)
+ case actionReturnBlock((data: ActionData) -> AnyObject?)
+
+ func call(data: ActionData) -> AnyObject? {
+
+ switch (self) {
+ case .actionBlock(let closure):
+ closure(data: data)
+ return true
+ case .actionReturnBlock(let closure):
+ return closure(data: data)
+ }
+ }
+}
+
+/**
+ Responsible for building cells of given type and passing items to them.
+*/
+public class TableRowBuilder : RowBuilder {
+
+ private var actions = Dictionary>()
+ private var items = [I]()
+
+ public var reusableIdentifier: String
+ public var estimatedRowHeight: CGFloat
+ public var numberOfRows: Int {
+ get {
+ return items.count
+ }
+ }
+
+ public init(item: I, id: String, estimatedRowHeight: CGFloat = UITableViewAutomaticDimension) {
+
+ reusableIdentifier = id
+ self.estimatedRowHeight = estimatedRowHeight
+ items.append(item)
+ }
+
+ public init(items: [I]? = nil, id: String, estimatedRowHeight: CGFloat = UITableViewAutomaticDimension) {
+
+ reusableIdentifier = id
+ self.estimatedRowHeight = estimatedRowHeight
+
+ if items != nil {
+ self.items.appendContentsOf(items!)
+ }
+ }
+
+ // MARK: Items manipulation
+
+ public func appendItems(items: [I]) {
+
+ self.items.appendContentsOf(items)
+ }
+
+ public func clear() {
+
+ items.removeAll()
+ }
+
+ // MARK: Chaining actions
+
+ public func action(key: String, closure: (data: ActionData) -> Void) -> Self {
+
+ actions[key] = .actionBlock(closure)
+ return self
+ }
+
+ public func action(actionType: ActionType, closure: (data: ActionData) -> Void) -> Self {
+
+ actions[actionType.key] = .actionBlock(closure)
+ return self
+ }
+
+ public func action(actionType: ActionType, closure: (data: ActionData) -> ReturnValue) -> Self {
+
+ actions[actionType.key] = .actionReturnBlock(closure)
+ return self
+ }
+
+ // MARK: Triggers
+
+ public func performAction(actionType: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int) -> AnyObject? {
+
+ if let action = actions[actionType.key] {
+ return action.call(ActionData(cell: cell as? C, indexPath: indexPath, item: items[itemIndex], itemIndex: itemIndex))
+ }
+ return nil
+ }
+}
+
+/**
+ Responsible for building configurable cells of given type and passing items to them.
+*/
+public class TableConfigurableRowBuilder : TableRowBuilder {
+
+ public init(item: I, estimatedRowHeight: CGFloat = UITableViewAutomaticDimension) {
+ super.init(item: item, id: C.reusableIdentifier(), estimatedRowHeight: estimatedRowHeight)
+ }
+
+ public init(items: [I]? = nil, estimatedRowHeight: CGFloat = UITableViewAutomaticDimension) {
+ super.init(items: items, id: C.reusableIdentifier(), estimatedRowHeight: estimatedRowHeight)
+ }
+
+ public override func performAction(actionType: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int) -> AnyObject? {
+
+ switch actionType {
+ case .configure:
+ (cell as? C)?.configureWithItem(items[itemIndex])
+ default: break
+ }
+ return super.performAction(actionType, cell: cell, indexPath: indexPath, itemIndex: itemIndex)
+ }
+}
diff --git a/Tablet/TableSectionBuilder.swift b/Tablet/TableSectionBuilder.swift
new file mode 100644
index 0000000..4853b3e
--- /dev/null
+++ b/Tablet/TableSectionBuilder.swift
@@ -0,0 +1,77 @@
+//
+// Copyright (c) 2015 Max Sokolov https://twitter.com/max_sokolov
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import UIKit
+import Foundation
+
+/**
+ Responsible for building a certain table view section.
+ Can host several row builders.
+*/
+public class TableSectionBuilder {
+
+ 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
+
+ /// 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, rowBuilders: [RowBuilder]? = nil) {
+
+ self.headerTitle = headerTitle
+ self.footerTitle = footerTitle
+
+ if let initialRows = rowBuilders {
+ self.builders.appendContentsOf(initialRows)
+ }
+ }
+
+ public init(headerView: UIView? = nil, headerHeight: CGFloat = UITableViewAutomaticDimension, footerView: UIView? = nil, footerHeight: CGFloat = UITableViewAutomaticDimension) {
+
+ self.headerView = headerView
+ self.headerHeight = headerHeight
+
+ self.footerView = footerView
+ self.footerHeight = footerHeight
+ }
+
+ internal func builderAtIndex(var index: Int) -> (RowBuilder, Int)? {
+
+ for builder in builders {
+ if index < builder.numberOfRows {
+ return (builder, index)
+ }
+ index -= builder.numberOfRows
+ }
+
+ return nil
+ }
+}
\ No newline at end of file
diff --git a/Tablet/Tablet.swift b/Tablet/Tablet.swift
new file mode 100644
index 0000000..3331c53
--- /dev/null
+++ b/Tablet/Tablet.swift
@@ -0,0 +1,118 @@
+//
+// Copyright (c) 2015 Max Sokolov https://twitter.com/max_sokolov
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+// the Software, and to permit persons to whom the Software is furnished to do so,
+// subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import UIKit
+import Foundation
+
+internal let kActionPerformedNotificationKey = "_action"
+
+/**
+ The actions that Tablet provides.
+*/
+public enum ActionType {
+
+ case click
+ case select
+ case deselect
+ case configure
+ case willDisplay
+ case shouldHighlight
+ case height
+ case custom(String)
+
+ var key: String {
+
+ switch (self) {
+ case .custom(let key):
+ return key
+ default:
+ return "_\(self)"
+ }
+ }
+}
+
+public class ActionData {
+
+ public let cell: C?
+ public let item: I
+ public let itemIndex: Int
+ public let indexPath: NSIndexPath
+
+ init(cell: C?, indexPath: NSIndexPath, item: I, itemIndex: Int) {
+
+ self.cell = cell
+ self.indexPath = indexPath
+ self.item = item
+ self.itemIndex = itemIndex
+ }
+}
+
+/**
+ A custom action that you can trigger from your cell.
+ You can eacily catch actions using a chaining manner with your row builder.
+*/
+public class Action {
+
+ /// The cell that triggers an action.
+ public let cell: UITableViewCell
+
+ /// The action unique key.
+ public let key: String
+
+ /// The custom user info.
+ public let userInfo: [NSObject: AnyObject]?
+
+ public init(key: String, sender: UITableViewCell, userInfo: [NSObject: AnyObject]? = nil) {
+
+ self.key = key
+ self.cell = sender
+ self.userInfo = userInfo
+ }
+
+ public func perform() {
+
+ NSNotificationCenter.defaultCenter().postNotificationName(kActionPerformedNotificationKey, object: self)
+ }
+}
+
+/**
+ If you want to delegate your cell configuration logic to cell itself (with your view model or even model) than
+ just provide an implementation of this protocol for your cell. Enjoy safe-typisation.
+*/
+public protocol ConfigurableCell {
+
+ typealias Item
+
+ static func reusableIdentifier() -> String
+ func configureWithItem(item: Item)
+}
+
+/**
+ A protocol that every row builder should follow.
+ A certain section can only works with row builders that respect this protocol.
+*/
+public protocol RowBuilder {
+
+ var numberOfRows: Int { get }
+ var reusableIdentifier: String { get }
+ var estimatedRowHeight: CGFloat { get }
+
+ func performAction(actionType: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int) -> AnyObject?
+}
\ No newline at end of file
diff --git a/TabletDemo/TabletDemo.xcodeproj/project.pbxproj b/TabletDemo/TabletDemo.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..f241c66
--- /dev/null
+++ b/TabletDemo/TabletDemo.xcodeproj/project.pbxproj
@@ -0,0 +1,333 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 508B71841BF48DD300272920 /* TableSectionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508B71831BF48DD300272920 /* TableSectionBuilder.swift */; };
+ 508B71861BF48E0D00272920 /* TableRowBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508B71851BF48E0D00272920 /* TableRowBuilder.swift */; };
+ DA1BCD0F1BF5472C00CC0479 /* TableDirector.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BCD0E1BF5472C00CC0479 /* TableDirector.swift */; };
+ DA1BCD111BF7388C00CC0479 /* CustomTableActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BCD101BF7388C00CC0479 /* CustomTableActions.swift */; };
+ DAB7EB2B1BEF787300D2AD5E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAB7EB2A1BEF787300D2AD5E /* AppDelegate.swift */; };
+ DAB7EB2D1BEF787300D2AD5E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAB7EB2C1BEF787300D2AD5E /* ViewController.swift */; };
+ DAB7EB301BEF787300D2AD5E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DAB7EB2E1BEF787300D2AD5E /* Main.storyboard */; };
+ DAB7EB321BEF787300D2AD5E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DAB7EB311BEF787300D2AD5E /* Assets.xcassets */; };
+ DAB7EB351BEF787300D2AD5E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DAB7EB331BEF787300D2AD5E /* LaunchScreen.storyboard */; };
+ DAB7EB3E1BEF78A400D2AD5E /* Tablet.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAB7EB3D1BEF78A400D2AD5E /* Tablet.swift */; };
+ DAB7EB401BEFD07E00D2AD5E /* ConfigurableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAB7EB3F1BEFD07E00D2AD5E /* ConfigurableTableViewCell.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 508B71831BF48DD300272920 /* TableSectionBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableSectionBuilder.swift; sourceTree = ""; };
+ 508B71851BF48E0D00272920 /* TableRowBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableRowBuilder.swift; sourceTree = ""; };
+ DA1BCD0E1BF5472C00CC0479 /* TableDirector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableDirector.swift; sourceTree = ""; };
+ DA1BCD101BF7388C00CC0479 /* CustomTableActions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomTableActions.swift; sourceTree = ""; };
+ DAB7EB271BEF787300D2AD5E /* TabletDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TabletDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ DAB7EB2A1BEF787300D2AD5E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ DAB7EB2C1BEF787300D2AD5E /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
+ DAB7EB2F1BEF787300D2AD5E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ DAB7EB311BEF787300D2AD5E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ DAB7EB341BEF787300D2AD5E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ DAB7EB361BEF787300D2AD5E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ DAB7EB3D1BEF78A400D2AD5E /* Tablet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tablet.swift; sourceTree = ""; };
+ DAB7EB3F1BEFD07E00D2AD5E /* ConfigurableTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurableTableViewCell.swift; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ DAB7EB241BEF787300D2AD5E /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ DAB7EB1E1BEF787300D2AD5E = {
+ isa = PBXGroup;
+ children = (
+ DAB7EB3C1BEF789500D2AD5E /* Tablet */,
+ DAB7EB291BEF787300D2AD5E /* TabletDemo */,
+ DAB7EB281BEF787300D2AD5E /* Products */,
+ );
+ sourceTree = "";
+ };
+ DAB7EB281BEF787300D2AD5E /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ DAB7EB271BEF787300D2AD5E /* TabletDemo.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ DAB7EB291BEF787300D2AD5E /* TabletDemo */ = {
+ isa = PBXGroup;
+ children = (
+ DAB7EB2A1BEF787300D2AD5E /* AppDelegate.swift */,
+ DAB7EB2C1BEF787300D2AD5E /* ViewController.swift */,
+ DAB7EB3F1BEFD07E00D2AD5E /* ConfigurableTableViewCell.swift */,
+ DA1BCD101BF7388C00CC0479 /* CustomTableActions.swift */,
+ DAB7EB2E1BEF787300D2AD5E /* Main.storyboard */,
+ DAB7EB311BEF787300D2AD5E /* Assets.xcassets */,
+ DAB7EB331BEF787300D2AD5E /* LaunchScreen.storyboard */,
+ DAB7EB361BEF787300D2AD5E /* Info.plist */,
+ );
+ path = TabletDemo;
+ sourceTree = "";
+ };
+ DAB7EB3C1BEF789500D2AD5E /* Tablet */ = {
+ isa = PBXGroup;
+ children = (
+ DAB7EB3D1BEF78A400D2AD5E /* Tablet.swift */,
+ DA1BCD0E1BF5472C00CC0479 /* TableDirector.swift */,
+ 508B71851BF48E0D00272920 /* TableRowBuilder.swift */,
+ 508B71831BF48DD300272920 /* TableSectionBuilder.swift */,
+ );
+ name = Tablet;
+ path = ../Tablet;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ DAB7EB261BEF787300D2AD5E /* TabletDemo */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = DAB7EB391BEF787300D2AD5E /* Build configuration list for PBXNativeTarget "TabletDemo" */;
+ buildPhases = (
+ DAB7EB231BEF787300D2AD5E /* Sources */,
+ DAB7EB241BEF787300D2AD5E /* Frameworks */,
+ DAB7EB251BEF787300D2AD5E /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = TabletDemo;
+ productName = TabletDemo;
+ productReference = DAB7EB271BEF787300D2AD5E /* TabletDemo.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ DAB7EB1F1BEF787300D2AD5E /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 0700;
+ ORGANIZATIONNAME = Tablet;
+ TargetAttributes = {
+ DAB7EB261BEF787300D2AD5E = {
+ CreatedOnToolsVersion = 7.0.1;
+ DevelopmentTeam = Z48R734SJX;
+ };
+ };
+ };
+ buildConfigurationList = DAB7EB221BEF787300D2AD5E /* Build configuration list for PBXProject "TabletDemo" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = DAB7EB1E1BEF787300D2AD5E;
+ productRefGroup = DAB7EB281BEF787300D2AD5E /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ DAB7EB261BEF787300D2AD5E /* TabletDemo */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ DAB7EB251BEF787300D2AD5E /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ DAB7EB351BEF787300D2AD5E /* LaunchScreen.storyboard in Resources */,
+ DAB7EB321BEF787300D2AD5E /* Assets.xcassets in Resources */,
+ DAB7EB301BEF787300D2AD5E /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ DAB7EB231BEF787300D2AD5E /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 508B71841BF48DD300272920 /* TableSectionBuilder.swift in Sources */,
+ DAB7EB2D1BEF787300D2AD5E /* ViewController.swift in Sources */,
+ DAB7EB3E1BEF78A400D2AD5E /* Tablet.swift in Sources */,
+ 508B71861BF48E0D00272920 /* TableRowBuilder.swift in Sources */,
+ DA1BCD0F1BF5472C00CC0479 /* TableDirector.swift in Sources */,
+ DAB7EB401BEFD07E00D2AD5E /* ConfigurableTableViewCell.swift in Sources */,
+ DAB7EB2B1BEF787300D2AD5E /* AppDelegate.swift in Sources */,
+ DA1BCD111BF7388C00CC0479 /* CustomTableActions.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ DAB7EB2E1BEF787300D2AD5E /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DAB7EB2F1BEF787300D2AD5E /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
+ DAB7EB331BEF787300D2AD5E /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DAB7EB341BEF787300D2AD5E /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ DAB7EB371BEF787300D2AD5E /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ DAB7EB381BEF787300D2AD5E /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ DAB7EB3A1BEF787300D2AD5E /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ INFOPLIST_FILE = TabletDemo/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.tablet.TabletDemo;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE = "";
+ };
+ name = Debug;
+ };
+ DAB7EB3B1BEF787300D2AD5E /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ INFOPLIST_FILE = TabletDemo/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.tablet.TabletDemo;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE = "";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ DAB7EB221BEF787300D2AD5E /* Build configuration list for PBXProject "TabletDemo" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DAB7EB371BEF787300D2AD5E /* Debug */,
+ DAB7EB381BEF787300D2AD5E /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ DAB7EB391BEF787300D2AD5E /* Build configuration list for PBXNativeTarget "TabletDemo" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ DAB7EB3A1BEF787300D2AD5E /* Debug */,
+ DAB7EB3B1BEF787300D2AD5E /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = DAB7EB1F1BEF787300D2AD5E /* Project object */;
+}
diff --git a/TabletDemo/TabletDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/TabletDemo/TabletDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..9fbb493
--- /dev/null
+++ b/TabletDemo/TabletDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/TabletDemo/TabletDemo.xcodeproj/project.xcworkspace/xcuserdata/max.xcuserdatad/UserInterfaceState.xcuserstate b/TabletDemo/TabletDemo.xcodeproj/project.xcworkspace/xcuserdata/max.xcuserdatad/UserInterfaceState.xcuserstate
new file mode 100644
index 0000000..72a945c
Binary files /dev/null and b/TabletDemo/TabletDemo.xcodeproj/project.xcworkspace/xcuserdata/max.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/TabletDemo/TabletDemo.xcodeproj/project.xcworkspace/xcuserdata/maxsokolov.xcuserdatad/UserInterfaceState.xcuserstate b/TabletDemo/TabletDemo.xcodeproj/project.xcworkspace/xcuserdata/maxsokolov.xcuserdatad/UserInterfaceState.xcuserstate
new file mode 100644
index 0000000..37a26d5
Binary files /dev/null and b/TabletDemo/TabletDemo.xcodeproj/project.xcworkspace/xcuserdata/maxsokolov.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/TabletDemo/TabletDemo.xcodeproj/xcuserdata/max.xcuserdatad/xcschemes/TabletDemo.xcscheme b/TabletDemo/TabletDemo.xcodeproj/xcuserdata/max.xcuserdatad/xcschemes/TabletDemo.xcscheme
new file mode 100644
index 0000000..6db4123
--- /dev/null
+++ b/TabletDemo/TabletDemo.xcodeproj/xcuserdata/max.xcuserdatad/xcschemes/TabletDemo.xcscheme
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/TabletDemo/TabletDemo.xcodeproj/xcuserdata/max.xcuserdatad/xcschemes/xcschememanagement.plist b/TabletDemo/TabletDemo.xcodeproj/xcuserdata/max.xcuserdatad/xcschemes/xcschememanagement.plist
new file mode 100644
index 0000000..c382e1a
--- /dev/null
+++ b/TabletDemo/TabletDemo.xcodeproj/xcuserdata/max.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ SchemeUserState
+
+ TabletDemo.xcscheme
+
+ orderHint
+ 0
+
+
+ SuppressBuildableAutocreation
+
+ DAB7EB261BEF787300D2AD5E
+
+ primary
+
+
+
+
+
diff --git a/TabletDemo/TabletDemo.xcodeproj/xcuserdata/maxsokolov.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/TabletDemo/TabletDemo.xcodeproj/xcuserdata/maxsokolov.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
new file mode 100644
index 0000000..fe2b454
--- /dev/null
+++ b/TabletDemo/TabletDemo.xcodeproj/xcuserdata/maxsokolov.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
@@ -0,0 +1,5 @@
+
+
+
diff --git a/TabletDemo/TabletDemo.xcodeproj/xcuserdata/maxsokolov.xcuserdatad/xcschemes/TabletDemo.xcscheme b/TabletDemo/TabletDemo.xcodeproj/xcuserdata/maxsokolov.xcuserdatad/xcschemes/TabletDemo.xcscheme
new file mode 100644
index 0000000..6db4123
--- /dev/null
+++ b/TabletDemo/TabletDemo.xcodeproj/xcuserdata/maxsokolov.xcuserdatad/xcschemes/TabletDemo.xcscheme
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/TabletDemo/TabletDemo.xcodeproj/xcuserdata/maxsokolov.xcuserdatad/xcschemes/xcschememanagement.plist b/TabletDemo/TabletDemo.xcodeproj/xcuserdata/maxsokolov.xcuserdatad/xcschemes/xcschememanagement.plist
new file mode 100644
index 0000000..c382e1a
--- /dev/null
+++ b/TabletDemo/TabletDemo.xcodeproj/xcuserdata/maxsokolov.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ SchemeUserState
+
+ TabletDemo.xcscheme
+
+ orderHint
+ 0
+
+
+ SuppressBuildableAutocreation
+
+ DAB7EB261BEF787300D2AD5E
+
+ primary
+
+
+
+
+
diff --git a/TabletDemo/TabletDemo/AppDelegate.swift b/TabletDemo/TabletDemo/AppDelegate.swift
new file mode 100644
index 0000000..69cc0e6
--- /dev/null
+++ b/TabletDemo/TabletDemo/AppDelegate.swift
@@ -0,0 +1,15 @@
+//
+// AppDelegate.swift
+// TabletDemo
+//
+// Created by Max Sokolov on 08/11/15.
+// Copyright © 2015 Tablet. All rights reserved.
+//
+
+import UIKit
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ var window: UIWindow?
+}
\ No newline at end of file
diff --git a/TabletDemo/TabletDemo/Assets.xcassets/AppIcon.appiconset/Contents.json b/TabletDemo/TabletDemo/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..118c98f
--- /dev/null
+++ b/TabletDemo/TabletDemo/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "60x60",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "60x60",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/TabletDemo/TabletDemo/Base.lproj/LaunchScreen.storyboard b/TabletDemo/TabletDemo/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..ffacbd6
--- /dev/null
+++ b/TabletDemo/TabletDemo/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/TabletDemo/TabletDemo/Base.lproj/Main.storyboard b/TabletDemo/TabletDemo/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..2bd0123
--- /dev/null
+++ b/TabletDemo/TabletDemo/Base.lproj/Main.storyboard
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/TabletDemo/TabletDemo/ConfigurableTableViewCell.swift b/TabletDemo/TabletDemo/ConfigurableTableViewCell.swift
new file mode 100644
index 0000000..07be0a2
--- /dev/null
+++ b/TabletDemo/TabletDemo/ConfigurableTableViewCell.swift
@@ -0,0 +1,34 @@
+//
+// ConfigurableTableViewCell.swift
+// TabletDemo
+//
+// Created by Max Sokolov on 08/11/15.
+// Copyright © 2015 Tablet. All rights reserved.
+//
+
+import UIKit
+
+let kConfigurableTableViewCellButtonClickedAction = "button_clicked"
+
+class ConfigurableTableViewCell: UITableViewCell, ConfigurableCell {
+
+ typealias Item = String
+
+ @IBOutlet weak var button: UIButton!
+ @IBOutlet weak var contentLabel: UILabel!
+
+ static func reusableIdentifier() -> String {
+
+ return "configurable_cell"
+ }
+
+ func configureWithItem(item: Item) {
+
+ button.setTitle("Button \(item)", forState: .Normal)
+ }
+
+ @IBAction func buttonClicked(sender: UIButton) {
+
+ Action(key: kConfigurableTableViewCellButtonClickedAction, sender: self).perform()
+ }
+}
\ No newline at end of file
diff --git a/TabletDemo/TabletDemo/CustomTableActions.swift b/TabletDemo/TabletDemo/CustomTableActions.swift
new file mode 100644
index 0000000..c586a75
--- /dev/null
+++ b/TabletDemo/TabletDemo/CustomTableActions.swift
@@ -0,0 +1,20 @@
+//
+// CustomTableActions.swift
+// TabletDemo
+//
+// Created by Max Sokolov on 14/11/15.
+// Copyright © 2015 Tablet. All rights reserved.
+//
+
+import UIKit
+import Foundation
+
+let kTableDirectorDidEndDisplayingCell = "enddisplaycell"
+
+extension TableDirector {
+
+ public func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
+
+ performAction(.custom(kTableDirectorDidEndDisplayingCell), cell: cell, indexPath: indexPath)
+ }
+}
\ No newline at end of file
diff --git a/TabletDemo/TabletDemo/Info.plist b/TabletDemo/TabletDemo/Info.plist
new file mode 100644
index 0000000..6c48029
--- /dev/null
+++ b/TabletDemo/TabletDemo/Info.plist
@@ -0,0 +1,38 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+
+
+
diff --git a/TabletDemo/TabletDemo/ViewController.swift b/TabletDemo/TabletDemo/ViewController.swift
new file mode 100644
index 0000000..ed353d5
--- /dev/null
+++ b/TabletDemo/TabletDemo/ViewController.swift
@@ -0,0 +1,60 @@
+//
+// ViewController.swift
+// TabletDemo
+//
+// Created by Max Sokolov on 08/11/15.
+// Copyright © 2015 Tablet. All rights reserved.
+//
+
+import UIKit
+
+class ViewController: UIViewController {
+
+ @IBOutlet weak var tableView: UITableView!
+ var tableDirector: TableDirector!
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ tableDirector = TableDirector(tableView: tableView)
+
+ let rowBuilder = TableRowBuilder(items: [1, 2, 3, 4], id: "cell")
+ .action(.configure) { data in
+
+ data.cell?.textLabel?.text = "\(data.item)"
+ }
+ .action(.shouldHighlight) { data in
+
+ return false
+ }
+ .action(kTableDirectorDidEndDisplayingCell) { data -> Void in
+
+ print("end display: \(data.indexPath)")
+ }
+
+ let configurableRowBuilder = TableConfigurableRowBuilder(items: ["5", "6", "7", "8"], estimatedRowHeight: 300)
+ .action(.click) { data -> Void in
+
+ print("click action indexPath: \(data.indexPath), item: \(data.item)")
+ }
+ .action(kConfigurableTableViewCellButtonClickedAction) { data -> Void in
+
+ print("custom action indexPath: \(data.indexPath), item: \(data.item)")
+ }
+ .action(.height) { data -> ReturnValue in
+
+ if data.item == "5" {
+ return 70
+ }
+ return nil
+ }
+ .action(.configure) { (data) -> Void in
+
+ data.cell!.contentLabel.text = "Tablet is a super lightweight yet powerful generic library that handles a complexity of UITableView's datasource and delegate methods in a Swift environment. Tablet's goal is to provide an easiest way to create complex table views. With Tablet you don't have to write a messy code of switch or if statements when you deal with bunch of different cells in different sections."
+ }
+
+ let sectionBuilder = TableSectionBuilder(headerTitle: "Tablet", footerTitle: "Deal with table view like a boss.", rowBuilders: [rowBuilder, configurableRowBuilder])
+
+ tableDirector.appendSection(sectionBuilder)
+ }
+}
\ No newline at end of file