Compare commits

...

4 Commits

Author SHA1 Message Date
Max Sokolov 8f895bdd31 bump podspec 2016-05-24 11:55:51 +03:00
Max Sokolov 79aef4dd39 fix clickDelete event 2016-05-24 11:55:09 +03:00
Max Sokolov b1f3b110b1 support can edit 2016-05-23 21:43:14 +03:00
Max Sokolov b5f7bd25e6 compatibility fix 2016-05-13 17:28:27 +03:00
6 changed files with 118 additions and 92 deletions

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = 'Tablet' s.name = 'Tablet'
s.version = '0.2.6' s.version = '0.2.8'
s.homepage = 'https://github.com/maxsokolov/tablet' s.homepage = 'https://github.com/maxsokolov/tablet'
s.summary = 'Powerful type-safe tool for UITableView. Swift 2.0 is required.' s.summary = 'Powerful type-safe tool for UITableView. Swift 2.0 is required.'
@ -12,5 +12,5 @@ Pod::Spec.new do |s|
s.source_files = 'Tablet/*.swift' s.source_files = 'Tablet/*.swift'
s.module_name = 'Tablet' 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 end

View File

@ -22,10 +22,10 @@ import UIKit
import Foundation import Foundation
/** /**
Responsible for table view's datasource and delegate. Responsible for table view's datasource and delegate.
*/ */
public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate { public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate {
public private(set) weak var tableView: UITableView! public private(set) weak var tableView: UITableView!
private var sections = [TableSectionBuilder]() private var sections = [TableSectionBuilder]()
public weak var scrollDelegate: UIScrollViewDelegate? public weak var scrollDelegate: UIScrollViewDelegate?
@ -36,25 +36,25 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate
self.tableView = tableView self.tableView = tableView
self.tableView.delegate = self self.tableView.delegate = self
self.tableView.dataSource = self self.tableView.dataSource = self
NSNotificationCenter.defaultCenter().addObserver(self, selector: "didReceiveAction:", name: kActionPerformedNotificationKey, object: nil) NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(didReceiveAction), name: kActionPerformedNotificationKey, object: nil)
} }
deinit { deinit {
NSNotificationCenter.defaultCenter().removeObserver(self) NSNotificationCenter.defaultCenter().removeObserver(self)
} }
// MARK: Private methods // MARK: Private methods
/** /**
Find a row builder that responsible for building a row from cell with given item type. Find a row builder that responsible for building a row from cell with given item type.
- Parameters: - Parameters:
- indexPath: path of cell to dequeue - indexPath: path of cell to dequeue
- Returns: A touple - (builder, builderItemIndex) - Returns: A touple - (builder, builderItemIndex)
*/ */
private func builderAtIndexPath(indexPath: NSIndexPath) -> (RowBuilder, Int) { private func builderAtIndexPath(indexPath: NSIndexPath) -> (RowBuilder, Int) {
return sections[indexPath.section].builderAtIndex(indexPath.row)! return sections[indexPath.section].builderAtIndex(indexPath.row)!
@ -74,18 +74,18 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate
builder.0.invokeAction(.custom(action.key), cell: action.cell, indexPath: indexPath, itemIndex: builder.1, userInfo: action.userInfo) builder.0.invokeAction(.custom(action.key), cell: action.cell, indexPath: indexPath, itemIndex: builder.1, userInfo: action.userInfo)
} }
} }
public override func respondsToSelector(selector: Selector) -> Bool { public override func respondsToSelector(selector: Selector) -> Bool {
return super.respondsToSelector(selector) || scrollDelegate?.respondsToSelector(selector) == true return super.respondsToSelector(selector) || scrollDelegate?.respondsToSelector(selector) == true
} }
public override func forwardingTargetForSelector(selector: Selector) -> AnyObject? { public override func forwardingTargetForSelector(selector: Selector) -> AnyObject? {
return scrollDelegate?.respondsToSelector(selector) == true ? scrollDelegate : super.forwardingTargetForSelector(selector) return scrollDelegate?.respondsToSelector(selector) == true ? scrollDelegate : super.forwardingTargetForSelector(selector)
} }
} }
public extension TableDirector { public extension TableDirector {
// MARK: UITableViewDataSource - configuration // MARK: UITableViewDataSource - configuration
func numberOfSectionsInTableView(tableView: UITableView) -> Int { func numberOfSectionsInTableView(tableView: UITableView) -> Int {
@ -103,7 +103,7 @@ public extension TableDirector {
let builder = builderAtIndexPath(indexPath) let builder = builderAtIndexPath(indexPath)
let cell = tableView.dequeueReusableCellWithIdentifier(builder.0.reusableIdentifier, forIndexPath: indexPath) let cell = tableView.dequeueReusableCellWithIdentifier(builder.0.reusableIdentifier, forIndexPath: indexPath)
if cell.frame.size.width != tableView.frame.size.width { if cell.frame.size.width != tableView.frame.size.width {
cell.frame = CGRectMake(0, 0, tableView.frame.size.width, cell.frame.size.height) cell.frame = CGRectMake(0, 0, tableView.frame.size.width, cell.frame.size.height)
cell.layoutIfNeeded() cell.layoutIfNeeded()
@ -116,7 +116,7 @@ public extension TableDirector {
} }
public extension TableDirector { public extension TableDirector {
// MARK: UITableViewDataSource - section setup // MARK: UITableViewDataSource - section setup
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
@ -153,16 +153,16 @@ public extension TableDirector {
} }
public extension TableDirector { public extension TableDirector {
// MARK: UITableViewDelegate - actions // MARK: UITableViewDelegate - actions
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return builderAtIndexPath(indexPath).0.estimatedRowHeight return builderAtIndexPath(indexPath).0.estimatedRowHeight
} }
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return invokeAction(.height, cell: nil, indexPath: indexPath) as? CGFloat ?? UITableViewAutomaticDimension return invokeAction(.height, cell: nil, indexPath: indexPath) as? CGFloat ?? UITableViewAutomaticDimension
} }
@ -172,7 +172,7 @@ public extension TableDirector {
} }
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) let cell = tableView.cellForRowAtIndexPath(indexPath)
if invokeAction(.click, cell: cell, indexPath: indexPath) != nil { if invokeAction(.click, cell: cell, indexPath: indexPath) != nil {
@ -183,31 +183,31 @@ public extension TableDirector {
} }
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) { func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
invokeAction(.deselect, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath) invokeAction(.deselect, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath)
} }
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) { func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
invokeAction(.willDisplay, cell: cell, indexPath: indexPath) invokeAction(.willDisplay, cell: cell, indexPath: indexPath)
} }
func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool { func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return invokeAction(.shouldHighlight, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath) as? Bool ?? true return invokeAction(.shouldHighlight, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath) as? Bool ?? true
} }
} }
public extension TableDirector { public extension TableDirector {
// MARK: Sections manipulation // MARK: Sections manipulation
public func appendSection(section: TableSectionBuilder) { public func appendSection(section: TableSectionBuilder) {
appendSections([section]) appendSections([section])
} }
public func appendSections(sections: [TableSectionBuilder]) { public func appendSections(sections: [TableSectionBuilder]) {
sections.forEach { $0.willMoveToDirector(tableView) } sections.forEach { $0.willMoveToDirector(tableView) }
self.sections.appendContentsOf(sections) self.sections.appendContentsOf(sections)
} }
@ -217,8 +217,22 @@ public extension TableDirector {
} }
} }
public func +=(left: TableDirector, right: RowBuilder) { public extension TableDirector {
public func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return invokeAction(.canEdit, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath) as? Bool ?? false
}
public func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
invokeAction(.clickDelete, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath)
}
}
}
public func +=(left: TableDirector, right: RowBuilder) {
left.appendSection(TableSectionBuilder(rowBuilders: [right])) left.appendSection(TableSectionBuilder(rowBuilders: [right]))
} }
@ -228,7 +242,7 @@ public func +=(left: TableDirector, right: [RowBuilder]) {
} }
public func +=(left: TableDirector, right: TableSectionBuilder) { public func +=(left: TableDirector, right: TableSectionBuilder) {
left.appendSection(right) left.appendSection(right)
} }

View File

@ -24,12 +24,12 @@ import Foundation
public typealias ReturnValue = AnyObject? public typealias ReturnValue = AnyObject?
internal enum ActionHandler<I, C> { internal enum ActionHandler<I, C> {
case actionBlock((data: ActionData<I, C>) -> Void) case actionBlock((data: ActionData<I, C>) -> Void)
case actionReturnBlock((data: ActionData<I, C>) -> AnyObject?) case actionReturnBlock((data: ActionData<I, C>) -> AnyObject?)
func invoke(data: ActionData<I, C>) -> ReturnValue { func invoke(data: ActionData<I, C>) -> ReturnValue {
switch (self) { switch (self) {
case .actionBlock(let closure): case .actionBlock(let closure):
closure(data: data) closure(data: data)
@ -41,13 +41,13 @@ internal enum ActionHandler<I, C> {
} }
/** /**
Responsible for building cells of given type and passing items to them. Responsible for building cells of given type and passing items to them.
*/ */
public class TableRowBuilder<I, C where C: UITableViewCell> : RowBuilder { public class TableRowBuilder<I, C where C: UITableViewCell> : RowBuilder {
private var actions = Dictionary<String, ActionHandler<I, C>>() private var actions = Dictionary<String, ActionHandler<I, C>>()
private var items = [I]() public var items = [I]()
public var reusableIdentifier: String public var reusableIdentifier: String
public var estimatedRowHeight: CGFloat public var estimatedRowHeight: CGFloat
public var numberOfRows: Int { public var numberOfRows: Int {
@ -64,7 +64,7 @@ public class TableRowBuilder<I, C where C: UITableViewCell> : RowBuilder {
} }
public init(items: [I]? = nil, id: String? = nil, estimatedRowHeight: CGFloat = 48) { public init(items: [I]? = nil, id: String? = nil, estimatedRowHeight: CGFloat = 48) {
reusableIdentifier = id ?? NSStringFromClass(C).componentsSeparatedByString(".").last ?? "" reusableIdentifier = id ?? NSStringFromClass(C).componentsSeparatedByString(".").last ?? ""
self.estimatedRowHeight = estimatedRowHeight self.estimatedRowHeight = estimatedRowHeight
@ -74,21 +74,21 @@ public class TableRowBuilder<I, C where C: UITableViewCell> : RowBuilder {
} }
// MARK: Chaining actions // MARK: Chaining actions
public func action(key: String, closure: (data: ActionData<I, C>) -> Void) -> Self { public func action(key: String, closure: (data: ActionData<I, C>) -> Void) -> Self {
actions[key] = .actionBlock(closure) actions[key] = .actionBlock(closure)
return self return self
} }
public func action(actionType: ActionType, closure: (data: ActionData<I, C>) -> Void) -> Self { public func action(actionType: ActionType, closure: (data: ActionData<I, C>) -> Void) -> Self {
actions[actionType.key] = .actionBlock(closure) actions[actionType.key] = .actionBlock(closure)
return self return self
} }
public func action(actionType: ActionType, closure: (data: ActionData<I, C>) -> ReturnValue) -> Self { public func action(actionType: ActionType, closure: (data: ActionData<I, C>) -> ReturnValue) -> Self {
actions[actionType.key] = .actionReturnBlock(closure) actions[actionType.key] = .actionReturnBlock(closure)
return self return self
} }
@ -96,49 +96,49 @@ public class TableRowBuilder<I, C where C: UITableViewCell> : RowBuilder {
// MARK: Triggers // MARK: Triggers
public func invokeAction(actionType: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int, userInfo: [NSObject: AnyObject]? = nil) -> AnyObject? { public func invokeAction(actionType: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int, userInfo: [NSObject: AnyObject]? = nil) -> AnyObject? {
if let action = actions[actionType.key] { if let action = actions[actionType.key] {
return action.invoke(ActionData(cell: cell as? C, indexPath: indexPath, item: items[itemIndex], itemIndex: itemIndex)) return action.invoke(ActionData(cell: cell as? C, indexPath: indexPath, item: items[itemIndex], itemIndex: itemIndex))
} }
return nil return nil
} }
public func registerCell(inTableView tableView: UITableView) { public func registerCell(inTableView tableView: UITableView) {
if tableView.dequeueReusableCellWithIdentifier(reusableIdentifier) != nil { if tableView.dequeueReusableCellWithIdentifier(reusableIdentifier) != nil {
return return
} }
guard let resource = NSStringFromClass(C).componentsSeparatedByString(".").last else { return } guard let resource = NSStringFromClass(C).componentsSeparatedByString(".").last else { return }
let bundle = NSBundle(forClass: C.self) let bundle = NSBundle(forClass: C.self)
if let _ = bundle.pathForResource(resource, ofType: "nib") { // existing cell if let _ = bundle.pathForResource(resource, ofType: "nib") { // existing cell
tableView.registerNib(UINib(nibName: resource, bundle: bundle), forCellReuseIdentifier: reusableIdentifier) tableView.registerNib(UINib(nibName: resource, bundle: bundle), forCellReuseIdentifier: reusableIdentifier)
} else { } else {
tableView.registerClass(C.self, forCellReuseIdentifier: reusableIdentifier) tableView.registerClass(C.self, forCellReuseIdentifier: reusableIdentifier)
} }
} }
} }
/** /**
Responsible for building configurable cells of given type and passing items to them. Responsible for building configurable cells of given type and passing items to them.
*/ */
public class TableConfigurableRowBuilder<I, C: ConfigurableCell where C.Item == I, C: UITableViewCell> : TableRowBuilder<I, C> { public class TableConfigurableRowBuilder<I, C: ConfigurableCell where C.Item == I, C: UITableViewCell> : TableRowBuilder<I, C> {
public init(item: I, estimatedRowHeight: CGFloat = 48) { public init(item: I, estimatedRowHeight: CGFloat = 48) {
super.init(item: item, id: C.reusableIdentifier(), estimatedRowHeight: estimatedRowHeight) super.init(item: item, id: C.reusableIdentifier(), estimatedRowHeight: estimatedRowHeight)
} }
public init(items: [I]? = nil, estimatedRowHeight: CGFloat = 48) { public init(items: [I]? = nil, estimatedRowHeight: CGFloat = 48) {
super.init(items: items, id: C.reusableIdentifier(), estimatedRowHeight: estimatedRowHeight) super.init(items: items, id: C.reusableIdentifier(), estimatedRowHeight: estimatedRowHeight)
} }
public override func invokeAction(actionType: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int, userInfo: [NSObject: AnyObject]? = nil) -> AnyObject? { public override func invokeAction(actionType: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int, userInfo: [NSObject: AnyObject]? = nil) -> AnyObject? {
switch actionType { switch actionType {
case .configure: case .configure:
(cell as? C)?.configureWithItem(items[itemIndex]) (cell as? C)?.configureWithItem(items[itemIndex])
@ -149,9 +149,16 @@ public class TableConfigurableRowBuilder<I, C: ConfigurableCell where C.Item ==
} }
public extension TableRowBuilder { public extension TableRowBuilder {
// MARK: Items manipulation // MARK: Items manipulation
public func removeItemAtIndex(index: Int) {
if index < items.count {
items.removeAtIndex(index)
}
}
public func appendItems(items: [I]) { public func appendItems(items: [I]) {
self.items.appendContentsOf(items) self.items.appendContentsOf(items)
@ -169,6 +176,6 @@ public func +=<I, C>(left: TableRowBuilder<I, C>, right: I) {
} }
public func +=<I, C>(left: TableRowBuilder<I, C>, right: [I]) { public func +=<I, C>(left: TableRowBuilder<I, C>, right: [I]) {
left.appendItems(right) left.appendItems(right)
} }

View File

@ -67,13 +67,14 @@ public class TableSectionBuilder {
internal extension TableSectionBuilder { internal extension TableSectionBuilder {
internal func builderAtIndex(var index: Int) -> (RowBuilder, Int)? { internal func builderAtIndex(index: Int) -> (RowBuilder, Int)? {
var builderIndex = index
for builder in builders { for builder in builders {
if index < builder.numberOfRows { if builderIndex < builder.numberOfRows {
return (builder, index) return (builder, builderIndex)
} }
index -= builder.numberOfRows builderIndex -= builder.numberOfRows
} }
return nil return nil

View File

@ -24,10 +24,10 @@ import Foundation
internal let kActionPerformedNotificationKey = "_action" internal let kActionPerformedNotificationKey = "_action"
/** /**
The actions that Tablet provides. The actions that Tablet provides.
*/ */
public enum ActionType { public enum ActionType {
case click case click
case select case select
case deselect case deselect
@ -36,10 +36,12 @@ public enum ActionType {
case willDisplay case willDisplay
case shouldHighlight case shouldHighlight
case height case height
case canEdit
case clickDelete
case custom(String) case custom(String)
var key: String { var key: String {
switch (self) { switch (self) {
case .custom(let key): case .custom(let key):
return key return key
@ -50,7 +52,7 @@ public enum ActionType {
} }
public class ActionData<I, C> { public class ActionData<I, C> {
public let cell: C? public let cell: C?
public let item: I public let item: I
public let itemIndex: Int public let itemIndex: Int
@ -66,63 +68,65 @@ public class ActionData<I, C> {
} }
/** /**
A custom action that you can trigger from your cell. A custom action that you can trigger from your cell.
You can eacily catch actions using a chaining manner with your row builder. You can eacily catch actions using a chaining manner with your row builder.
*/ */
public class Action { public class Action {
/// The cell that triggers an action. /// The cell that triggers an action.
public let cell: UITableViewCell public let cell: UITableViewCell
/// The action unique key. /// The action unique key.
public let key: String public let key: String
/// The custom user info. /// The custom user info.
public let userInfo: [NSObject: AnyObject]? public let userInfo: [NSObject: AnyObject]?
public init(key: String, sender: UITableViewCell, userInfo: [NSObject: AnyObject]? = nil) { public init(key: String, sender: UITableViewCell, userInfo: [NSObject: AnyObject]? = nil) {
self.key = key self.key = key
self.cell = sender self.cell = sender
self.userInfo = userInfo self.userInfo = userInfo
} }
public func invoke() { public func invoke() {
NSNotificationCenter.defaultCenter().postNotificationName(kActionPerformedNotificationKey, object: self) 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 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. just provide an implementation of this protocol for your cell. Enjoy safe-typisation.
*/ */
public protocol ConfigurableCell { public protocol ConfigurableCell {
typealias Item associatedtype Item
static func reusableIdentifier() -> String static func reusableIdentifier() -> String
func configureWithItem(item: Item) func configureWithItem(item: Item)
} }
public extension ConfigurableCell where Self: UITableViewCell { public extension ConfigurableCell where Self: UITableViewCell {
static func reusableIdentifier() -> String { static func reusableIdentifier() -> String {
return NSStringFromClass(self).componentsSeparatedByString(".").last ?? "" return NSStringFromClass(self).componentsSeparatedByString(".").last ?? ""
} }
} }
/** /**
A protocol that every row builder should follow. A protocol that every row builder should follow.
A certain section can only works with row builders that respect this protocol. A certain section can only works with row builders that respect this protocol.
*/ */
public protocol RowBuilder { public protocol RowBuilder {
var numberOfRows: Int { get } var numberOfRows: Int { get }
var reusableIdentifier: String { get } var reusableIdentifier: String { get }
var estimatedRowHeight: CGFloat { get } var estimatedRowHeight: CGFloat { get }
func removeItemAtIndex(index: Int)
func registerCell(inTableView tableView: UITableView) func registerCell(inTableView tableView: UITableView)
func invokeAction(actionType: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int, userInfo: [NSObject: AnyObject]?) -> AnyObject? func invokeAction(actionType: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int, userInfo: [NSObject: AnyObject]?) -> AnyObject?
} }