support can edit

This commit is contained in:
Max Sokolov 2016-05-23 21:43:14 +03:00
parent b5f7bd25e6
commit b1f3b110b1
4 changed files with 116 additions and 83 deletions

View File

@ -22,10 +22,10 @@ import UIKit
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 private(set) weak var tableView: UITableView!
private var sections = [TableSectionBuilder]()
public weak var scrollDelegate: UIScrollViewDelegate?
@ -36,7 +36,7 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate
self.tableView = tableView
self.tableView.delegate = self
self.tableView.dataSource = self
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(didReceiveAction), name: kActionPerformedNotificationKey, object: nil)
}
@ -44,17 +44,17 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate
NSNotificationCenter.defaultCenter().removeObserver(self)
}
// 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)
*/
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)!
@ -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)
}
}
public override func respondsToSelector(selector: Selector) -> Bool {
return super.respondsToSelector(selector) || scrollDelegate?.respondsToSelector(selector) == true
}
public override func forwardingTargetForSelector(selector: Selector) -> AnyObject? {
return scrollDelegate?.respondsToSelector(selector) == true ? scrollDelegate : super.forwardingTargetForSelector(selector)
}
}
public extension TableDirector {
// MARK: UITableViewDataSource - configuration
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
@ -103,7 +103,7 @@ public extension TableDirector {
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()
@ -116,7 +116,7 @@ public extension TableDirector {
}
public extension TableDirector {
// MARK: UITableViewDataSource - section setup
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
@ -153,16 +153,16 @@ public extension TableDirector {
}
public extension TableDirector {
// MARK: UITableViewDelegate - actions
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return builderAtIndexPath(indexPath).0.estimatedRowHeight
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return invokeAction(.height, cell: nil, indexPath: indexPath) as? CGFloat ?? UITableViewAutomaticDimension
}
@ -172,7 +172,7 @@ public extension TableDirector {
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)
if invokeAction(.click, cell: cell, indexPath: indexPath) != nil {
@ -183,31 +183,31 @@ public extension TableDirector {
}
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
invokeAction(.deselect, cell: tableView.cellForRowAtIndexPath(indexPath), indexPath: indexPath)
}
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
invokeAction(.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 extension TableDirector {
// MARK: Sections manipulation
public func appendSection(section: TableSectionBuilder) {
appendSections([section])
}
public func appendSections(sections: [TableSectionBuilder]) {
sections.forEach { $0.willMoveToDirector(tableView) }
self.sections.appendContentsOf(sections)
}
@ -217,8 +217,30 @@ 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)
let builderInfo = builderAtIndexPath(indexPath)
builderInfo.0.removeItemAtIndex(builderInfo.1)
tableView.beginUpdates()
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
tableView.endUpdates()
}
}
}
public func +=(left: TableDirector, right: RowBuilder) {
left.appendSection(TableSectionBuilder(rowBuilders: [right]))
}
@ -228,7 +250,7 @@ public func +=(left: TableDirector, right: [RowBuilder]) {
}
public func +=(left: TableDirector, right: TableSectionBuilder) {
left.appendSection(right)
}

View File

@ -24,12 +24,12 @@ import Foundation
public typealias ReturnValue = AnyObject?
internal 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 {
switch (self) {
case .actionBlock(let closure):
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 {
private var actions = Dictionary<String, ActionHandler<I, C>>()
public var items = [I]()
public var reusableIdentifier: String
public var estimatedRowHeight: CGFloat
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) {
reusableIdentifier = id ?? NSStringFromClass(C).componentsSeparatedByString(".").last ?? ""
self.estimatedRowHeight = estimatedRowHeight
@ -74,21 +74,21 @@ public class TableRowBuilder<I, C where C: UITableViewCell> : RowBuilder {
}
// MARK: Chaining actions
public func action(key: String, closure: (data: ActionData<I, C>) -> Void) -> Self {
actions[key] = .actionBlock(closure)
return self
}
public func action(actionType: ActionType, closure: (data: ActionData<I, C>) -> Void) -> Self {
actions[actionType.key] = .actionBlock(closure)
return self
}
public func action(actionType: ActionType, closure: (data: ActionData<I, C>) -> ReturnValue) -> Self {
actions[actionType.key] = .actionReturnBlock(closure)
return self
}
@ -96,49 +96,49 @@ public class TableRowBuilder<I, C where C: UITableViewCell> : RowBuilder {
// MARK: Triggers
public func invokeAction(actionType: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int, userInfo: [NSObject: AnyObject]? = nil) -> AnyObject? {
if let action = actions[actionType.key] {
return action.invoke(ActionData(cell: cell as? C, indexPath: indexPath, item: items[itemIndex], itemIndex: itemIndex))
}
return nil
}
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)
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)
}
}
}
/**
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 init(item: I, estimatedRowHeight: CGFloat = 48) {
super.init(item: item, id: C.reusableIdentifier(), estimatedRowHeight: estimatedRowHeight)
}
public init(items: [I]? = nil, estimatedRowHeight: CGFloat = 48) {
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? {
switch actionType {
case .configure:
(cell as? C)?.configureWithItem(items[itemIndex])
@ -149,9 +149,16 @@ public class TableConfigurableRowBuilder<I, C: ConfigurableCell where C.Item ==
}
public extension TableRowBuilder {
// MARK: Items manipulation
public func removeItemAtIndex(index: Int) {
if index < items.count {
items.removeAtIndex(index)
}
}
public func appendItems(items: [I]) {
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]) {
left.appendItems(right)
}

View File

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