From 4dfb77a54bc4823bf1eeb9e5cb24b425f745cd9d Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Fri, 4 Dec 2015 10:13:32 +0100 Subject: [PATCH 01/17] Imported ImmuTable --- .../System/WordPress-Bridging-Header.h | 2 + WordPress/Classes/Utility/ImmuTable.swift | 206 ++++++++++++++++++ .../ViewRelated/Cells/WPImmuTableCells.swift | 172 +++++++++++++++ WordPress/WordPress.xcodeproj/project.pbxproj | 16 ++ WordPress/WordPressTest/ImmuTableTest.swift | 82 +++++++ .../ImmuTableTestViewCellWithNib.xib | 32 +++ 6 files changed, 510 insertions(+) create mode 100644 WordPress/Classes/Utility/ImmuTable.swift create mode 100644 WordPress/Classes/ViewRelated/Cells/WPImmuTableCells.swift create mode 100644 WordPress/WordPressTest/ImmuTableTest.swift create mode 100644 WordPress/WordPressTest/Test Data/ImmuTableTestViewCellWithNib.xib diff --git a/WordPress/Classes/System/WordPress-Bridging-Header.h b/WordPress/Classes/System/WordPress-Bridging-Header.h index 49002c010caa..e8fd629c1f0a 100644 --- a/WordPress/Classes/System/WordPress-Bridging-Header.h +++ b/WordPress/Classes/System/WordPress-Bridging-Header.h @@ -11,6 +11,8 @@ #import "DDLogSwift.h" +#import "MediaService.h" + #import "Notification.h" #import "Notification+Internals.h" #import "NotificationsManager.h" diff --git a/WordPress/Classes/Utility/ImmuTable.swift b/WordPress/Classes/Utility/ImmuTable.swift new file mode 100644 index 000000000000..ec9b74b17641 --- /dev/null +++ b/WordPress/Classes/Utility/ImmuTable.swift @@ -0,0 +1,206 @@ +import Foundation +import UIKit + +public typealias ImmuTableActionType = (ImmuTableRow) -> Void + +public protocol Reusable { + static var reusableIdentifier: String { get } +} + +extension Reusable { + var reusableIdentifier: String { + get { + return self.dynamicType.reusableIdentifier + } + } +} + +public protocol ImmuTableRow: Reusable { + var action: ImmuTableActionType? { get } + static var cellClass: AnyClass { get } + func configureCell(cell: UITableViewCell) + static var registrable: CellRegistrable { get } + static var customHeight: Float? { get } +} + +public struct ImmuTableSection { + let headerText: String? + let rows: [ImmuTableRow] + let footerText: String? + + init(rows: [ImmuTableRow]) { + self.headerText = nil + self.rows = rows + self.footerText = nil + } + + init(headerText: String?, rows: [ImmuTableRow], footerText: String?) { + self.headerText = headerText + self.rows = rows + self.footerText = footerText + } +} + +public enum CellRegistrable { + case Nib(UINib) + case Class(AnyClass) +} + +public protocol CellRegistrator { + func register(registrable: CellRegistrable, cellReuseIdentifier: String) +} + + +extension UITableView: CellRegistrator { + public func register(registrable: CellRegistrable, cellReuseIdentifier: String) { + switch registrable { + case .Nib(let nib): + registerNib(nib, forCellReuseIdentifier: cellReuseIdentifier) + case .Class(let cellClass): + registerClass(cellClass, forCellReuseIdentifier: cellReuseIdentifier) + } + } +} + +public struct ImmuTable { + public let sections: [ImmuTableSection] + + public func rowAtIndexPath(indexPath: NSIndexPath) -> ImmuTableRow { + return sections[indexPath.section].rows[indexPath.row] + } + + public static func registerRows(rows: [ImmuTableRow.Type], tableView: CellRegistrator) { + let registrables = rows.reduce([:]) { + (var classes, row) -> [String: CellRegistrable] in + + classes[row.reusableIdentifier] = row.registrable + return classes + } + for (identifier, registrable) in registrables { + tableView.register(registrable, cellReuseIdentifier: identifier) + } + } +} + +public class ImmuTableDataSource: NSObject, UITableViewDataSource { + var viewModel: ImmuTable + var configureCell: ((UITableViewCell) -> Void)? + + init(viewModel: ImmuTable) { + self.viewModel = viewModel + } + + public func numberOfSectionsInTableView(tableView: UITableView) -> Int { + return viewModel.sections.count + } + + public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return viewModel.sections[section].rows.count + } + + public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + let row = viewModel.rowAtIndexPath(indexPath) + let cell = tableView.dequeueReusableCellWithIdentifier(row.reusableIdentifier, forIndexPath: indexPath) + + row.configureCell(cell) + + configureCell?(cell) + + return cell + } +} + +public class ImmuTableDelegate: NSObject, UITableViewDelegate { + var viewModel: ImmuTable + + init(viewModel: ImmuTable) { + self.viewModel = viewModel + } + + public func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { + let row = viewModel.rowAtIndexPath(indexPath) + if let action = row.action { + action(row) + } + } + + public func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { + let row = viewModel.rowAtIndexPath(indexPath) + if let customHeight = row.dynamicType.customHeight { + return CGFloat(customHeight) + } + return tableView.rowHeight + } +} + +public struct ImmuTableViewHandler { + let target: UITableViewController + + init(takeOver target: UITableViewController) { + self.target = target + self.target.tableView.dataSource = dataSource + self.target.tableView.delegate = delegate + } + + var viewModel = ImmuTable(sections: []) { + didSet { + dataSource.viewModel = viewModel + delegate.viewModel = viewModel + if target.isViewLoaded() { + target.tableView.reloadData() + } + } + } + + lazy var dataSource: ImmuTableDataSource = { + return ImmuTableDataSource(viewModel: self.viewModel) + }() + + lazy var delegate: ImmuTableDelegate = { + return ImmuTableDelegate(viewModel: self.viewModel) + }() +} + +protocol CustomImmuTableRow: ImmuTableRow { + typealias CellType: AnyObject +} + +extension CustomImmuTableRow { + static var reusableIdentifier: String { + get { + return NSStringFromClass(cellClass) + } + } + + static var cellClass: AnyClass { + get { + return CellType.self + } + } +} + +protocol CustomCellImmuTableRow: CustomImmuTableRow { } +extension CustomCellImmuTableRow { + static var customHeight: Float? { + get { + return nil + } + } + static var registrable: CellRegistrable { + get { + return .Class(cellClass) + } + } +} + +protocol CustomNibImmuTableRow: CustomImmuTableRow { + static var nib: UINib { get } +} +extension CustomNibImmuTableRow { + static var registrable: CellRegistrable { + get { + return .Nib(nib) + } + } +} + diff --git a/WordPress/Classes/ViewRelated/Cells/WPImmuTableCells.swift b/WordPress/Classes/ViewRelated/Cells/WPImmuTableCells.swift new file mode 100644 index 000000000000..2dbde9fe2ba7 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Cells/WPImmuTableCells.swift @@ -0,0 +1,172 @@ +import Foundation +import UIKit +import WordPressShared.WPTableViewCell + +class WPReusableTableViewCell: WPTableViewCell { + override func prepareForReuse() { + super.prepareForReuse() + + textLabel?.text = nil + detailTextLabel?.text = nil + imageView?.image = nil + accessoryType = .None + selectionStyle = .Default + } +} + +class WPTableViewCellDefault: WPReusableTableViewCell { + override init(style: UITableViewCellStyle, reuseIdentifier: String?) { + super.init(style: .Default, reuseIdentifier: reuseIdentifier) + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } +} + +class WPTableViewCellSubtitle: WPReusableTableViewCell { + override init(style: UITableViewCellStyle, reuseIdentifier: String?) { + super.init(style: .Subtitle, reuseIdentifier: reuseIdentifier) + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } +} + +class WPTableViewCellValue1: WPReusableTableViewCell { + override init(style: UITableViewCellStyle, reuseIdentifier: String?) { + super.init(style: .Value1, reuseIdentifier: reuseIdentifier) + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } +} + +class WPTableViewCellValue2: WPReusableTableViewCell { + override init(style: UITableViewCellStyle, reuseIdentifier: String?) { + super.init(style: .Value2, reuseIdentifier: reuseIdentifier) + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } +} + +struct NavigationItemRow : CustomCellImmuTableRow { + typealias CellType = WPTableViewCellDefault + + let title: String + let action: ImmuTableActionType? + + func configureCell(cell: UITableViewCell) { + let cell = cell as! CellType + + cell.textLabel?.text = title + cell.accessoryType = .DisclosureIndicator + + WPStyleGuide.configureTableViewCell(cell) + } +} + +struct EditableTextRow : CustomCellImmuTableRow { + typealias CellType = WPTableViewCellValue1 + + let title: String + let value: String + let action: ImmuTableActionType? + + func configureCell(cell: UITableViewCell) { + cell.textLabel?.text = title + cell.detailTextLabel?.text = value + cell.accessoryType = .DisclosureIndicator + + WPStyleGuide.configureTableViewCell(cell) + } +} + +struct TextRow : CustomCellImmuTableRow { + typealias CellType = WPTableViewCellValue1 + + let title: String + let value: String + let action: ImmuTableActionType? = nil + + func configureCell(cell: UITableViewCell) { + cell.textLabel?.text = title + cell.detailTextLabel?.text = value + cell.selectionStyle = .None + + WPStyleGuide.configureTableViewCell(cell) + } +} + +struct LinkRow : CustomCellImmuTableRow { + typealias CellType = WPTableViewCellValue1 + + let title: String + let action: ImmuTableActionType? + + func configureCell(cell: UITableViewCell) { + cell.textLabel?.text = title + + WPStyleGuide.configureTableViewActionCell(cell) + } +} + +struct LinkWithValueRow : CustomCellImmuTableRow { + typealias CellType = WPTableViewCellValue1 + + let title: String + let value: String + let action: ImmuTableActionType? + + func configureCell(cell: UITableViewCell) { + cell.textLabel?.text = title + cell.detailTextLabel?.text = value + + WPStyleGuide.configureTableViewActionCell(cell) + } +} + +struct SwitchRow: CustomCellImmuTableRow { + typealias CellType = SwitchTableViewCell + + let title: String + let value: Bool + let action: ImmuTableActionType? = nil + let onChange: Bool -> Void + + func configureCell(cell: UITableViewCell) { + let cell = cell as! CellType + + cell.textLabel?.text = title + cell.selectionStyle = .None + cell.on = value + cell.onChange = onChange + } +} + +struct MediaSizeRow: CustomNibImmuTableRow { + typealias CellType = MediaSizeSliderCell + static let nib = UINib(nibName: "MediaSizeSliderCell", bundle: NSBundle(forClass: CellType.self)) + static let customHeight: Float? = 108.0 + + let title: String + let value: Int + let onChange: Int -> Void + + let action: ImmuTableActionType? = nil + + func configureCell(cell: UITableViewCell) { + let cell = cell as! CellType + + cell.title = title + cell.value = value + cell.onChange = onChange + + cell.minValue = MediaMinImageSizeDimension + cell.maxValue = MediaMaxImageSizeDimension + } +} diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 8a970ecfea39..64dfe1af4f7a 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -565,6 +565,10 @@ E1E4CE0B1773C59B00430844 /* WPAvatarSource.m in Sources */ = {isa = PBXBuildFile; fileRef = E1E4CE0A1773C59B00430844 /* WPAvatarSource.m */; }; E1E4CE0D177439D100430844 /* WPAvatarSourceTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E1E4CE0C177439D100430844 /* WPAvatarSourceTest.m */; }; E1E4CE0F1774563F00430844 /* misteryman.jpg in Resources */ = {isa = PBXBuildFile; fileRef = E1E4CE0E1774531500430844 /* misteryman.jpg */; }; + E1EBC36F1C118EA500F638E0 /* ImmuTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBC36E1C118EA500F638E0 /* ImmuTable.swift */; }; + E1EBC3711C118EB200F638E0 /* WPImmuTableCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBC3701C118EB200F638E0 /* WPImmuTableCells.swift */; }; + E1EBC3731C118ED200F638E0 /* ImmuTableTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1EBC3721C118ED200F638E0 /* ImmuTableTest.swift */; }; + E1EBC3751C118EDE00F638E0 /* ImmuTableTestViewCellWithNib.xib in Resources */ = {isa = PBXBuildFile; fileRef = E1EBC3741C118EDE00F638E0 /* ImmuTableTestViewCellWithNib.xib */; }; E1F5A1BC1771C90A00E0495F /* WPTableImageSource.m in Sources */ = {isa = PBXBuildFile; fileRef = E1F5A1BB1771C90A00E0495F /* WPTableImageSource.m */; }; E1F80825146420B000726BC7 /* UIImageView+Gravatar.m in Sources */ = {isa = PBXBuildFile; fileRef = E1F80824146420B000726BC7 /* UIImageView+Gravatar.m */; }; E1F8E1231B0B411E0073E628 /* JetpackService.m in Sources */ = {isa = PBXBuildFile; fileRef = E1F8E1221B0B411E0073E628 /* JetpackService.m */; }; @@ -1656,6 +1660,10 @@ E1E4CE0C177439D100430844 /* WPAvatarSourceTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WPAvatarSourceTest.m; sourceTree = ""; }; E1E4CE0E1774531500430844 /* misteryman.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = misteryman.jpg; sourceTree = ""; }; E1E977BC17B0FA9A00AFB867 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Localizable.strings; sourceTree = ""; }; + E1EBC36E1C118EA500F638E0 /* ImmuTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImmuTable.swift; sourceTree = ""; }; + E1EBC3701C118EB200F638E0 /* WPImmuTableCells.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WPImmuTableCells.swift; sourceTree = ""; }; + E1EBC3721C118ED200F638E0 /* ImmuTableTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImmuTableTest.swift; sourceTree = ""; }; + E1EBC3741C118EDE00F638E0 /* ImmuTableTestViewCellWithNib.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ImmuTableTestViewCellWithNib.xib; sourceTree = ""; }; E1F5A1BA1771C90A00E0495F /* WPTableImageSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WPTableImageSource.h; sourceTree = ""; }; E1F5A1BB1771C90A00E0495F /* WPTableImageSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WPTableImageSource.m; sourceTree = ""; }; E1F80823146420B000726BC7 /* UIImageView+Gravatar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImageView+Gravatar.h"; sourceTree = ""; }; @@ -2549,6 +2557,7 @@ 5D839AA7187F0D6B00811F4A /* PostFeaturedImageCell.m */, 5D839AA9187F0D8000811F4A /* PostGeolocationCell.h */, 5D839AAA187F0D8000811F4A /* PostGeolocationCell.m */, + E1EBC3701C118EB200F638E0 /* WPImmuTableCells.swift */, FF0AAE081A1509C50089841D /* WPProgressTableViewCell.h */, FF0AAE091A150A560089841D /* WPProgressTableViewCell.m */, ); @@ -2655,6 +2664,7 @@ 852416D11A12ED690030700C /* AppRatingUtilityTests.m */, 5DA988051AEEA594002AFB12 /* DisplayableImageHelperTest.m */, E1266D2E1BBEC37B00FCB6B6 /* GravatarTest.swift */, + E1EBC3721C118ED200F638E0 /* ImmuTableTest.swift */, 93A379EB19FFBF7900415023 /* KeychainTest.m */, 5948AD101AB73D19006E8882 /* WPAppAnalyticsTests.m */, E1E4CE0C177439D100430844 /* WPAvatarSourceTest.m */, @@ -2716,6 +2726,7 @@ 313692771A5D6F7900EBE645 /* HelpshiftUtils.h */, E1266D2C1BBE8B9A00FCB6B6 /* Gravatar.swift */, 313692781A5D6F7900EBE645 /* HelpshiftUtils.m */, + E1EBC36E1C118EA500F638E0 /* ImmuTable.swift */, 5DB4683918A2E718004A89A9 /* LocationService.h */, 5DB4683A18A2E718004A89A9 /* LocationService.m */, 5D3E334C15EEBB6B005FC6F2 /* ReachabilityUtils.h */, @@ -3656,6 +3667,7 @@ E16AB94414D9A13A0047A2E5 /* Mock Data */ = { isa = PBXGroup; children = ( + E1EBC3741C118EDE00F638E0 /* ImmuTableTestViewCellWithNib.xib */, B5AEEC741ACACFDA008BF2A4 /* notifications-badge.json */, B5AEEC751ACACFDA008BF2A4 /* notifications-like.json */, B5AEEC771ACACFDA008BF2A4 /* notifications-new-follower.json */, @@ -4125,6 +4137,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + E1EBC3751C118EDE00F638E0 /* ImmuTableTestViewCellWithNib.xib in Resources */, B5A6BB8C1BF4DF38002F6A96 /* rest-site-settings.json in Resources */, E16AB93414D978240047A2E5 /* InfoPlist.strings in Resources */, 93594BD5191D2F5A0079E6B2 /* stats-batch.json in Resources */, @@ -4674,6 +4687,7 @@ E240859C183D82AE002EB0EF /* WPAnimatedBox.m in Sources */, 852416CF1A12EBDD0030700C /* AppRatingUtility.m in Sources */, B5CC05F91962186D00975CAC /* Meta.m in Sources */, + E1EBC3711C118EB200F638E0 /* WPImmuTableCells.swift in Sources */, 5D20A6531982D56600463A91 /* FollowedSitesViewController.m in Sources */, 5D8D53F119250412003C8859 /* BlogSelectorViewController.m in Sources */, 5D3D559718F88C3500782892 /* ReaderPostService.m in Sources */, @@ -4859,6 +4873,7 @@ 857610D618C0377300EDF406 /* StatsWebViewController.m in Sources */, 5DBFC8A71A9BC34F00E00DE4 /* PostListViewController.m in Sources */, 5D08B90419648C3400D5B381 /* ReaderSubscriptionViewController.m in Sources */, + E1EBC36F1C118EA500F638E0 /* ImmuTable.swift in Sources */, 5DDC44671A72BB07007F538E /* ReaderViewController.m in Sources */, E1D086E2194214C600F0CC19 /* NSDate+WordPressJSON.m in Sources */, 5D839AAB187F0D8000811F4A /* PostGeolocationCell.m in Sources */, @@ -4899,6 +4914,7 @@ E66969CD1B9E2EBF00EC9C00 /* SafeReaderTopicToReaderTopic.m in Sources */, E66969C81B9E0A6800EC9C00 /* ReaderTopicServiceTest.swift in Sources */, 931D26F519ED7E6D00114F17 /* BlogJetpackTest.m in Sources */, + E1EBC3731C118ED200F638E0 /* ImmuTableTest.swift in Sources */, 93B853231B4416A30064FE72 /* WPAnalyticsTrackerAutomatticTracksTests.m in Sources */, E66969CA1B9E0C4F00EC9C00 /* ReaderTopicServiceRemoteTests.m in Sources */, 5DFA7EBC1AF7B8D30072023B /* NSDateStringFormattingTest.m in Sources */, diff --git a/WordPress/WordPressTest/ImmuTableTest.swift b/WordPress/WordPressTest/ImmuTableTest.swift new file mode 100644 index 000000000000..2a8c6485752b --- /dev/null +++ b/WordPress/WordPressTest/ImmuTableTest.swift @@ -0,0 +1,82 @@ +import XCTest +@testable import WordPress + +class ImmuTableTest: XCTestCase { + + func testRegisterRowsWorksWithNibs() { + let mockTable = MockTableView() + let rowsToRegister: [ImmuTableRow.Type] = [ + TestWithNibImmuTableRow.self + ] + + ImmuTable.registerRows(rowsToRegister, tableView: mockTable) + XCTAssertEqual(mockTable.registeredNibs.count, 1, "The table should have registered a nib for TestWithNibImmuTableRow") + XCTAssertEqual(mockTable.registeredClasses.count, 0, "The table shouldn't have registered any classes for TestWithNibImmuTableRow") + } + + func testRegisterRowsDoesntRegisterSameCellTwice() { + let mockTable = MockTableView() + let rowsToRegister: [ImmuTableRow.Type] = [ + BasicImmuTableRow.self, + ImageImmuTableRow.self, + TestImmuTableRow.self + ] + + ImmuTable.registerRows(rowsToRegister, tableView: mockTable) + XCTAssertEqual(2, mockTable.registeredClasses.count, "Each cell class shouldn't be registered more than once") + } + +} + +class TestTableViewCell: UITableViewCell {} +class ImmuTableTestViewCellWithNib: UITableViewCell {} + +struct BasicImmuTableRow: CustomCellImmuTableRow { + typealias CellType = UITableViewCell + let title: String + var action: ImmuTableActionType? = nil + func configureCell(cell: UITableViewCell) { + } +} + +struct ImageImmuTableRow: CustomCellImmuTableRow { + typealias CellType = UITableViewCell + let title: String + let image: UIImage + var action: ImmuTableActionType? = nil + func configureCell(cell: UITableViewCell) { + } +} + +struct TestImmuTableRow: CustomCellImmuTableRow { + typealias CellType = TestTableViewCell + let title: String + var action: ImmuTableActionType? = nil + func configureCell(cell: UITableViewCell) { + } +} + +struct TestWithNibImmuTableRow: CustomNibImmuTableRow { + typealias CellType = ImmuTableTestViewCellWithNib + static let nib = UINib(nibName: "ImmuTableTestViewCellWithNib", bundle: NSBundle(forClass: ImmuTableTestViewCellWithNib.self)) + static let customHeight: Float? = nil + var action: ImmuTableActionType? = nil + func configureCell(cell: UITableViewCell) { + } +} + +class MockTableView: CellRegistrator { + var registeredClasses = [(String, AnyClass)]() + var registeredNibs = [(String, UINib)]() + func register(registrable: CellRegistrable, cellReuseIdentifier identifier: String) { + switch registrable { + case .Class(let cellClass): + registeredClasses.append((identifier, cellClass)) + case .Nib(let nib): + registeredNibs.append((identifier, nib)) + } + } + func registerClass(cellClass: AnyClass?, forCellReuseIdentifier identifier: String) { + registeredClasses.append((identifier, cellClass!)) + } +} diff --git a/WordPress/WordPressTest/Test Data/ImmuTableTestViewCellWithNib.xib b/WordPress/WordPressTest/Test Data/ImmuTableTestViewCellWithNib.xib new file mode 100644 index 000000000000..a45d9bd547f3 --- /dev/null +++ b/WordPress/WordPressTest/Test Data/ImmuTableTestViewCellWithNib.xib @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + From 7c2760950d33e70ac93abb48682927469b9ef0ad Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Fri, 4 Dec 2015 10:35:51 +0100 Subject: [PATCH 02/17] Use cell's height constant for model height --- WordPress/Classes/ViewRelated/Cells/MediaSizeSliderCell.swift | 2 +- WordPress/Classes/ViewRelated/Cells/WPImmuTableCells.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WordPress/Classes/ViewRelated/Cells/MediaSizeSliderCell.swift b/WordPress/Classes/ViewRelated/Cells/MediaSizeSliderCell.swift index 86e690c9b4c1..f3bea7fea52b 100644 --- a/WordPress/Classes/ViewRelated/Cells/MediaSizeSliderCell.swift +++ b/WordPress/Classes/ViewRelated/Cells/MediaSizeSliderCell.swift @@ -16,7 +16,7 @@ class MediaSizeSliderCell: UITableViewCell { } } - static let height = 108.0 + static let height: Float = 108.0 // MARK: - Public interface var value: Int { diff --git a/WordPress/Classes/ViewRelated/Cells/WPImmuTableCells.swift b/WordPress/Classes/ViewRelated/Cells/WPImmuTableCells.swift index 2dbde9fe2ba7..ee37811d704b 100644 --- a/WordPress/Classes/ViewRelated/Cells/WPImmuTableCells.swift +++ b/WordPress/Classes/ViewRelated/Cells/WPImmuTableCells.swift @@ -151,7 +151,7 @@ struct SwitchRow: CustomCellImmuTableRow { struct MediaSizeRow: CustomNibImmuTableRow { typealias CellType = MediaSizeSliderCell static let nib = UINib(nibName: "MediaSizeSliderCell", bundle: NSBundle(forClass: CellType.self)) - static let customHeight: Float? = 108.0 + static let customHeight: Float? = CellType.height let title: String let value: Int From a7d7c31a67c2ced5b3db0a4a659cb9f66b057265 Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Tue, 8 Dec 2015 15:23:15 +0100 Subject: [PATCH 03/17] Removed ImmuTableDataSource configureCell handler --- WordPress/Classes/Utility/ImmuTable.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/WordPress/Classes/Utility/ImmuTable.swift b/WordPress/Classes/Utility/ImmuTable.swift index ec9b74b17641..e0f325ae23c3 100644 --- a/WordPress/Classes/Utility/ImmuTable.swift +++ b/WordPress/Classes/Utility/ImmuTable.swift @@ -84,7 +84,6 @@ public struct ImmuTable { public class ImmuTableDataSource: NSObject, UITableViewDataSource { var viewModel: ImmuTable - var configureCell: ((UITableViewCell) -> Void)? init(viewModel: ImmuTable) { self.viewModel = viewModel @@ -104,8 +103,6 @@ public class ImmuTableDataSource: NSObject, UITableViewDataSource { row.configureCell(cell) - configureCell?(cell) - return cell } } From f45fa5b9303d1755950c60c13a8f91a919d8cf8d Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Tue, 8 Dec 2015 15:24:32 +0100 Subject: [PATCH 04/17] Shorter optional syntax --- WordPress/Classes/Utility/ImmuTable.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/WordPress/Classes/Utility/ImmuTable.swift b/WordPress/Classes/Utility/ImmuTable.swift index e0f325ae23c3..5e7db0758c5d 100644 --- a/WordPress/Classes/Utility/ImmuTable.swift +++ b/WordPress/Classes/Utility/ImmuTable.swift @@ -116,9 +116,7 @@ public class ImmuTableDelegate: NSObject, UITableViewDelegate { public func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { let row = viewModel.rowAtIndexPath(indexPath) - if let action = row.action { - action(row) - } + row.action?(row) } public func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { From c5633ad3935beb181bea3c164adf78b69e744efd Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Tue, 8 Dec 2015 15:33:48 +0100 Subject: [PATCH 05/17] Keep unowned reference to target controller --- WordPress/Classes/Utility/ImmuTable.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/Classes/Utility/ImmuTable.swift b/WordPress/Classes/Utility/ImmuTable.swift index 5e7db0758c5d..0d088e8a1db0 100644 --- a/WordPress/Classes/Utility/ImmuTable.swift +++ b/WordPress/Classes/Utility/ImmuTable.swift @@ -129,7 +129,7 @@ public class ImmuTableDelegate: NSObject, UITableViewDelegate { } public struct ImmuTableViewHandler { - let target: UITableViewController + unowned let target: UITableViewController init(takeOver target: UITableViewController) { self.target = target From 3685a9f89026cf86fe8be9cceb045ca174c84bc4 Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Tue, 8 Dec 2015 16:11:06 +0100 Subject: [PATCH 06/17] Marked types/properties/methods public --- WordPress/Classes/Utility/ImmuTable.swift | 26 +++++++++++++---------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/WordPress/Classes/Utility/ImmuTable.swift b/WordPress/Classes/Utility/ImmuTable.swift index 0d088e8a1db0..a10d87a3d13e 100644 --- a/WordPress/Classes/Utility/ImmuTable.swift +++ b/WordPress/Classes/Utility/ImmuTable.swift @@ -28,13 +28,13 @@ public struct ImmuTableSection { let rows: [ImmuTableRow] let footerText: String? - init(rows: [ImmuTableRow]) { + public init(rows: [ImmuTableRow]) { self.headerText = nil self.rows = rows self.footerText = nil } - init(headerText: String?, rows: [ImmuTableRow], footerText: String?) { + public init(headerText: String?, rows: [ImmuTableRow], footerText: String?) { self.headerText = headerText self.rows = rows self.footerText = footerText @@ -65,6 +65,10 @@ extension UITableView: CellRegistrator { public struct ImmuTable { public let sections: [ImmuTableSection] + public init(sections: [ImmuTableSection]) { + self.sections = sections + } + public func rowAtIndexPath(indexPath: NSIndexPath) -> ImmuTableRow { return sections[indexPath.section].rows[indexPath.row] } @@ -131,13 +135,13 @@ public class ImmuTableDelegate: NSObject, UITableViewDelegate { public struct ImmuTableViewHandler { unowned let target: UITableViewController - init(takeOver target: UITableViewController) { + public init(takeOver target: UITableViewController) { self.target = target self.target.tableView.dataSource = dataSource self.target.tableView.delegate = delegate } - var viewModel = ImmuTable(sections: []) { + public var viewModel = ImmuTable(sections: []) { didSet { dataSource.viewModel = viewModel delegate.viewModel = viewModel @@ -150,38 +154,38 @@ public struct ImmuTableViewHandler { lazy var dataSource: ImmuTableDataSource = { return ImmuTableDataSource(viewModel: self.viewModel) }() - + lazy var delegate: ImmuTableDelegate = { return ImmuTableDelegate(viewModel: self.viewModel) }() } -protocol CustomImmuTableRow: ImmuTableRow { +public protocol CustomImmuTableRow: ImmuTableRow { typealias CellType: AnyObject } extension CustomImmuTableRow { - static var reusableIdentifier: String { + public static var reusableIdentifier: String { get { return NSStringFromClass(cellClass) } } - static var cellClass: AnyClass { + public static var cellClass: AnyClass { get { return CellType.self } } } -protocol CustomCellImmuTableRow: CustomImmuTableRow { } +public protocol CustomCellImmuTableRow: CustomImmuTableRow { } extension CustomCellImmuTableRow { - static var customHeight: Float? { + public static var customHeight: Float? { get { return nil } } - static var registrable: CellRegistrable { + public static var registrable: CellRegistrable { get { return .Class(cellClass) } From e6d020ebac27281f4dc537cc821c3e2893468f2c Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Tue, 8 Dec 2015 17:06:13 +0100 Subject: [PATCH 07/17] Merged data source and delegate into handler --- WordPress/Classes/Utility/ImmuTable.swift | 56 +++++++---------------- 1 file changed, 16 insertions(+), 40 deletions(-) diff --git a/WordPress/Classes/Utility/ImmuTable.swift b/WordPress/Classes/Utility/ImmuTable.swift index a10d87a3d13e..f027580c36de 100644 --- a/WordPress/Classes/Utility/ImmuTable.swift +++ b/WordPress/Classes/Utility/ImmuTable.swift @@ -86,11 +86,23 @@ public struct ImmuTable { } } -public class ImmuTableDataSource: NSObject, UITableViewDataSource { - var viewModel: ImmuTable +public class ImmuTableViewHandler: NSObject, UITableViewDataSource, UITableViewDelegate { + unowned let target: UITableViewController - init(viewModel: ImmuTable) { - self.viewModel = viewModel + public init(takeOver target: UITableViewController) { + self.target = target + super.init() + + self.target.tableView.dataSource = self + self.target.tableView.delegate = self + } + + public var viewModel = ImmuTable(sections: []) { + didSet { + if target.isViewLoaded() { + target.tableView.reloadData() + } + } } public func numberOfSectionsInTableView(tableView: UITableView) -> Int { @@ -109,14 +121,6 @@ public class ImmuTableDataSource: NSObject, UITableViewDataSource { return cell } -} - -public class ImmuTableDelegate: NSObject, UITableViewDelegate { - var viewModel: ImmuTable - - init(viewModel: ImmuTable) { - self.viewModel = viewModel - } public func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { let row = viewModel.rowAtIndexPath(indexPath) @@ -132,34 +136,6 @@ public class ImmuTableDelegate: NSObject, UITableViewDelegate { } } -public struct ImmuTableViewHandler { - unowned let target: UITableViewController - - public init(takeOver target: UITableViewController) { - self.target = target - self.target.tableView.dataSource = dataSource - self.target.tableView.delegate = delegate - } - - public var viewModel = ImmuTable(sections: []) { - didSet { - dataSource.viewModel = viewModel - delegate.viewModel = viewModel - if target.isViewLoaded() { - target.tableView.reloadData() - } - } - } - - lazy var dataSource: ImmuTableDataSource = { - return ImmuTableDataSource(viewModel: self.viewModel) - }() - - lazy var delegate: ImmuTableDelegate = { - return ImmuTableDelegate(viewModel: self.viewModel) - }() -} - public protocol CustomImmuTableRow: ImmuTableRow { typealias CellType: AnyObject } From 61d2f72bf28a53a1deaede83a86c529f826c4cea Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Tue, 8 Dec 2015 18:22:47 +0100 Subject: [PATCH 08/17] Added initial documentation for ImmuTable --- WordPress/Classes/Utility/ImmuTable.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/WordPress/Classes/Utility/ImmuTable.swift b/WordPress/Classes/Utility/ImmuTable.swift index f027580c36de..e1005b13ff8a 100644 --- a/WordPress/Classes/Utility/ImmuTable.swift +++ b/WordPress/Classes/Utility/ImmuTable.swift @@ -62,6 +62,18 @@ extension UITableView: CellRegistrator { } } +/** + ImmuTable represents the view model for a static UITableView. + + ImmuTable consists of zero or more sections, each one containing zero or more rows, + and an optional header and footer text. + + Each row contains the model necessary to configure a specific type of UITableViewCell. + + - attention: before using any ImmuTableRow type, you need to call `registerRows(_:tableView:)` + passing the row type. This is needed so ImmuTable can register the class or nib with the table view. + If you fail to do this, UIKit will raise an exception when it tries to load the row. +*/ public struct ImmuTable { public let sections: [ImmuTableSection] From 0f658d47ec73dfb5c1058945d5f40f91e127ff43 Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Tue, 8 Dec 2015 19:09:22 +0100 Subject: [PATCH 09/17] Simplify ImmuTableRow requirements Several changes to make the API simpler: - Only one row protocol: dropped CustomImmuTableRow, CustomCellImmuTableRow, and CustomNibImmuTableRow in favor of just ImmuTableRow. - Moved all the cell class and reusable identifier data and logic inside the registrable enum. - Renamed CellRegistrable to ImmuTableCell for clarity. - Provided a default (nil) implementation for customHeight --- WordPress/Classes/Utility/ImmuTable.swift | 120 +++++++----------- .../ViewRelated/Cells/WPImmuTableCells.swift | 36 +++--- WordPress/WordPressTest/ImmuTableTest.swift | 26 ++-- 3 files changed, 81 insertions(+), 101 deletions(-) diff --git a/WordPress/Classes/Utility/ImmuTable.swift b/WordPress/Classes/Utility/ImmuTable.swift index e1005b13ff8a..1cd3c201bbb5 100644 --- a/WordPress/Classes/Utility/ImmuTable.swift +++ b/WordPress/Classes/Utility/ImmuTable.swift @@ -3,24 +3,25 @@ import UIKit public typealias ImmuTableActionType = (ImmuTableRow) -> Void -public protocol Reusable { - static var reusableIdentifier: String { get } +public protocol ImmuTableRow { + var action: ImmuTableActionType? { get } + func configureCell(cell: UITableViewCell) + static var cell: ImmuTableCell { get } + static var customHeight: Float? { get } } -extension Reusable { - var reusableIdentifier: String { - get { - return self.dynamicType.reusableIdentifier - } +extension ImmuTableRow { + public var reusableIdentifier: String { + return self.dynamicType.cell.reusableIdentifier } -} -public protocol ImmuTableRow: Reusable { - var action: ImmuTableActionType? { get } - static var cellClass: AnyClass { get } - func configureCell(cell: UITableViewCell) - static var registrable: CellRegistrable { get } - static var customHeight: Float? { get } + public var cellClass: UITableViewCell.Type { + return self.dynamicType.cell.cellClass + } + + public static var customHeight: Float? { + return nil; + } } public struct ImmuTableSection { @@ -41,39 +42,57 @@ public struct ImmuTableSection { } } -public enum CellRegistrable { - case Nib(UINib) - case Class(AnyClass) +public enum ImmuTableCell { + case Nib(UINib, UITableViewCell.Type) + case Class(UITableViewCell.Type) + + public var reusableIdentifier: String { + switch self { + case .Class(let cellClass): + return NSStringFromClass(cellClass) + case .Nib(_, let cellClass): + return NSStringFromClass(cellClass) + } + } + + public var cellClass: UITableViewCell.Type { + switch self { + case .Class(let cellClass): + return cellClass + case .Nib(_, let cellClass): + return cellClass + } + } } public protocol CellRegistrator { - func register(registrable: CellRegistrable, cellReuseIdentifier: String) + func register(cell: ImmuTableCell, cellReuseIdentifier: String) } extension UITableView: CellRegistrator { - public func register(registrable: CellRegistrable, cellReuseIdentifier: String) { - switch registrable { - case .Nib(let nib): - registerNib(nib, forCellReuseIdentifier: cellReuseIdentifier) + public func register(cell: ImmuTableCell, cellReuseIdentifier: String) { + switch cell { + case .Nib(let nib, _): + registerNib(nib, forCellReuseIdentifier: cell.reusableIdentifier) case .Class(let cellClass): - registerClass(cellClass, forCellReuseIdentifier: cellReuseIdentifier) + registerClass(cellClass, forCellReuseIdentifier: cell.reusableIdentifier) } } } /** ImmuTable represents the view model for a static UITableView. - + ImmuTable consists of zero or more sections, each one containing zero or more rows, and an optional header and footer text. - + Each row contains the model necessary to configure a specific type of UITableViewCell. - + - attention: before using any ImmuTableRow type, you need to call `registerRows(_:tableView:)` passing the row type. This is needed so ImmuTable can register the class or nib with the table view. If you fail to do this, UIKit will raise an exception when it tries to load the row. -*/ + */ public struct ImmuTable { public let sections: [ImmuTableSection] @@ -87,9 +106,9 @@ public struct ImmuTable { public static func registerRows(rows: [ImmuTableRow.Type], tableView: CellRegistrator) { let registrables = rows.reduce([:]) { - (var classes, row) -> [String: CellRegistrable] in + (var classes, row) -> [String: ImmuTableCell] in - classes[row.reusableIdentifier] = row.registrable + classes[row.cell.reusableIdentifier] = row.cell return classes } for (identifier, registrable) in registrables { @@ -148,46 +167,3 @@ public class ImmuTableViewHandler: NSObject, UITableViewDataSource, UITableViewD } } -public protocol CustomImmuTableRow: ImmuTableRow { - typealias CellType: AnyObject -} - -extension CustomImmuTableRow { - public static var reusableIdentifier: String { - get { - return NSStringFromClass(cellClass) - } - } - - public static var cellClass: AnyClass { - get { - return CellType.self - } - } -} - -public protocol CustomCellImmuTableRow: CustomImmuTableRow { } -extension CustomCellImmuTableRow { - public static var customHeight: Float? { - get { - return nil - } - } - public static var registrable: CellRegistrable { - get { - return .Class(cellClass) - } - } -} - -protocol CustomNibImmuTableRow: CustomImmuTableRow { - static var nib: UINib { get } -} -extension CustomNibImmuTableRow { - static var registrable: CellRegistrable { - get { - return .Nib(nib) - } - } -} - diff --git a/WordPress/Classes/ViewRelated/Cells/WPImmuTableCells.swift b/WordPress/Classes/ViewRelated/Cells/WPImmuTableCells.swift index ee37811d704b..b64b5d72b25a 100644 --- a/WordPress/Classes/ViewRelated/Cells/WPImmuTableCells.swift +++ b/WordPress/Classes/ViewRelated/Cells/WPImmuTableCells.swift @@ -54,15 +54,13 @@ class WPTableViewCellValue2: WPReusableTableViewCell { } } -struct NavigationItemRow : CustomCellImmuTableRow { - typealias CellType = WPTableViewCellDefault +struct NavigationItemRow : ImmuTableRow { + static let cell = ImmuTableCell.Class(WPTableViewCellDefault) let title: String let action: ImmuTableActionType? func configureCell(cell: UITableViewCell) { - let cell = cell as! CellType - cell.textLabel?.text = title cell.accessoryType = .DisclosureIndicator @@ -70,8 +68,8 @@ struct NavigationItemRow : CustomCellImmuTableRow { } } -struct EditableTextRow : CustomCellImmuTableRow { - typealias CellType = WPTableViewCellValue1 +struct EditableTextRow : ImmuTableRow { + static let cell = ImmuTableCell.Class(WPTableViewCellValue1) let title: String let value: String @@ -86,8 +84,8 @@ struct EditableTextRow : CustomCellImmuTableRow { } } -struct TextRow : CustomCellImmuTableRow { - typealias CellType = WPTableViewCellValue1 +struct TextRow : ImmuTableRow { + static let cell = ImmuTableCell.Class(WPTableViewCellValue1) let title: String let value: String @@ -102,8 +100,8 @@ struct TextRow : CustomCellImmuTableRow { } } -struct LinkRow : CustomCellImmuTableRow { - typealias CellType = WPTableViewCellValue1 +struct LinkRow : ImmuTableRow { + static let cell = ImmuTableCell.Class(WPTableViewCellValue1) let title: String let action: ImmuTableActionType? @@ -115,8 +113,8 @@ struct LinkRow : CustomCellImmuTableRow { } } -struct LinkWithValueRow : CustomCellImmuTableRow { - typealias CellType = WPTableViewCellValue1 +struct LinkWithValueRow : ImmuTableRow { + static let cell = ImmuTableCell.Class(WPTableViewCellValue1) let title: String let value: String @@ -130,8 +128,8 @@ struct LinkWithValueRow : CustomCellImmuTableRow { } } -struct SwitchRow: CustomCellImmuTableRow { - typealias CellType = SwitchTableViewCell +struct SwitchRow: ImmuTableRow { + static let cell = ImmuTableCell.Class(SwitchTableViewCell) let title: String let value: Bool @@ -139,7 +137,7 @@ struct SwitchRow: CustomCellImmuTableRow { let onChange: Bool -> Void func configureCell(cell: UITableViewCell) { - let cell = cell as! CellType + let cell = cell as! SwitchTableViewCell cell.textLabel?.text = title cell.selectionStyle = .None @@ -148,9 +146,13 @@ struct SwitchRow: CustomCellImmuTableRow { } } -struct MediaSizeRow: CustomNibImmuTableRow { +struct MediaSizeRow: ImmuTableRow { typealias CellType = MediaSizeSliderCell - static let nib = UINib(nibName: "MediaSizeSliderCell", bundle: NSBundle(forClass: CellType.self)) + + static let cell: ImmuTableCell = { + let nib = UINib(nibName: "MediaSizeSliderCell", bundle: NSBundle(forClass: CellType.self)) + return ImmuTableCell.Nib(nib, CellType.self) + }() static let customHeight: Float? = CellType.height let title: String diff --git a/WordPress/WordPressTest/ImmuTableTest.swift b/WordPress/WordPressTest/ImmuTableTest.swift index 2a8c6485752b..97d4953695ab 100644 --- a/WordPress/WordPressTest/ImmuTableTest.swift +++ b/WordPress/WordPressTest/ImmuTableTest.swift @@ -31,16 +31,16 @@ class ImmuTableTest: XCTestCase { class TestTableViewCell: UITableViewCell {} class ImmuTableTestViewCellWithNib: UITableViewCell {} -struct BasicImmuTableRow: CustomCellImmuTableRow { - typealias CellType = UITableViewCell +struct BasicImmuTableRow: ImmuTableRow { + static let cell = ImmuTableCell.Class(UITableViewCell) let title: String var action: ImmuTableActionType? = nil func configureCell(cell: UITableViewCell) { } } -struct ImageImmuTableRow: CustomCellImmuTableRow { - typealias CellType = UITableViewCell +struct ImageImmuTableRow: ImmuTableRow { + static let cell = ImmuTableCell.Class(UITableViewCell) let title: String let image: UIImage var action: ImmuTableActionType? = nil @@ -48,18 +48,20 @@ struct ImageImmuTableRow: CustomCellImmuTableRow { } } -struct TestImmuTableRow: CustomCellImmuTableRow { - typealias CellType = TestTableViewCell +struct TestImmuTableRow: ImmuTableRow { + static let cell = ImmuTableCell.Class(TestTableViewCell) let title: String var action: ImmuTableActionType? = nil func configureCell(cell: UITableViewCell) { } } -struct TestWithNibImmuTableRow: CustomNibImmuTableRow { +struct TestWithNibImmuTableRow: ImmuTableRow { typealias CellType = ImmuTableTestViewCellWithNib - static let nib = UINib(nibName: "ImmuTableTestViewCellWithNib", bundle: NSBundle(forClass: ImmuTableTestViewCellWithNib.self)) - static let customHeight: Float? = nil + static let cell: ImmuTableCell = { + let nib = UINib(nibName: "ImmuTableTestViewCellWithNib", bundle: NSBundle(forClass: ImmuTableTestViewCellWithNib.self)) + return ImmuTableCell.Nib(nib, CellType.self) + }() var action: ImmuTableActionType? = nil func configureCell(cell: UITableViewCell) { } @@ -68,11 +70,11 @@ struct TestWithNibImmuTableRow: CustomNibImmuTableRow { class MockTableView: CellRegistrator { var registeredClasses = [(String, AnyClass)]() var registeredNibs = [(String, UINib)]() - func register(registrable: CellRegistrable, cellReuseIdentifier identifier: String) { - switch registrable { + func register(cell: ImmuTableCell, cellReuseIdentifier identifier: String) { + switch cell { case .Class(let cellClass): registeredClasses.append((identifier, cellClass)) - case .Nib(let nib): + case .Nib(let nib, _): registeredNibs.append((identifier, nib)) } } From ed0b1f9cce6ab6a202f742685f7f034492fe6dfd Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Tue, 8 Dec 2015 19:46:34 +0100 Subject: [PATCH 10/17] Added more documentation for ImmuTable --- WordPress/Classes/Utility/ImmuTable.swift | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/WordPress/Classes/Utility/ImmuTable.swift b/WordPress/Classes/Utility/ImmuTable.swift index 1cd3c201bbb5..7c98fbc6d3c9 100644 --- a/WordPress/Classes/Utility/ImmuTable.swift +++ b/WordPress/Classes/Utility/ImmuTable.swift @@ -89,13 +89,35 @@ extension UITableView: CellRegistrator { Each row contains the model necessary to configure a specific type of UITableViewCell. + To use ImmuTable, first you need to create some custom rows. An example row for a cell + that acts as a button which performs a destructive action could look like this: + + struct DestructiveButtonRow: ImmuTableRow { + static let cell = ImmuTableCell.Class(UITableViewCell.self) + let title: String + let action: ImmuTableActionType? + + func configureCell(cell: UITableViewCell) { + cell.textLabel?.text = title + cell.textLabel?.textAlignment = .Center + cell.textLabel?.textColor = UIColor.redColor() + } + } + + The easiest way to use ImmuTable is through ImmuTableViewHandler, which takes a + UITableViewController as an argument, and acts as the table view delegate and data + source. You would then assign an `ImmuTable` object to the handler's `viewModel` + property. + - attention: before using any ImmuTableRow type, you need to call `registerRows(_:tableView:)` passing the row type. This is needed so ImmuTable can register the class or nib with the table view. If you fail to do this, UIKit will raise an exception when it tries to load the row. */ public struct ImmuTable { + /// An array of the sections to be represented in the table view public let sections: [ImmuTableSection] + /// Initializes an ImmuTable object with the given sections public init(sections: [ImmuTableSection]) { self.sections = sections } From 474e2424721e75050db82276d10c21a7a43248e8 Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Tue, 8 Dec 2015 22:28:50 +0100 Subject: [PATCH 11/17] Keep CellRegistrator internal --- WordPress/Classes/Utility/ImmuTable.swift | 10 +++++++--- WordPress/WordPressTest/ImmuTableTest.swift | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/WordPress/Classes/Utility/ImmuTable.swift b/WordPress/Classes/Utility/ImmuTable.swift index 7c98fbc6d3c9..1fb9c39e89d4 100644 --- a/WordPress/Classes/Utility/ImmuTable.swift +++ b/WordPress/Classes/Utility/ImmuTable.swift @@ -65,7 +65,7 @@ public enum ImmuTableCell { } } -public protocol CellRegistrator { +protocol CellRegistrator { func register(cell: ImmuTableCell, cellReuseIdentifier: String) } @@ -126,7 +126,11 @@ public struct ImmuTable { return sections[indexPath.section].rows[indexPath.row] } - public static func registerRows(rows: [ImmuTableRow.Type], tableView: CellRegistrator) { + public static func registerRows(rows: [ImmuTableRow.Type], tableView: UITableView) { + registerRows(rows, registrator: tableView) + } + + internal static func registerRows(rows: [ImmuTableRow.Type], registrator: CellRegistrator) { let registrables = rows.reduce([:]) { (var classes, row) -> [String: ImmuTableCell] in @@ -134,7 +138,7 @@ public struct ImmuTable { return classes } for (identifier, registrable) in registrables { - tableView.register(registrable, cellReuseIdentifier: identifier) + registrator.register(registrable, cellReuseIdentifier: identifier) } } } diff --git a/WordPress/WordPressTest/ImmuTableTest.swift b/WordPress/WordPressTest/ImmuTableTest.swift index 97d4953695ab..083842213508 100644 --- a/WordPress/WordPressTest/ImmuTableTest.swift +++ b/WordPress/WordPressTest/ImmuTableTest.swift @@ -9,7 +9,7 @@ class ImmuTableTest: XCTestCase { TestWithNibImmuTableRow.self ] - ImmuTable.registerRows(rowsToRegister, tableView: mockTable) + ImmuTable.registerRows(rowsToRegister, registrator: mockTable) XCTAssertEqual(mockTable.registeredNibs.count, 1, "The table should have registered a nib for TestWithNibImmuTableRow") XCTAssertEqual(mockTable.registeredClasses.count, 0, "The table shouldn't have registered any classes for TestWithNibImmuTableRow") } @@ -22,7 +22,7 @@ class ImmuTableTest: XCTestCase { TestImmuTableRow.self ] - ImmuTable.registerRows(rowsToRegister, tableView: mockTable) + ImmuTable.registerRows(rowsToRegister, registrator: mockTable) XCTAssertEqual(2, mockTable.registeredClasses.count, "Each cell class shouldn't be registered more than once") } From ac29752fd46bf1826ea3f46a95aa5fc8b9e1d712 Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Tue, 8 Dec 2015 22:29:07 +0100 Subject: [PATCH 12/17] Added more documentation for ImmuTable methods --- WordPress/Classes/Utility/ImmuTable.swift | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/Utility/ImmuTable.swift b/WordPress/Classes/Utility/ImmuTable.swift index 1fb9c39e89d4..8a783e81f367 100644 --- a/WordPress/Classes/Utility/ImmuTable.swift +++ b/WordPress/Classes/Utility/ImmuTable.swift @@ -106,7 +106,7 @@ extension UITableView: CellRegistrator { The easiest way to use ImmuTable is through ImmuTableViewHandler, which takes a UITableViewController as an argument, and acts as the table view delegate and data - source. You would then assign an `ImmuTable` object to the handler's `viewModel` + source. You would then assign an ImmuTable object to the handler's `viewModel` property. - attention: before using any ImmuTableRow type, you need to call `registerRows(_:tableView:)` @@ -122,14 +122,26 @@ public struct ImmuTable { self.sections = sections } + /** + Returns the row model for a specific index path. + + - precondition: `indexPath` should represent a valid section and row, + otherwise this method will raise an exception. + */ public func rowAtIndexPath(indexPath: NSIndexPath) -> ImmuTableRow { return sections[indexPath.section].rows[indexPath.row] } + /** + Registers the row custom class or nib with the table view so it can later be + dequeued with `dequeueReusableCellWithIdentifier(_:forIndexPath:)` + */ public static func registerRows(rows: [ImmuTableRow.Type], tableView: UITableView) { registerRows(rows, registrator: tableView) } + /// This function exists for testing purposes + /// - seealso: registerRows(_:tableView:) internal static func registerRows(rows: [ImmuTableRow.Type], registrator: CellRegistrator) { let registrables = rows.reduce([:]) { (var classes, row) -> [String: ImmuTableCell] in From 2aa1f47265b54be895104d31c8c1db3f9bfedacc Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Tue, 8 Dec 2015 22:34:50 +0100 Subject: [PATCH 13/17] Reorganized ImmuTable more hierarchically --- WordPress/Classes/Utility/ImmuTable.swift | 184 ++++++++++++---------- 1 file changed, 104 insertions(+), 80 deletions(-) diff --git a/WordPress/Classes/Utility/ImmuTable.swift b/WordPress/Classes/Utility/ImmuTable.swift index 8a783e81f367..27d206f7c6d8 100644 --- a/WordPress/Classes/Utility/ImmuTable.swift +++ b/WordPress/Classes/Utility/ImmuTable.swift @@ -1,86 +1,6 @@ import Foundation import UIKit -public typealias ImmuTableActionType = (ImmuTableRow) -> Void - -public protocol ImmuTableRow { - var action: ImmuTableActionType? { get } - func configureCell(cell: UITableViewCell) - static var cell: ImmuTableCell { get } - static var customHeight: Float? { get } -} - -extension ImmuTableRow { - public var reusableIdentifier: String { - return self.dynamicType.cell.reusableIdentifier - } - - public var cellClass: UITableViewCell.Type { - return self.dynamicType.cell.cellClass - } - - public static var customHeight: Float? { - return nil; - } -} - -public struct ImmuTableSection { - let headerText: String? - let rows: [ImmuTableRow] - let footerText: String? - - public init(rows: [ImmuTableRow]) { - self.headerText = nil - self.rows = rows - self.footerText = nil - } - - public init(headerText: String?, rows: [ImmuTableRow], footerText: String?) { - self.headerText = headerText - self.rows = rows - self.footerText = footerText - } -} - -public enum ImmuTableCell { - case Nib(UINib, UITableViewCell.Type) - case Class(UITableViewCell.Type) - - public var reusableIdentifier: String { - switch self { - case .Class(let cellClass): - return NSStringFromClass(cellClass) - case .Nib(_, let cellClass): - return NSStringFromClass(cellClass) - } - } - - public var cellClass: UITableViewCell.Type { - switch self { - case .Class(let cellClass): - return cellClass - case .Nib(_, let cellClass): - return cellClass - } - } -} - -protocol CellRegistrator { - func register(cell: ImmuTableCell, cellReuseIdentifier: String) -} - - -extension UITableView: CellRegistrator { - public func register(cell: ImmuTableCell, cellReuseIdentifier: String) { - switch cell { - case .Nib(let nib, _): - registerNib(nib, forCellReuseIdentifier: cell.reusableIdentifier) - case .Class(let cellClass): - registerClass(cellClass, forCellReuseIdentifier: cell.reusableIdentifier) - } - } -} - /** ImmuTable represents the view model for a static UITableView. @@ -155,6 +75,84 @@ public struct ImmuTable { } } + +// MARK: - + + +public struct ImmuTableSection { + let headerText: String? + let rows: [ImmuTableRow] + let footerText: String? + + public init(rows: [ImmuTableRow]) { + self.headerText = nil + self.rows = rows + self.footerText = nil + } + + public init(headerText: String?, rows: [ImmuTableRow], footerText: String?) { + self.headerText = headerText + self.rows = rows + self.footerText = footerText + } +} + + +// MARK: - ImmuTableRow + + +public protocol ImmuTableRow { + var action: ImmuTableActionType? { get } + func configureCell(cell: UITableViewCell) + static var cell: ImmuTableCell { get } + static var customHeight: Float? { get } +} + +extension ImmuTableRow { + public var reusableIdentifier: String { + return self.dynamicType.cell.reusableIdentifier + } + + public var cellClass: UITableViewCell.Type { + return self.dynamicType.cell.cellClass + } + + public static var customHeight: Float? { + return nil; + } +} + + +// MARK: - ImmuTableCell + + +public enum ImmuTableCell { + case Nib(UINib, UITableViewCell.Type) + case Class(UITableViewCell.Type) + + public var reusableIdentifier: String { + switch self { + case .Class(let cellClass): + return NSStringFromClass(cellClass) + case .Nib(_, let cellClass): + return NSStringFromClass(cellClass) + } + } + + public var cellClass: UITableViewCell.Type { + switch self { + case .Class(let cellClass): + return cellClass + case .Nib(_, let cellClass): + return cellClass + } + } +} + + +// MARK: - + + public class ImmuTableViewHandler: NSObject, UITableViewDataSource, UITableViewDelegate { unowned let target: UITableViewController @@ -205,3 +203,29 @@ public class ImmuTableViewHandler: NSObject, UITableViewDataSource, UITableViewD } } + +// MARK: - Type aliases + + +public typealias ImmuTableActionType = (ImmuTableRow) -> Void + + +// MARK: - Internal testing helpers + + +protocol CellRegistrator { + func register(cell: ImmuTableCell, cellReuseIdentifier: String) +} + + +extension UITableView: CellRegistrator { + public func register(cell: ImmuTableCell, cellReuseIdentifier: String) { + switch cell { + case .Nib(let nib, _): + registerNib(nib, forCellReuseIdentifier: cell.reusableIdentifier) + case .Class(let cellClass): + registerClass(cellClass, forCellReuseIdentifier: cell.reusableIdentifier) + } + } +} + From 6455aeca980b2e81453ea9bd2e1b92b41a6ca0cc Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Tue, 8 Dec 2015 22:51:02 +0100 Subject: [PATCH 14/17] Added docs for ImmuTableRow --- WordPress/Classes/Utility/ImmuTable.swift | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/WordPress/Classes/Utility/ImmuTable.swift b/WordPress/Classes/Utility/ImmuTable.swift index 27d206f7c6d8..a4f2a10ab891 100644 --- a/WordPress/Classes/Utility/ImmuTable.swift +++ b/WordPress/Classes/Utility/ImmuTable.swift @@ -79,17 +79,26 @@ public struct ImmuTable { // MARK: - +/** +ImmuTableSection represents the view model for a table view section. + +A section has an optional header and footer text, and zero or more rows. + +- seealso: ImmuTableRow +*/ public struct ImmuTableSection { let headerText: String? let rows: [ImmuTableRow] let footerText: String? + /// Initializes a ImmuTableSection with the given rows and no header or footer text public init(rows: [ImmuTableRow]) { self.headerText = nil self.rows = rows self.footerText = nil } + /// Initializes a ImmuTableSection with the given rows and optionally header and footer text public init(headerText: String?, rows: [ImmuTableRow], footerText: String?) { self.headerText = headerText self.rows = rows @@ -101,10 +110,28 @@ public struct ImmuTableSection { // MARK: - ImmuTableRow +/** +ImmuTableRow represents the minimum common elements of a row model. + +You should implement your own types that conform to ImmuTableRow to define your custom rows. +*/ public protocol ImmuTableRow { + + /// The closure to call when the row is tapped. The row is passed as an argument to the closure. var action: ImmuTableActionType? { get } + + /// This method is called when an associated cell needs to be configured. + /// - precondition: You can assume that the passed cell is of the type defined + /// by cell.cellClass and force downcast accordingly. func configureCell(cell: UITableViewCell) + + /// An ImmuTableCell value defining the associated cell type. + /// - seealso: See ImmuTableCell for possible options. static var cell: ImmuTableCell { get } + + /// The desired row height (Optional) + /// + /// If not defined or nil, the default height will be used. static var customHeight: Float? { get } } From 95efcd3c1e122721662416bdbc84c6779285dbd0 Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Tue, 8 Dec 2015 22:58:55 +0100 Subject: [PATCH 15/17] Added docs for ImmuTableCell --- WordPress/Classes/Utility/ImmuTable.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/WordPress/Classes/Utility/ImmuTable.swift b/WordPress/Classes/Utility/ImmuTable.swift index a4f2a10ab891..1ec1ed49ecc4 100644 --- a/WordPress/Classes/Utility/ImmuTable.swift +++ b/WordPress/Classes/Utility/ImmuTable.swift @@ -153,10 +153,27 @@ extension ImmuTableRow { // MARK: - ImmuTableCell +/** +ImmuTableCell describes cell types so they can be registered with a table view. + +It supports two options: + - Nib for Interface Builder defined cells. + - Class for cells defined in code. +Both cases presume a custom UITableViewCell subclass. If you aren't subclassing, +you can also use UITableViewCell as the type. + +- note: If you need to use any cell style other than .Default we recommend you + subclass UITableViewCell and override init(style:reuseIdentifier:). +*/ public enum ImmuTableCell { + + /// A cell using a UINib. Values are the UINib object and the custom cell class. case Nib(UINib, UITableViewCell.Type) + + /// A cell using a custom class. The associated value is the custom cell class. case Class(UITableViewCell.Type) + /// A String that uniquely identifies the cell type public var reusableIdentifier: String { switch self { case .Class(let cellClass): @@ -166,6 +183,7 @@ public enum ImmuTableCell { } } + /// The class of the custom cell public var cellClass: UITableViewCell.Type { switch self { case .Class(let cellClass): From d3b880465fda2041398ec3b7b4432564b2c144b9 Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Tue, 8 Dec 2015 23:04:16 +0100 Subject: [PATCH 16/17] Added docs for ImmuTableViewHandler --- WordPress/Classes/Utility/ImmuTable.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/WordPress/Classes/Utility/ImmuTable.swift b/WordPress/Classes/Utility/ImmuTable.swift index 1ec1ed49ecc4..9e4db8a9c53a 100644 --- a/WordPress/Classes/Utility/ImmuTable.swift +++ b/WordPress/Classes/Utility/ImmuTable.swift @@ -198,9 +198,22 @@ public enum ImmuTableCell { // MARK: - +/** +ImmuTableViewHandler is a helper to facilitate integration of ImmuTable in your +table view controllers. + +It acts as the table view data source and delegate, and signals the table view to +reload its data when the underlying model changes. + +- note: as it keeps a weak reference to its target, you should keep a strong + reference to the handler from your view controller. +*/ public class ImmuTableViewHandler: NSObject, UITableViewDataSource, UITableViewDelegate { unowned let target: UITableViewController + /// Initializes the handler with a target table view controller. + /// - postcondition: After initialization, it becomse the data source and + /// delegate for the the target's table view. public init(takeOver target: UITableViewController) { self.target = target super.init() @@ -209,6 +222,7 @@ public class ImmuTableViewHandler: NSObject, UITableViewDataSource, UITableViewD self.target.tableView.delegate = self } + /// An ImmuTable object representing the table structure. public var viewModel = ImmuTable(sections: []) { didSet { if target.isViewLoaded() { @@ -217,6 +231,8 @@ public class ImmuTableViewHandler: NSObject, UITableViewDataSource, UITableViewD } } + // MARK: Table View Data Source + public func numberOfSectionsInTableView(tableView: UITableView) -> Int { return viewModel.sections.count } @@ -234,6 +250,8 @@ public class ImmuTableViewHandler: NSObject, UITableViewDataSource, UITableViewD return cell } + // MARK: Table View Delegate + public func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { let row = viewModel.rowAtIndexPath(indexPath) row.action?(row) From e175ab4d914a1f4aa91c92bc1595ad8137c9a86a Mon Sep 17 00:00:00 2001 From: Jorge Bernal Date: Tue, 8 Dec 2015 23:29:30 +0100 Subject: [PATCH 17/17] Added implementation suggestions for row actions --- WordPress/Classes/Utility/ImmuTable.swift | 31 ++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/WordPress/Classes/Utility/ImmuTable.swift b/WordPress/Classes/Utility/ImmuTable.swift index 9e4db8a9c53a..a685d77d194d 100644 --- a/WordPress/Classes/Utility/ImmuTable.swift +++ b/WordPress/Classes/Utility/ImmuTable.swift @@ -117,7 +117,36 @@ You should implement your own types that conform to ImmuTableRow to define your */ public protocol ImmuTableRow { - /// The closure to call when the row is tapped. The row is passed as an argument to the closure. + /** + The closure to call when the row is tapped. The row is passed as an argument to the closure. + + To improve readability, we recommend that you implement the action logic in one of + your view controller methods, instead of including the closure inline. + + Also, be mindful of retain cycles. If your closure needs to reference `self` in + any way, make sure to use `[unowned self]` in the parameter list. + + An example row with its action could look like this: + + class ViewController: UITableViewController { + + func buildViewModel() { + let item1Row = NavigationItemRow(title: "Item 1", action: navigationAction()) + ... + } + + func navigationAction() -> ImmuTableRow -> Void { + return { [unowned self] row in + let controller = self.controllerForRow(row) + self.navigationController?.pushViewController(controller, animated: true) + } + } + + ... + + } + + */ var action: ImmuTableActionType? { get } /// This method is called when an associated cell needs to be configured.