From ba8aa05d8b10dbd389bc512a1c03a30365022c2d Mon Sep 17 00:00:00 2001 From: Max Sokolov Date: Tue, 24 May 2016 01:13:18 +0300 Subject: [PATCH] add prototype based cell height row builder --- Tablet/ConfigurableCell.swift | 46 ++++++++++++++ ...otypeRowBuilder.swift => RowBuilder.swift} | 13 +++- Tablet/TableDirector.swift | 4 +- Tablet/TableRowBuilder.swift | 63 +++++++++---------- Tablet/Tablet.swift | 41 +++++------- Tablet/Tablet.xcodeproj/project.pbxproj | 14 +++-- 6 files changed, 115 insertions(+), 66 deletions(-) create mode 100644 Tablet/ConfigurableCell.swift rename Tablet/{TablePrototypeRowBuilder.swift => RowBuilder.swift} (74%) diff --git a/Tablet/ConfigurableCell.swift b/Tablet/ConfigurableCell.swift new file mode 100644 index 0000000..ba3ca10 --- /dev/null +++ b/Tablet/ConfigurableCell.swift @@ -0,0 +1,46 @@ +// +// 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 + +/** + 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 T + + static func reusableIdentifier() -> String + static func estimatedHeight() -> Float + static func defaultHeight() -> Float? + func configure(_: T) +} + +public extension ConfigurableCell where Self: UITableViewCell { + + static func reusableIdentifier() -> String { + return String(self) + } + + static func defaultHeight() -> Float? { + return nil + } +} \ No newline at end of file diff --git a/Tablet/TablePrototypeRowBuilder.swift b/Tablet/RowBuilder.swift similarity index 74% rename from Tablet/TablePrototypeRowBuilder.swift rename to Tablet/RowBuilder.swift index 1f3d9f4..e57bebc 100644 --- a/Tablet/TablePrototypeRowBuilder.swift +++ b/Tablet/RowBuilder.swift @@ -19,9 +19,16 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import UIKit -import Foundation - -public class TablePrototypeRowBuilder : TableBaseRowBuilder { +public protocol RowBuilder { + var reusableIdentifier: String { get } + var numberOfRows: Int { get } + + var estimatedRowHeight: Float { get } + + func rowHeight(index: Int) -> CGFloat + + func invoke(action action: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int, userInfo: [NSObject: AnyObject]?) -> AnyObject? + func registerCell(inTableView tableView: UITableView) } \ No newline at end of file diff --git a/Tablet/TableDirector.swift b/Tablet/TableDirector.swift index b79f730..1e21985 100644 --- a/Tablet/TableDirector.swift +++ b/Tablet/TableDirector.swift @@ -146,7 +146,9 @@ public class TableDirector: NSObject, UITableViewDataSource, UITableViewDelegate } public func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { - return invoke(action: .height, cell: nil, indexPath: indexPath) as? CGFloat ?? UITableViewAutomaticDimension + + let builder = builderAtIndexPath(indexPath) + return builder.0.rowHeight(builder.1) } public func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { diff --git a/Tablet/TableRowBuilder.swift b/Tablet/TableRowBuilder.swift index 72325a7..1af47fa 100644 --- a/Tablet/TableRowBuilder.swift +++ b/Tablet/TableRowBuilder.swift @@ -23,35 +23,6 @@ import Foundation public typealias ReturnValue = AnyObject? -enum ActionHandler { - - case Handler((data: ActionData) -> Void) - case ValueHandler((data: ActionData) -> AnyObject?) - - func invoke(data: ActionData) -> ReturnValue { - - switch (self) { - case .Handler(let handler): - handler(data: data) - return nil - case .ValueHandler(let handler): - return handler(data: data) - } - } -} - -public protocol RowBuilder { - - var reusableIdentifier: String { get } - var numberOfRows: Int { get } - - var rowHeight: Float { get } - var estimatedRowHeight: Float { get } - - func invoke(action action: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int, userInfo: [NSObject: AnyObject]?) -> AnyObject? - func registerCell(inTableView tableView: UITableView) -} - /** Responsible for building cells of given type and passing items to them. */ @@ -70,10 +41,6 @@ public class TableBaseRowBuilder CGFloat { + return 0 + } + // MARK: - Chaining actions - public func action(key: String, handler: (data: ActionData) -> Void) -> Self { @@ -170,6 +141,32 @@ public class TableRowBuilder : TableBaseRowBuilder { + + private var cachedHeights = [Int: CGFloat]() + private var prototypeCell: CellType? + + public override func rowHeight(index: Int) -> CGFloat { + + guard let cell = prototypeCell else { return 0 } + + let item = items[index] + + if let height = cachedHeights[item.hashValue] { + return height + } + + cell.configure(item) + cell.setNeedsLayout() + cell.layoutIfNeeded() + + let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1 + cachedHeights[item.hashValue] = height + + return height + } +} + public func +=(left: TableBaseRowBuilder, right: DataType) { left.append(items: [right]) } diff --git a/Tablet/Tablet.swift b/Tablet/Tablet.swift index 3a1f663..b960913 100644 --- a/Tablet/Tablet.swift +++ b/Tablet/Tablet.swift @@ -69,6 +69,23 @@ public class ActionData { } } +enum ActionHandler { + + case Handler((data: ActionData) -> Void) + case ValueHandler((data: ActionData) -> AnyObject?) + + func invoke(data: ActionData) -> ReturnValue { + + switch (self) { + case .Handler(let handler): + handler(data: data) + return nil + case .ValueHandler(let handler): + return handler(data: data) + } + } +} + /** A custom action that you can trigger from your cell. You can eacily catch actions using a chaining manner with your row builder. @@ -96,27 +113,3 @@ public class Action { } } -/** - 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 T - - static func reusableIdentifier() -> String - static func estimatedHeight() -> Float - static func defaultHeight() -> Float? - func configure(_: T) -} - -public extension ConfigurableCell where Self: UITableViewCell { - - static func reusableIdentifier() -> String { - return String(self) - } - - static func defaultHeight() -> Float? { - return nil - } -} \ No newline at end of file diff --git a/Tablet/Tablet.xcodeproj/project.pbxproj b/Tablet/Tablet.xcodeproj/project.pbxproj index b0a1e05..ed7ad7e 100644 --- a/Tablet/Tablet.xcodeproj/project.pbxproj +++ b/Tablet/Tablet.xcodeproj/project.pbxproj @@ -7,7 +7,8 @@ objects = { /* Begin PBXBuildFile section */ - 501C70391CF387090099458A /* TablePrototypeRowBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501C70381CF387090099458A /* TablePrototypeRowBuilder.swift */; }; + DA08A04F1CF3AB0C00BBF1F8 /* ConfigurableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA08A04E1CF3AB0C00BBF1F8 /* ConfigurableCell.swift */; }; + DA08A0511CF3AB6100BBF1F8 /* RowBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA08A0501CF3AB6100BBF1F8 /* RowBuilder.swift */; }; DAC2D6741C9D743D009E9C19 /* Tablet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DAC2D6691C9D743D009E9C19 /* Tablet.framework */; }; DAC2D6871C9D7517009E9C19 /* Tablet.h in Headers */ = {isa = PBXBuildFile; fileRef = DAC2D6851C9D7517009E9C19 /* Tablet.h */; settings = {ATTRIBUTES = (Public, ); }; }; DAC2D6901C9D799E009E9C19 /* TableDirector.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC2D68C1C9D799E009E9C19 /* TableDirector.swift */; }; @@ -28,7 +29,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 501C70381CF387090099458A /* TablePrototypeRowBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TablePrototypeRowBuilder.swift; sourceTree = ""; }; + DA08A04E1CF3AB0C00BBF1F8 /* ConfigurableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurableCell.swift; sourceTree = ""; }; + DA08A0501CF3AB6100BBF1F8 /* RowBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowBuilder.swift; sourceTree = ""; }; DAC2D6691C9D743D009E9C19 /* Tablet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Tablet.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DAC2D6731C9D743D009E9C19 /* TabletTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TabletTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; DAC2D6841C9D7517009E9C19 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -91,11 +93,12 @@ DAC2D68B1C9D7990009E9C19 /* Classes */ = { isa = PBXGroup; children = ( - DAC2D68F1C9D799E009E9C19 /* Tablet.swift */, + DA08A04E1CF3AB0C00BBF1F8 /* ConfigurableCell.swift */, + DA08A0501CF3AB6100BBF1F8 /* RowBuilder.swift */, DAC2D68C1C9D799E009E9C19 /* TableDirector.swift */, DAC2D68D1C9D799E009E9C19 /* TableRowBuilder.swift */, DAC2D68E1C9D799E009E9C19 /* TableSectionBuilder.swift */, - 501C70381CF387090099458A /* TablePrototypeRowBuilder.swift */, + DAC2D68F1C9D799E009E9C19 /* Tablet.swift */, ); name = Classes; sourceTree = ""; @@ -220,8 +223,9 @@ files = ( DAC2D6901C9D799E009E9C19 /* TableDirector.swift in Sources */, DAC2D6921C9D799E009E9C19 /* TableSectionBuilder.swift in Sources */, + DA08A0511CF3AB6100BBF1F8 /* RowBuilder.swift in Sources */, + DA08A04F1CF3AB0C00BBF1F8 /* ConfigurableCell.swift in Sources */, DAC2D6911C9D799E009E9C19 /* TableRowBuilder.swift in Sources */, - 501C70391CF387090099458A /* TablePrototypeRowBuilder.swift in Sources */, DAC2D6931C9D799E009E9C19 /* Tablet.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0;