From b1f3b110b13a410fcfdbdc701ed9dc38afddbcfd Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Mon, 23 May 2016 21:43:14 +0300 Subject: [PATCH] support can edit --- Tablet/TableDirector.swift | 80 +++++++++++------- Tablet/TableRowBuilder.swift | 59 +++++++------ Tablet/Tablet.swift | 60 +++++++------ .../UserInterfaceState.xcuserstate | Bin 17446 -> 17838 bytes 4 files changed, 116 insertions(+), 83 deletions(-) diff --git a/Tablet/TableDirector.swift b/Tablet/TableDirector.swift index e8b64ee..f070027 100644 --- a/Tablet/TableDirector.swift +++ b/Tablet/TableDirector.swift @@ -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) } diff --git a/Tablet/TableRowBuilder.swift b/Tablet/TableRowBuilder.swift index a5b79f0..fd9fe52 100644 --- a/Tablet/TableRowBuilder.swift +++ b/Tablet/TableRowBuilder.swift @@ -24,12 +24,12 @@ import Foundation public typealias ReturnValue = AnyObject? internal enum ActionHandler { - + case actionBlock((data: ActionData) -> Void) case actionReturnBlock((data: ActionData) -> AnyObject?) func invoke(data: ActionData) -> ReturnValue { - + switch (self) { case .actionBlock(let closure): closure(data: data) @@ -41,13 +41,13 @@ internal enum ActionHandler { } /** - 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 : RowBuilder { - + private var actions = Dictionary>() public var items = [I]() - + public var reusableIdentifier: String public var estimatedRowHeight: CGFloat public var numberOfRows: Int { @@ -64,7 +64,7 @@ public class TableRowBuilder : 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 : RowBuilder { } // 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 } @@ -96,49 +96,49 @@ public class TableRowBuilder : 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 : TableRowBuilder { - + 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(left: TableRowBuilder, right: I) { } public func +=(left: TableRowBuilder, right: [I]) { - + left.appendItems(right) } \ No newline at end of file diff --git a/Tablet/Tablet.swift b/Tablet/Tablet.swift index f0764e8..becc6f0 100644 --- a/Tablet/Tablet.swift +++ b/Tablet/Tablet.swift @@ -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 { - + public let cell: C? public let item: I public let itemIndex: Int @@ -66,63 +68,65 @@ public class ActionData { } /** - 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? } \ No newline at end of file diff --git a/TabletDemo/TabletDemo.xcodeproj/project.xcworkspace/xcuserdata/max.xcuserdatad/UserInterfaceState.xcuserstate b/TabletDemo/TabletDemo.xcodeproj/project.xcworkspace/xcuserdata/max.xcuserdatad/UserInterfaceState.xcuserstate index d52173a1263a548fa62bbcbdfdeefce20847284a..a18bc63ff06d5f310e85c0e5d48b70ca77031160 100644 GIT binary patch delta 8563 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 NnGpH^y}*GW;y@HsS`>@8NA9V6kGQP^MA2$d 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