diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..085a258 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,25 @@ +excluded: # paths to ignore during linting. Takes precedence over `included`. + - External + - Examples +opt_in_rules: + - empty_count + - explicit_init + - closure_spacing + - overridden_super_call + - redundant_nil_coalescing + - private_outlet + - nimble_operator + - attributes + - operator_usage_whitespace + - closure_end_indentation + - first_where + - number_separator + - prohibited_super_call + - fatal_error_message +identifier_name: + excluded: + - x + - y + - id + - from + - to diff --git a/.travis.yml b/.travis.yml index 4ee27f1..4a693b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ language: objective-c -osx_image: xcode8.1 -script: travis_retry sh build.sh +osx_image: xcode8.3 +script: + - swiftlint + - travis_retry sh build.sh after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7229ec3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,20 @@ +# Change Log +All notable changes to this project will be documented in this file. + +--- + +## [Unreleased] +### Added +- Support `TableViewManager` as `manager` property of `Item` and `Section` +- Support `UIScrollViewDelegate` from `TableViewManager` via `scrollDelegate` + +### Changed +- Move `UITableViewDataSource` and `UITableViewDelegate` implementations from `TableViewManager` to `TableViewKitDataSource` and `TableViewKitDelegate` which are now the properties: `dataSource` and `delegate`, respectively. +- Method `Item.section(in:)` has been renamed to `Item.section` +- Method `Item.indexPath(in:)` has been renamed to `Item.indexPath` +- Method `Item.reload(in:with:)` has been renamed to `Item.reload(with:)` +- Method `Item.select(in:animated:scrollPosition:)` has been renamed to `Item.select(animated:scrollPosition:)` +- Method `Item.select(in:animated:scrollPosition:)` has been renamed to `Item.select(animated:scrollPosition:)` +- Method `Item.deselect(in:animated:)` has been renamed to `Item.deselect(animated:)` +- Method `Section.index(in:)` has been renamed to `Section.index` + diff --git a/Examples/SampleApp/SampleApp/ActionBar/ActionBar.swift b/Examples/SampleApp/SampleApp/ActionBar/ActionBar.swift index c8aa814..2a8c505 100644 --- a/Examples/SampleApp/SampleApp/ActionBar/ActionBar.swift +++ b/Examples/SampleApp/SampleApp/ActionBar/ActionBar.swift @@ -34,8 +34,8 @@ open class ActionBar: UIToolbar { fileprivate func setup() { - let previousButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.init(rawValue: 105)!, target: self, action: #selector(previousHandler)) - let nextButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.init(rawValue: 106)!, target: self, action: #selector(nextHandler)) + let previousButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem(rawValue: 105)!, target: self, action: #selector(previousHandler)) + let nextButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem(rawValue: 106)!, target: self, action: #selector(nextHandler)) let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(handleActionBarDone)) let spacer = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil) diff --git a/Examples/SampleApp/SampleApp/ActionBar/ActionBarManager.swift b/Examples/SampleApp/SampleApp/ActionBar/ActionBarManager.swift index c68f73d..ab633bd 100644 --- a/Examples/SampleApp/SampleApp/ActionBar/ActionBarManager.swift +++ b/Examples/SampleApp/SampleApp/ActionBar/ActionBarManager.swift @@ -22,7 +22,7 @@ class ActionBarManager: ActionBarDelegate { func isFirstResponder(item: Item) -> Bool { if isResponder(item: item), - let indexPath = item.indexPath(in: manager), + let indexPath = item.indexPath, manager.tableView.cellForRow(at: indexPath)?.isFirstResponder == true { return true } @@ -47,7 +47,7 @@ class ActionBarManager: ActionBarDelegate { item = array.prefix(upTo: index).reversed().first(where: isResponder) } - return item?.indexPath(in: manager) + return item?.indexPath } } diff --git a/Examples/SampleApp/SampleApp/Example1.swift b/Examples/SampleApp/SampleApp/Example1.swift index a16559e..9b0b69a 100644 --- a/Examples/SampleApp/SampleApp/Example1.swift +++ b/Examples/SampleApp/SampleApp/Example1.swift @@ -42,17 +42,17 @@ class Example1: UIViewController, TableViewManagerCompatible { textFieldItem2.validation.add(rule: ExistRule()) item.onSelection = { item in - item.deselect(in: self.vc.tableViewManager, animated: true) + item.deselect(animated: true) self.vc.showPickerControl() } dateItem.accessoryType = .disclosureIndicator dateItem.onSelection = { item in - item.deselect(in: self.vc.tableViewManager, animated: true) + item.deselect(animated: true) self.vc.showDatePickerControl() } selectionItem.accessoryType = .disclosureIndicator selectionItem.onSelection = { item in - item.deselect(in: self.vc.tableViewManager, animated: true) + item.deselect(animated: true) self.vc.showPickerControl() } states[State.preParty] = [item, dateItem, selectionItem, textFieldItem, textFieldItem2] @@ -82,7 +82,7 @@ class Example1: UIViewController, TableViewManagerCompatible { } else { let item = CustomItem(title: "Label \(index)") item.onSelection = { item in - item.deselect(in: self.vc.tableViewManager, animated: true) + item.deselect(animated: true) } return item } @@ -117,7 +117,7 @@ class Example1: UIViewController, TableViewManagerCompatible { default: self.transition(to: .all) } - item.deselect(in: self.vc.tableViewManager, animated: true) + item.deselect(animated: true) } return item } diff --git a/Examples/SampleApp/SampleApp/PickerControl.swift b/Examples/SampleApp/SampleApp/PickerControl.swift index 85fa1a2..8c7f793 100644 --- a/Examples/SampleApp/SampleApp/PickerControl.swift +++ b/Examples/SampleApp/SampleApp/PickerControl.swift @@ -145,9 +145,9 @@ open class PickerControl: NSObject { } } - if components.count != 0 { + if components.isEmpty { self.components.append(components) - self.selections = Array.init(repeating: nil, count: self.components.count) + self.selections = Array(repeating: nil, count: self.components.count) } } else { let item = PickerItem(title: String(describing: value), value: value) @@ -323,7 +323,7 @@ open class PickerControl: NSObject { if type == .single { - if items.count == 0 { + if items.isEmpty { return } @@ -332,7 +332,7 @@ open class PickerControl: NSObject { selection = item } else if type == .multiColumn { - if components.count == 0 { + if components.isEmpty { return } diff --git a/Examples/SampleApp/SampleApp/SelectionViewController.swift b/Examples/SampleApp/SampleApp/SelectionViewController.swift index d7302b8..e6b21f0 100644 --- a/Examples/SampleApp/SampleApp/SelectionViewController.swift +++ b/Examples/SampleApp/SampleApp/SelectionViewController.swift @@ -125,13 +125,13 @@ public class SelectionViewController: UITableViewController { if let checkedItem = itemSelected() { checkedItem.selected = false checkedItem.accessoryType = .none - checkedItem.reload(in: tableViewManager, with: .fade) + checkedItem.reload(with: .fade) } } item.selected = !item.selected item.accessoryType = item.accessoryType == .checkmark ? .none : .checkmark - item.reload(in: tableViewManager, with: .fade) + item.reload(with: .fade) fillSelected() } diff --git a/Examples/SampleApp/SampleApp/Validator/Validation.swift b/Examples/SampleApp/SampleApp/Validator/Validation.swift index a861f4c..b860af8 100644 --- a/Examples/SampleApp/SampleApp/Validator/Validation.swift +++ b/Examples/SampleApp/SampleApp/Validator/Validation.swift @@ -44,10 +44,10 @@ open class Validation { public init(forInput: @escaping () -> Input, withIdentifier identifier: Any? = nil, rule: R) where R.Input == Input { self.forInput = forInput self.identifier = identifier - self.rules.append(AnyValidatable.init(base: rule)) + self.rules.append(AnyValidatable(base: rule)) } open func add(rule: R) where R.Input == Input { - self.rules.append(AnyValidatable.init(base: rule)) + self.rules.append(AnyValidatable(base: rule)) } } diff --git a/Examples/SampleApp/SampleApp/Validator/Validator.swift b/Examples/SampleApp/SampleApp/Validator/Validator.swift index 34a592e..31cc14c 100644 --- a/Examples/SampleApp/SampleApp/Validator/Validator.swift +++ b/Examples/SampleApp/SampleApp/Validator/Validator.swift @@ -36,7 +36,7 @@ public struct ValidatorManager { internal var validations: [Validation] = [] public mutating func add(_ getInput: @escaping () -> Input, withRule rule: R) where R.Input == Input { - validations.append(Validation.init(forInput: getInput, rule: rule)) + validations.append(Validation(forInput: getInput, rule: rule)) } public mutating func add(validation: Validation) { diff --git a/Examples/SampleApp/SampleApp/ValidatorTests.swift b/Examples/SampleApp/SampleApp/ValidatorTests.swift index 3a0f436..3887465 100644 --- a/Examples/SampleApp/SampleApp/ValidatorTests.swift +++ b/Examples/SampleApp/SampleApp/ValidatorTests.swift @@ -40,7 +40,7 @@ class ValidatorTests: XCTestCase { var validator = ValidatorManager() validator.add(validation: validation) - expect(validator.errors.count).to(equal(0)) + expect(validator.errors.count) == 0 } func testCharactersLengthRule() { @@ -53,7 +53,7 @@ class ValidatorTests: XCTestCase { var validator = ValidatorManager() validator.add(validation: validation) - expect(validator.errors.count).to(equal(0)) + expect(validator.errors.count) == 0 } func testNumberBetweenRule() { @@ -66,6 +66,6 @@ class ValidatorTests: XCTestCase { var validator = ValidatorManager() validator.add(validation: validation) - expect(validator.errors.count).to(equal(0)) + expect(validator.errors.count) == 0 } } diff --git a/Examples/SampleApp/SampleApp/ViewController.swift b/Examples/SampleApp/SampleApp/ViewController.swift index 49dba19..e881727 100644 --- a/Examples/SampleApp/SampleApp/ViewController.swift +++ b/Examples/SampleApp/SampleApp/ViewController.swift @@ -16,12 +16,12 @@ class ViewController: UIViewController, TableViewManagerCompatible { let mappedItems = array.map({ (className) -> Item in let viewController = className.init(nibName: String(describing: className), bundle: nil) - let navigationController = UINavigationController.init(rootViewController: viewController) + let navigationController = UINavigationController(rootViewController: viewController) let item = CustomItem(title: "Example 1") item.onSelection = { _ in self.vc.present(navigationController, animated: true, completion: { - item.deselect(in: self.vc.tableViewManager, animated: false) + item.deselect(animated: false) }) } return item diff --git a/Examples/Viper/TableViewKit+VIPER/AboutModule/MoreAboutItem.swift b/Examples/Viper/TableViewKit+VIPER/AboutModule/MoreAboutItem.swift index c6e6e8f..59683dd 100644 --- a/Examples/Viper/TableViewKit+VIPER/AboutModule/MoreAboutItem.swift +++ b/Examples/Viper/TableViewKit+VIPER/AboutModule/MoreAboutItem.swift @@ -58,8 +58,6 @@ class MoreAboutItem: Item, Selectable, Editable { presenter?.showRateApp() } - if let manager = manager { - deselect(in: manager, animated: true) - } + deselect(animated: true) } } diff --git a/External/Nimble b/External/Nimble index 8e2703f..39b6700 160000 --- a/External/Nimble +++ b/External/Nimble @@ -1 +1 @@ -Subproject commit 8e2703fabde578a6d28537c73844f84d48e71200 +Subproject commit 39b67002306fda9de4c9fd1290a6295f97edd09e diff --git a/README.md b/README.md index 2417e8b..78530f3 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@

-Empowering `UITableView` with painless multi-type cell support and build-in automatic state transition animations +Empowering `UITableView` with painless multi-type cell support and built-in automatic state transition animations ## Getting Started - [Download TableViewKit](https://github.com/odigeoteam/TableViewKit/releases) and play with our [examples](https://github.com/odigeoteam/TableViewKit/tree/develop/Examples) diff --git a/TableViewKit.podspec b/TableViewKit.podspec index 9fa19de..e5e8849 100644 --- a/TableViewKit.podspec +++ b/TableViewKit.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "TableViewKit" - s.version = "1.0.0" - s.summary = "Empowering UITableView with painless multi-type cell support and build-in automatic state transition animations" + s.version = "1.1.0" + s.summary = "Empowering UITableView with painless multi-type cell support and built-in automatic state transition animations" s.homepage = "http://github.com/odigeoteam/TableViewKit/" s.license = "MIT" s.author = "TableViewKit Contributors" diff --git a/TableViewKit.xcodeproj/project.pbxproj b/TableViewKit.xcodeproj/project.pbxproj index 1750484..a9afc97 100644 --- a/TableViewKit.xcodeproj/project.pbxproj +++ b/TableViewKit.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ 2564E61C1D88450F00A9DC3E /* TestRegisterHeaderFooterView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2564E61B1D88450F00A9DC3E /* TestRegisterHeaderFooterView.xib */; }; 25837C781D87F819001EF4B8 /* ItemCompatible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25837C771D87F819001EF4B8 /* ItemCompatible.swift */; }; 25B0B7541DC74F6C00591467 /* Editable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25B0B7531DC74F6C00591467 /* Editable.swift */; }; + CC09F8891EA79178006FF4EA /* TableViewKitDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC09F8881EA79178006FF4EA /* TableViewKitDataSource.swift */; }; + CC09F88B1EA791B7006FF4EA /* TableViewKitDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC09F88A1EA791B7006FF4EA /* TableViewKitDelegate.swift */; }; CC0DE2C81DE33B9C00E2EDE5 /* AnyEquatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC0DE2C71DE33B9C00E2EDE5 /* AnyEquatable.swift */; }; CC5CF11C1D839E71004DECB3 /* ArrayDiff.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC5CF11B1D839E71004DECB3 /* ArrayDiff.swift */; }; CC97D76D1D741DC4009CDF9D /* Selectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC97D76C1D741DC4009CDF9D /* Selectable.swift */; }; @@ -55,6 +57,8 @@ 25837C771D87F819001EF4B8 /* ItemCompatible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ItemCompatible.swift; path = Protocols/ItemCompatible.swift; sourceTree = ""; }; 25B0B7531DC74F6C00591467 /* Editable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Editable.swift; path = Protocols/Editable.swift; sourceTree = ""; }; 4BCAD51C1D89A704002F3420 /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = "External/Nimble/build/Debug-iphoneos/Nimble.framework"; sourceTree = ""; }; + CC09F8881EA79178006FF4EA /* TableViewKitDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewKitDataSource.swift; sourceTree = ""; }; + CC09F88A1EA791B7006FF4EA /* TableViewKitDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewKitDelegate.swift; sourceTree = ""; }; CC0DE2C71DE33B9C00E2EDE5 /* AnyEquatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AnyEquatable.swift; path = Protocols/AnyEquatable.swift; sourceTree = ""; }; CC5CF11B1D839E71004DECB3 /* ArrayDiff.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayDiff.swift; sourceTree = ""; }; CC97D76C1D741DC4009CDF9D /* Selectable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Selectable.swift; path = Protocols/Selectable.swift; sourceTree = ""; }; @@ -155,6 +159,8 @@ CCCAC1271D7DA2CF0001FC1D /* Height.swift */, CCEF387D1D8D59C000F5893F /* NibClassType.swift */, CCBE458E1D72F79C00414A64 /* TableViewManager.swift */, + CC09F88A1EA791B7006FF4EA /* TableViewKitDelegate.swift */, + CC09F8881EA79178006FF4EA /* TableViewKitDataSource.swift */, CCBE455F1D72F69500414A64 /* TableViewKit.h */, CCBE45611D72F69500414A64 /* Info.plist */, CC5CF11B1D839E71004DECB3 /* ArrayDiff.swift */, @@ -205,6 +211,7 @@ buildConfigurationList = CCBE45701D72F69500414A64 /* Build configuration list for PBXNativeTarget "TableViewKit" */; buildPhases = ( CCBE45571D72F69400414A64 /* Sources */, + CCC87B4A1ED9E7160008968D /* SwiftLint */, CCBE45581D72F69400414A64 /* Frameworks */, CCBE45591D72F69400414A64 /* Headers */, CCBE455A1D72F69400414A64 /* Resources */, @@ -293,12 +300,30 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + CCC87B4A1ED9E7160008968D /* SwiftLint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = SwiftLint; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ CCBE45571D72F69400414A64 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( CCBE45A01D72F79C00414A64 /* Section.swift in Sources */, + CC09F88B1EA791B7006FF4EA /* TableViewKitDelegate.swift in Sources */, CC0DE2C81DE33B9C00E2EDE5 /* AnyEquatable.swift in Sources */, CCFD05CC1D95BD3C0063A002 /* Stateful.swift in Sources */, 2519E1C91E013DF00021E06E /* UITableView+Moves.swift in Sources */, @@ -313,6 +338,7 @@ CCDB8FAB1D83649B00E44A99 /* ObservableArray.swift in Sources */, CCCAC1261D7D9EE00001FC1D /* HeaderFooter.swift in Sources */, CCBE45A11D72F79C00414A64 /* TableViewManager.swift in Sources */, + CC09F8891EA79178006FF4EA /* TableViewKitDataSource.swift in Sources */, CCBE45931D72F79C00414A64 /* UITableView+Register.swift in Sources */, CCBE459D1D72F79C00414A64 /* CellDrawer.swift in Sources */, CCBE459F1D72F79C00414A64 /* Item.swift in Sources */, diff --git a/TableViewKit/ArrayDiff.swift b/TableViewKit/ArrayDiff.swift index 9466d99..85ba89e 100644 --- a/TableViewKit/ArrayDiff.swift +++ b/TableViewKit/ArrayDiff.swift @@ -1,20 +1,34 @@ import Foundation -struct Diff { +struct Diff { var inserts: [Int] var deletes: [Int] var moves: [(Int, Int)] + var insertsElement: [Element] + var deletesElement: [Element] + var isEmpty: Bool { return (inserts.count + deletes.count + moves.count) == 0 } - init(inserts: [Int] = [], deletes: [Int] = [], moves: [(Int, Int)] = []) { + init(inserts: [Int] = [], deletes: [Int] = [], moves: [(Int, Int)] = [], + insertsElement: [Element] = [], deletesElement: [Element] = []) { self.inserts = inserts self.deletes = deletes self.moves = moves + self.insertsElement = insertsElement + self.deletesElement = deletesElement + } } +enum ArrayIndexesChanges { + case inserts(Int) + case deletes(Int) + case updates(Int) + case moves(Int, Int) + case beginUpdates + case endUpdates } class DiffIterator: IteratorProtocol { @@ -27,23 +41,23 @@ class DiffIterator: IteratorProtocol { init(matrix: Matrix) { self.matrix = matrix - self.last = Coordinates(x: matrix.rows-1, y: matrix.columns-1) + self.last = Coordinates(x: matrix.rows - 1, y: matrix.columns - 1) } - func next() -> ArrayChanges? { - while(last.x > 0 || last.y > 0) { + func next() -> ArrayIndexesChanges? { + while last.x > 0 || last.y > 0 { if last.x == 0 { last.y -= 1 - return .inserts([last.y]) + return .inserts(last.y) } else if last.y == 0 { last.x -= 1 - return .deletes([last.x]) + return .deletes(last.x) } else if matrix[last.x, last.y] == matrix[last.x, last.y - 1] { last.y -= 1 - return .inserts([last.y]) + return .inserts(last.y) } else if matrix[last.x, last.y] == matrix[last.x - 1, last.y] { last.x -= 1 - return .deletes([last.x]) + return .deletes(last.x) } else { last.x -= 1 last.y -= 1 @@ -91,29 +105,29 @@ extension Array { typealias Predicate = (Element, Element) -> Bool - static func diff(between x: [Element], and y: [Element], where predicate: Predicate) -> Diff { + static func diff(between x: [Element], and y: [Element], where predicate: Predicate) -> Diff { - var matrix = Matrix(rows: x.count+1, columns: y.count+1, repeatedValue: 0) + var matrix = Matrix(rows: x.count + 1, columns: y.count + 1, repeatedValue: 0) for (i, xElem) in x.enumerated() { for (j, yElem) in y.enumerated() { if predicate(xElem, yElem) { - matrix[i+1, j+1] = matrix[i, j] + 1 + matrix[i + 1, j + 1] = matrix[i, j] + 1 } else { - matrix[i+1, j+1] = Swift.max(matrix[i, j+1], matrix[i+1, j]) + matrix[i + 1, j + 1] = Swift.max(matrix[i, j + 1], matrix[i + 1, j]) } } } - let changes = [ArrayChanges](DiffSequence(matrix: matrix)) - var inserts: [Int] = changes.flatMap { change -> [Int] in - guard case .inserts(let array) = change else { return [] } - return array - }.sorted { $0 > $1 } + let changes = [ArrayIndexesChanges](DiffSequence(matrix: matrix)) + var inserts: [Int] = changes.flatMap { change -> Int? in + guard case .inserts(let index) = change else { return nil } + return index + }.sorted { $0 > $1 } - var deletes: [Int] = changes.flatMap { change -> [Int] in - guard case .deletes(let array) = change else { return [] } - return array - }.sorted { $0 < $1 } + var deletes: [Int] = changes.flatMap { change -> Int? in + guard case .deletes(let index) = change else { return nil } + return index + }.sorted { $0 < $1 } var moves: [(Int, Int)] = [] @@ -131,7 +145,13 @@ extension Array { deleted += 1 } - return Diff(inserts: inserts, deletes: deletes, moves: moves) + let diff = Diff(inserts: inserts, + deletes: deletes, + moves: moves, + insertsElement: inserts.flatMap { y[$0] }, + deletesElement: deletes.flatMap { x[$0] }) + + return diff } } diff --git a/TableViewKit/Extensions/NSIndexSet+Array.swift b/TableViewKit/Extensions/NSIndexSet+Array.swift index 3fe5d88..50f8ccc 100644 --- a/TableViewKit/Extensions/NSIndexSet+Array.swift +++ b/TableViewKit/Extensions/NSIndexSet+Array.swift @@ -7,7 +7,7 @@ extension IndexSet { /// - parameter array: An array of integers init(_ array: [Int]) { let mutable = NSMutableIndexSet() - array.forEach {mutable.add($0)} + array.forEach { mutable.add($0) } self.init(mutable) } } diff --git a/TableViewKit/Extensions/UITableView+Moves.swift b/TableViewKit/Extensions/UITableView+Moves.swift index b1677e3..25c5997 100644 --- a/TableViewKit/Extensions/UITableView+Moves.swift +++ b/TableViewKit/Extensions/UITableView+Moves.swift @@ -4,13 +4,13 @@ import UIKit extension UITableView { func moveRows(at indexPaths: [IndexPath], to newIndexPaths: [IndexPath]) { - for (index, _) in indexPaths.enumerated() { + indexPaths.indices.forEach { index in moveRow(at: indexPaths[index], to: newIndexPaths[index]) } } func moveSections(from: [Int], to: [Int]) { - for (index, _) in from.enumerated() { + from.indices.forEach { index in moveSection(from[index], toSection: to[index]) } } diff --git a/TableViewKit/Height.swift b/TableViewKit/Height.swift index c6577f9..442f70a 100644 --- a/TableViewKit/Height.swift +++ b/TableViewKit/Height.swift @@ -24,7 +24,7 @@ public enum Height { switch self { case .static(let value): return value - case .dynamic(_): + case .dynamic: return UITableViewAutomaticDimension } } diff --git a/TableViewKit/ObservableArray.swift b/TableViewKit/ObservableArray.swift index 6352ec0..930dc50 100644 --- a/TableViewKit/ObservableArray.swift +++ b/TableViewKit/ObservableArray.swift @@ -1,21 +1,20 @@ import Foundation -enum ArrayChanges { - case inserts([Int]) - case deletes([Int]) +enum ArrayChanges { + case inserts([Int], [Element]) + case deletes([Int], [Element]) case updates([Int]) case moves([(Int, Int)]) case beginUpdates case endUpdates } - /// An observable array. It will notify any kind of changes. public struct ObservableArray: ExpressibleByArrayLiteral, Collection, MutableCollection, RangeReplaceableCollection { /// The type of the elements of an array literal. public typealias Element = T - private var diff = Diff() + private var diff: Diff! private var array: [T] { willSet { @@ -23,14 +22,15 @@ public struct ObservableArray: ExpressibleByArrayLiteral, Collection, Mutable } didSet { callback?(.beginUpdates) - if (!diff.moves.isEmpty) { callback?(.moves(diff.moves)) } - if (!diff.deletes.isEmpty) { callback?(.deletes(diff.deletes)) } - if (!diff.inserts.isEmpty) { callback?(.inserts(diff.inserts)) } + if !diff.moves.isEmpty { callback?(.moves(diff.moves)) } + if !diff.deletes.isEmpty { callback?(.deletes(diff.deletes, diff.deletesElement)) } + if !diff.inserts.isEmpty { callback?(.inserts(diff.inserts, diff.insertsElement)) } callback?(.endUpdates) + diff = nil } } - var callback: ((ArrayChanges) -> Void)? + var callback: ((ArrayChanges) -> Void)? /// Creates an empty `ObservableArray` public init() { @@ -71,8 +71,8 @@ public struct ObservableArray: ExpressibleByArrayLiteral, Collection, Mutable /// - parameter i: A valid index of the collection. i must be less than endIndex. /// /// - returns: The index value immediately after i. - public func index(after i: Int) -> Int { - return array.index(after: i) + public func index(after index: Int) -> Int { + return array.index(after: index) } /// A Boolean value indicating whether the collection is empty. @@ -101,6 +101,7 @@ public struct ObservableArray: ExpressibleByArrayLiteral, Collection, Mutable /// /// - parameter subrange: The subrange that must be replaced /// - parameter newElements: The new elements that must be replaced + // swiftlint:disable:next line_length public mutating func replaceSubrange(_ subrange: Range, with newElements: C) where C : Collection, C.Iterator.Element == T { array.replaceSubrange(subrange, with: newElements) } diff --git a/TableViewKit/Protocols/CellDrawer.swift b/TableViewKit/Protocols/CellDrawer.swift index 3b7593a..20d9f63 100644 --- a/TableViewKit/Protocols/CellDrawer.swift +++ b/TableViewKit/Protocols/CellDrawer.swift @@ -41,6 +41,7 @@ public extension CellDrawer { cell.item = item as? Item } + // swiftlint:disable:next force_cast return cell as! Cell } } @@ -52,9 +53,12 @@ public struct AnyCellDrawer { let draw: (UITableViewCell, Item) -> Void /// Creates a type-erased drawer that wraps the given cell drawer - public init(_ drawer: Drawer.Type) where Drawer.GenericItem == GenericItem, Drawer.Cell == Cell { + public init(_ drawer: Drawer.Type) + where Drawer.GenericItem == GenericItem, Drawer.Cell == Cell { self.type = drawer.type.cellType + // swiftlint:disable:next force_cast self.cell = { manager, item, indexPath in drawer.cell(in: manager, with: item as! GenericItem, for: indexPath) } + // swiftlint:disable:next force_cast self.draw = { cell, item in drawer.draw(cell as! Cell, with: item as! GenericItem) } } diff --git a/TableViewKit/Protocols/HeaderFooterDrawer.swift b/TableViewKit/Protocols/HeaderFooterDrawer.swift index 8b5be49..9703065 100644 --- a/TableViewKit/Protocols/HeaderFooterDrawer.swift +++ b/TableViewKit/Protocols/HeaderFooterDrawer.swift @@ -31,6 +31,7 @@ public extension HeaderFooterDrawer { /// Returns a dequeued header/footer static func view(in manager: TableViewManager, with item: GenericItem) -> View { + // swiftlint:disable:next force_cast return manager.tableView.dequeueReusableHeaderFooterView(withIdentifier: self.type.reusableIdentifier) as! View } } @@ -42,9 +43,12 @@ public struct AnyHeaderFooterDrawer { let draw: (UITableViewHeaderFooterView, HeaderFooter) -> Void /// Creates a type-erased drawer that wraps the given header or footer drawer - public init(_ drawer: Drawer.Type) where Drawer.GenericItem == GenericItem, Drawer.View == View { + public init(_ drawer: Drawer.Type) + where Drawer.GenericItem == GenericItem, Drawer.View == View { self.type = drawer.type.headerFooterType + // swiftlint:disable:next force_cast self.view = { manager, item in drawer.view(in: manager, with: item as! GenericItem) } + // swiftlint:disable:next force_cast self.draw = { cell, item in drawer.draw(cell as! View, with: item as! GenericItem) } } diff --git a/TableViewKit/Protocols/Item.swift b/TableViewKit/Protocols/Item.swift index 48f4b87..3254205 100644 --- a/TableViewKit/Protocols/Item.swift +++ b/TableViewKit/Protocols/Item.swift @@ -20,6 +20,24 @@ public extension Item where Self: Equatable { } } +// swiftlint:disable:next identifier_name +private var ItemTableViewManagerKey: UInt8 = 0 + +extension Item { + + public internal(set) var manager: TableViewManager? { + get { + return objc_getAssociatedObject(self, &ItemTableViewManagerKey) as? TableViewManager + } + set(newValue) { + objc_setAssociatedObject(self, + &ItemTableViewManagerKey, + newValue as AnyObject, + objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN) + } + } +} + extension Item { public func equals(_ other: Any?) -> Bool { @@ -34,41 +52,37 @@ extension Item { return .dynamic(44.0) } - /// Returns the `section` of the `item` in the specified `manager` - /// - /// - parameter manager: A `manager` where the `item` may have been added + /// Returns the `section` of the `item` /// /// - returns: The `section` of the `item` or `nil` if not present - public func section(in manager: TableViewManager) -> Section? { - guard let indexPath = self.indexPath(in: manager) else { return nil } - return manager.sections[indexPath.section] + public var section: Section? { + guard let indexPath = indexPath else { return nil } + return manager?.sections[indexPath.section] } - /// Returns the `indexPath` of the `item` in the specified `manager` - /// - /// - parameter manager: A `manager` where the `item` may have been added + /// Returns the `indexPath` of the `item` /// /// - returns: The `indexPath` of the `item` or `nil` if not present - public func indexPath(in manager: TableViewManager) -> IndexPath? { + public var indexPath: IndexPath? { + guard let manager = manager else { return nil } for section in manager.sections { guard - let sectionIndex = section.index(in: manager), + let sectionIndex = section.index, let rowIndex = section.items.index(of: self) else { continue } return IndexPath(row: rowIndex, section: sectionIndex) } return nil } - /// Reload the `item` in the specified `manager` with an `animation` + /// Reload the `item` with an `animation` /// - /// - parameter manager: A `manager` where the `item` may have been added /// - parameter animation: A constant that indicates how the reloading is to be animated /// /// - returns: The `section` of the `item` or `nil` if not present - public func reload(in manager: TableViewManager, with animation: UITableViewRowAnimation = .automatic) { - guard let indexPath = self.indexPath(in: manager) else { return } - let section = manager.sections[indexPath.section] - section.items.callback?(.updates([indexPath.row])) + public func reload(with animation: UITableViewRowAnimation = .automatic) { + guard let indexPath = indexPath else { return } + let section = manager?.sections[indexPath.section] + section?.items.callback?(.updates([indexPath.row])) } } diff --git a/TableViewKit/Protocols/Selectable.swift b/TableViewKit/Protocols/Selectable.swift index 0d1b173..2da5d7d 100644 --- a/TableViewKit/Protocols/Selectable.swift +++ b/TableViewKit/Protocols/Selectable.swift @@ -11,22 +11,26 @@ extension Selectable { /// Select the `item` /// - /// - parameter manager: A `manager` where the `item` may have been added /// - parameter animated: If the selection should be animated /// - parameter scrollPosition: The scrolling position - public func select(in manager: TableViewManager, animated: Bool, scrollPosition: UITableViewScrollPosition = .none) { + // swiftlint:disable:next line_length + public func select(animated: Bool, scrollPosition: UITableViewScrollPosition = .none) { + guard let tableView = manager?.tableView else { return } - manager.tableView.selectRow(at: indexPath(in: manager), animated: animated, scrollPosition: scrollPosition) - manager.tableView(manager.tableView, didSelectRowAt: indexPath(in: manager)!) + tableView.selectRow(at: indexPath, animated: animated, scrollPosition: scrollPosition) + if let indexPath = indexPath { + tableView.delegate?.tableView?(tableView, didSelectRowAt: indexPath) + } } /// Deselect the `item` /// - /// - parameter manager: A `manager` where the `item` may have been added /// - parameter animated: If the selection should be animated - public func deselect(in manager: TableViewManager, animated: Bool) { - if let indexPath = indexPath(in: manager) { - manager.tableView.deselectRow(at: indexPath, animated: animated) + public func deselect(animated: Bool) { + guard let tableView = manager?.tableView else { return } + + if let indexPath = indexPath { + tableView.deselectRow(at: indexPath, animated: animated) } } diff --git a/TableViewKit/Section.swift b/TableViewKit/Section.swift index 6af9e89..59e5945 100644 --- a/TableViewKit/Section.swift +++ b/TableViewKit/Section.swift @@ -14,7 +14,7 @@ public protocol Section: class, AnyEquatable { /// The `footer` of the section, none if not defined var footer: HeaderFooterView { get } - func index(in manager: TableViewManager) -> Int? + var index: Int? { get } } public extension Section where Self: Equatable { @@ -35,6 +35,24 @@ extension Section { public var footer: HeaderFooterView { return nil } } +// swiftlint:disable:next identifier_name +private var SectionTableViewManagerKey: UInt8 = 0 + +extension Section { + + public internal(set) var manager: TableViewManager? { + get { + return objc_getAssociatedObject(self, &SectionTableViewManagerKey) as? TableViewManager + } + set(newValue) { + objc_setAssociatedObject(self, + &SectionTableViewManagerKey, + newValue as AnyObject, + objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN) + } + } +} + extension Section { public func equals(_ other: Any?) -> Bool { @@ -44,55 +62,67 @@ extension Section { return false } - /// Returns the `index` of the `section` in the specified `manager` - /// - /// - parameter manager: A `manager` where the `section` may have been added + /// Returns the `index` of the `section` /// /// - returns: The `index` of the `section` or `nil` if not present - public func index(in manager: TableViewManager) -> Int? { - return manager.sections.index(of: self) + public var index: Int? { + return manager?.sections.index(of: self) } /// Register the section in the specified manager /// /// - parameter manager: A manager where the section may have been added internal func register(in manager: TableViewManager) { + setup(in: manager) + if case .view(let header) = header { - manager.tableView.register(type(of: header).drawer.type) + manager.register(type(of: header).drawer.type) } if case .view(let footer) = footer { - manager.tableView.register(type(of: footer).drawer.type) + manager.register(type(of: footer).drawer.type) } - items.forEach { - manager.tableView.register(type(of: $0).drawer.type) + items.forEach { item in + item.manager = manager + manager.register(type(of: item).drawer.type) } } + /// Unregister the section + internal func unregister() { + self.manager = nil + items.forEach { $0.manager = nil } + } + /// Setup the section internals /// /// - parameter manager: A manager where the section may have been added - internal func setup(in manager: TableViewManager) { - items.callback = { [weak self, weak manager] change in - if let manager = manager { - self?.onItemsUpdate(withChanges: change, in: manager) + private func setup(in manager: TableViewManager) { + self.manager = manager + + items.callback = { [weak self] change in + if let weakSelf = self, let manager = weakSelf.manager { + weakSelf.onItemsUpdate(withChanges: change, in: manager) } } } - private func onItemsUpdate(withChanges changes: ArrayChanges, in manager: TableViewManager) { + private func onItemsUpdate(withChanges changes: ArrayChanges, in manager: TableViewManager) { - guard let sectionIndex = index(in: manager) else { return } + guard let sectionIndex = index else { return } let tableView = manager.tableView - if case .inserts(let array) = changes { - array.forEach { manager.register(type(of: items[$0]).drawer.type) } - } + if case .inserts(_, let items) = changes { + items.forEach { item in + item.manager = manager + manager.register(type(of: item).drawer.type) + } + } switch changes { - case .inserts(let array): - let indexPaths = array.map { IndexPath(item: $0, section: sectionIndex) } + case .inserts(let array, _): + let indexPaths = array.map { IndexPath(item: $0, section: sectionIndex) } tableView.insertRows(at: indexPaths, with: manager.animation) - case .deletes(let array): + case .deletes(let array, _): let indexPaths = array.map { IndexPath(item: $0, section: sectionIndex) } tableView.deleteRows(at: indexPaths, with: manager.animation) case .updates(let array): diff --git a/TableViewKit/TableViewKitDataSource.swift b/TableViewKit/TableViewKitDataSource.swift new file mode 100644 index 0000000..0f0da17 --- /dev/null +++ b/TableViewKit/TableViewKitDataSource.swift @@ -0,0 +1,62 @@ +import UIKit + +open class TableViewKitDataSource: NSObject, UITableViewDataSource { + + open unowned var manager: TableViewManager + private var sections: ObservableArray
{ return manager.sections } + + public required init(manager: TableViewManager) { + self.manager = manager + } + + /// Implementation of UITableViewDataSource + open func numberOfSections(in tableView: UITableView) -> Int { + return sections.count + } + + /// Implementation of UITableViewDataSource + open func tableView(_ tableView: UITableView, numberOfRowsInSection sectionIndex: Int) -> Int { + let section = sections[sectionIndex] + return section.items.count + } + + /// Implementation of UITableViewDataSource + open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let currentItem = manager.item(at: indexPath) + let drawer = type(of: currentItem).drawer + + let cell = drawer.cell(in: manager, with: currentItem, for: indexPath) + drawer.draw(cell, with: currentItem) + + return cell + } + + /// Implementation of UITableViewDataSource + open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return title(for: { $0.header }, inSection: section) + } + + /// Implementation of UITableViewDataSource + open func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + return title(for: { $0.footer }, inSection: section) + } + + /// Implementation of UITableViewDataSource + // swiftlint:disable:next line_length + open func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { + // Intentionally blank. Required to use UITableViewRowActions + } + + /// Implementation of UITableViewDataSource + open func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + return manager.item(at: indexPath) is Editable + } + + fileprivate func title(for key: (Section) -> HeaderFooterView, inSection section: Int) -> String? { + if case .title(let value) = key(sections[section]) { + return value + } + return nil + } + +} diff --git a/TableViewKit/TableViewKitDelegate.swift b/TableViewKit/TableViewKitDelegate.swift new file mode 100644 index 0000000..e526672 --- /dev/null +++ b/TableViewKit/TableViewKitDelegate.swift @@ -0,0 +1,119 @@ +import UIKit + +open class TableViewKitDelegate: NSObject, UITableViewDelegate { + + open unowned var manager: TableViewManager + open weak var scrollDelegate: UIScrollViewDelegate? + private var sections: ObservableArray
{ return manager.sections } + + public required init(manager: TableViewManager) { + self.manager = manager + } + + /// Implementation of UITableViewDelegate + open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let currentItem = manager.item(at: indexPath) as? Selectable else { return } + currentItem.didSelect() + } + + /// Implementation of UITableViewDelegate + open func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return height(at: indexPath) ?? tableView.rowHeight + } + + /// Implementation of UITableViewDelegate + open func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return height(for: { $0.header }, inSection: section) ?? tableView.sectionHeaderHeight + } + + /// Implementation of UITableViewDelegate + open func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + return height(for: { $0.footer }, inSection: section) ?? tableView.sectionFooterHeight + } + + /// Implementation of UITableViewDelegate + open func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { + return estimatedHeight(at: indexPath) ?? tableView.estimatedRowHeight + } + + /// Implementation of UITableViewDelegate + open func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat { + return estimatedHeight(for: { $0.header }, inSection: section) ?? tableView.estimatedSectionHeaderHeight + } + + /// Implementation of UITableViewDelegate + open func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat { + return estimatedHeight(for: { $0.footer }, inSection: section) ?? tableView.estimatedSectionHeaderHeight + } + + /// Implementation of UITableViewDelegate + open func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + return view(for: { $0.header }, inSection: section) + } + + /// Implementation of UITableViewDelegate + open func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + return view(for: { $0.footer }, inSection: section) + } + + /// Implementation of UITableViewDelegate + open func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { + guard let item = manager.item(at: indexPath) as? Editable else { return nil } + return item.actions + } + + /// Implementation of UIScrollViewDelegate + open func scrollViewDidScroll(_ scrollView: UIScrollView) { + scrollDelegate?.scrollViewDidScroll?(scrollView) + } + + /// Implementation of UIScrollViewDelegate + open func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + scrollDelegate?.scrollViewWillBeginDragging?(scrollView) + } + + /// Implementation of UIScrollViewDelegate + open func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + scrollDelegate?.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate) + } + + fileprivate func view(for key: (Section) -> HeaderFooterView, inSection section: Int) -> UIView? { + guard case .view(let item) = key(sections[section]) else { return nil } + + let drawer = type(of: item).drawer + let view = drawer.view(in: manager, with: item) + drawer.draw(view, with: item) + + return view + } + + fileprivate func estimatedHeight(for key: (Section) -> HeaderFooterView, inSection section: Int) -> CGFloat? { + let item = key(sections[section]) + switch item { + case .view(let view): + guard let height = view.height else { return nil } + return height.estimated + case .title: + return 1.0 + default: + return nil + } + } + + fileprivate func estimatedHeight(at indexPath: IndexPath) -> CGFloat? { + guard let height = manager.item(at: indexPath).height else { return nil } + return height.estimated + } + + fileprivate func height(for key: (Section) -> HeaderFooterView, inSection section: Int) -> CGFloat? { + guard case .view(let view) = key(sections[section]), let value = view.height + else { return nil } + return value.height + } + + fileprivate func height(at indexPath: IndexPath) -> CGFloat? { + guard let value = manager.item(at: indexPath).height else { return nil } + return value.height + } + +} diff --git a/TableViewKit/TableViewManager.swift b/TableViewKit/TableViewManager.swift index dc004f2..cd96384 100644 --- a/TableViewKit/TableViewManager.swift +++ b/TableViewKit/TableViewManager.swift @@ -5,7 +5,7 @@ import UIKit /// It controls how to display an array of sections, from the header, to its items, to the footer. /// It automatically registers any related initial or new views/cells for reusability. /// Any changes of the sections will be automatically animated and reflected. -open class TableViewManager: NSObject { +open class TableViewManager { /// The `tableView` linked to this manager open let tableView: UITableView @@ -15,66 +15,55 @@ open class TableViewManager: NSObject { open var animation: UITableViewRowAnimation = .automatic - var reusableIdentifiers: Set = [] + open var dataSource: TableViewKitDataSource? { didSet { tableView.dataSource = dataSource } } + open var delegate: TableViewKitDelegate? { didSet { tableView.delegate = delegate } } + open var scrollDelegate: UIScrollViewDelegate? { didSet { delegate?.scrollDelegate = scrollDelegate } } - /// Initialize a `TableViewManager` with a `tableView`. - /// - /// - parameter tableView: A `tableView` that will be controlled by the `TableViewManager` - public init(tableView: UITableView) { - self.tableView = tableView - self.sections = [] - super.init() - self.tableView.dataSource = self - self.tableView.delegate = self - self.setupSections() - - } + var reusableIdentifiers: Set = [] /// Initialize a `TableViewManager` with a `tableView` and an initial array of sections /// /// - parameter tableView: A `tableView` that will be controlled by the `TableViewManager` /// - parameter sections: An array of sections - public init(tableView: UITableView, sections: [Section]) { + public init(tableView: UITableView, sections: [Section] = []) { self.tableView = tableView self.sections = ObservableArray(array: sections) - super.init() - self.tableView.dataSource = self - self.tableView.delegate = self + self.setupDelegates() self.setupSections() } + private func setupDelegates() { + self.delegate = TableViewKitDelegate(manager: self) + self.dataSource = TableViewKitDataSource(manager: self) + } + private func setupSections() { - sections.forEach { section in - section.setup(in: self) - section.register(in: self) - } + sections.forEach { $0.register(in: self) } sections.callback = { [weak self] in self?.onSectionsUpdate(withChanges: $0) } } - private func onSectionsUpdate(withChanges changes: ArrayChanges) { + private func onSectionsUpdate(withChanges changes: ArrayChanges
) { switch changes { - case .inserts(let array): - array.forEach { index in - sections[index].setup(in: self) - sections[index].register(in: self) - } - tableView.insertSections(IndexSet(array), with: animation) - case .deletes(let array): - tableView.deleteSections(IndexSet(array), with: animation) - case .updates(let array): - tableView.reloadSections(IndexSet(array), with: animation) - case .moves(let array): - let fromIndex = array.map { $0.0 } - let toIndex = array.map { $0.1 } + case .inserts(let indexes, let insertedSections): + insertedSections.forEach { $0.register(in: self) } + tableView.insertSections(IndexSet(indexes), with: animation) + case .deletes(let indexes, let removedSections): + removedSections.forEach { $0.unregister() } + tableView.deleteSections(IndexSet(indexes), with: animation) + case .updates(let indexes): + tableView.reloadSections(IndexSet(indexes), with: animation) + case .moves(let indexes): + let fromIndex = indexes.map { $0.0 } + let toIndex = indexes.map { $0.1 } tableView.moveSections(from: fromIndex, to: toIndex) case .beginUpdates: - if (animation == .none) { + if animation == .none { UIView.setAnimationsEnabled(false) } tableView.beginUpdates() case .endUpdates: tableView.endUpdates() - if (animation == .none) { + if animation == .none { UIView.setAnimationsEnabled(true) } } @@ -96,158 +85,8 @@ extension TableViewManager { reusableIdentifiers.insert(type.reusableIdentifier) } } -} -extension TableViewManager { - - fileprivate func item(at indexPath: IndexPath) -> Item { + func item(at indexPath: IndexPath) -> Item { return sections[indexPath.section].items[indexPath.row] } - - fileprivate func view(for key: (Section) -> HeaderFooterView, inSection section: Int) -> UIView? { - guard case .view(let item) = key(sections[section]) else { return nil } - - let drawer = type(of: item).drawer - let view = drawer.view(in: self, with: item) - drawer.draw(view, with: item) - - return view - } - - fileprivate func title(for key: (Section) -> HeaderFooterView, inSection section: Int) -> String? { - if case .title(let value) = key(sections[section]) { - return value - } - return nil - - } - - fileprivate func estimatedHeight(for key: (Section) -> HeaderFooterView, inSection section: Int) -> CGFloat? { - let item = key(sections[section]) - switch item { - case .view(let view): - guard let height = view.height else { return nil } - return height.estimated - case .title(_): - return 1.0 - default: - return nil - } - } - - fileprivate func estimatedHeight(at indexPath: IndexPath) -> CGFloat? { - guard let height = item(at: indexPath).height else { return nil } - return height.estimated - } - - fileprivate func height(for key: (Section) -> HeaderFooterView, inSection section: Int) -> CGFloat? { - guard case .view(let view) = key(sections[section]), let value = view.height - else { return nil } - return value.height - } - - fileprivate func height(at indexPath: IndexPath) -> CGFloat? { - guard let value = item(at: indexPath).height else { return nil } - return value.height - } - -} - -extension TableViewManager: UITableViewDataSource { - - /// Implementation of UITableViewDataSource - open func numberOfSections(in tableView: UITableView) -> Int { - return sections.count - } - - /// Implementation of UITableViewDataSource - open func tableView(_ tableView: UITableView, numberOfRowsInSection sectionIndex: Int) -> Int { - let section = sections[sectionIndex] - return section.items.count - } - - /// Implementation of UITableViewDataSource - open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let currentItem = item(at: indexPath) - let drawer = type(of: currentItem).drawer - - let cell = drawer.cell(in: self, with: currentItem, for: indexPath) - drawer.draw(cell, with: currentItem) - - return cell - } - - /// Implementation of UITableViewDataSource - open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return title(for: {$0.header}, inSection: section) - } - - /// Implementation of UITableViewDataSource - open func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { - return title(for: {$0.footer}, inSection: section) - } - - /// Implementation of UITableViewDataSource - open func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { - // Intentionally blank. Required to use UITableViewRowActions - } -} - -extension TableViewManager: UITableViewDelegate { - - /// Implementation of UITableViewDelegate - open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - guard let currentItem = item(at: indexPath) as? Selectable else { return } - currentItem.didSelect() - } - - /// Implementation of UITableViewDelegate - open func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - return height(at: indexPath) ?? tableView.rowHeight - } - - /// Implementation of UITableViewDelegate - open func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return height(for: {$0.header}, inSection: section) ?? tableView.sectionHeaderHeight - } - - /// Implementation of UITableViewDelegate - open func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { - return height(for: {$0.footer}, inSection: section) ?? tableView.sectionFooterHeight - } - - /// Implementation of UITableViewDelegate - open func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { - return estimatedHeight(at: indexPath) ?? tableView.estimatedRowHeight - } - - /// Implementation of UITableViewDelegate - open func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat { - return estimatedHeight(for: {$0.header}, inSection: section) ?? tableView.estimatedSectionHeaderHeight - } - - /// Implementation of UITableViewDelegate - open func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat { - return estimatedHeight(for: {$0.footer}, inSection: section) ?? tableView.estimatedSectionHeaderHeight - } - - /// Implementation of UITableViewDelegate - open func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - return view(for: {$0.header}, inSection: section) - } - - /// Implementation of UITableViewDelegate - open func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { - return view(for: {$0.footer}, inSection: section) - } - - open func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - return item(at: indexPath) is Editable - } - - /// Implementation of UITableViewDelegate - open func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { - guard let item = item(at: indexPath) as? Editable else { return nil } - return item.actions - } } diff --git a/TableViewKitTests/TableViewDataSourceTests.swift b/TableViewKitTests/TableViewDataSourceTests.swift index 895072c..a918251 100644 --- a/TableViewKitTests/TableViewDataSourceTests.swift +++ b/TableViewKitTests/TableViewDataSourceTests.swift @@ -63,6 +63,7 @@ class DifferentCell: UITableViewCell { } class TableViewDataSourceTests: XCTestCase { fileprivate var tableViewManager: TableViewManager! + fileprivate var dataSource: TableViewKitDataSource { return tableViewManager.dataSource! } override func setUp() { super.setUp() @@ -80,7 +81,7 @@ class TableViewDataSourceTests: XCTestCase { func testCellForRow() { let indexPath = IndexPath(row: 0, section: 0) - let cell = self.tableViewManager.tableView(self.tableViewManager.tableView, cellForRowAt: indexPath) + let cell = dataSource.tableView(self.tableViewManager.tableView, cellForRowAt: indexPath) expect(cell).to(beAnInstanceOf(TestCell.self)) } @@ -94,53 +95,40 @@ class TableViewDataSourceTests: XCTestCase { let otherItem = DifferentItem() section.items.append(otherItem) - let indexPath = otherItem.indexPath(in: tableViewManager)! - let drawer = type(of: otherItem).drawer - let cell = drawer.cell(in: tableViewManager, with: otherItem as Item, for: indexPath) + let indexPath = otherItem.indexPath! + let drawer = type(of: otherItem).drawer + let cell = drawer.cell(in: tableViewManager, with: otherItem as Item, for: indexPath) XCTAssertTrue(cell is DifferentCell) } func testNumberOfSections() { - let count = self.tableViewManager.numberOfSections(in: self.tableViewManager.tableView) - expect(count).to(equal(2)) + let count = dataSource.numberOfSections(in: self.tableViewManager.tableView) + expect(count) == 2 } func testNumberOfRowsInSection() { - let count = self.tableViewManager.tableView(self.tableViewManager.tableView, numberOfRowsInSection: 0) + let count = dataSource.tableView(self.tableViewManager.tableView, numberOfRowsInSection: 0) expect(count) == 1 } func testTitleForHeaderInSection() { - let title = self.tableViewManager.tableView(self.tableViewManager.tableView, titleForHeaderInSection: 0)! + let title = dataSource.tableView(self.tableViewManager.tableView, titleForHeaderInSection: 0)! let section = tableViewManager.sections.first! - expect(HeaderFooterView.title(title)).to(equal(section.header)) + expect(HeaderFooterView.title(title)) == section.header } func testTitleForFooterInSection() { var title: String? - title = self.tableViewManager.tableView(self.tableViewManager.tableView, titleForFooterInSection: 0) + title = dataSource.tableView(self.tableViewManager.tableView, titleForFooterInSection: 0) let section = tableViewManager.sections.first! - expect(HeaderFooterView.title(title!)).to(equal(section.footer)) + expect(HeaderFooterView.title(title!)) == section.footer - title = self.tableViewManager.tableView(self.tableViewManager.tableView, titleForFooterInSection: 1) + title = dataSource.tableView(self.tableViewManager.tableView, titleForFooterInSection: 1) expect(title).to(beNil()) } - func testViewForHeaderInSection() { - let view = self.tableViewManager.tableView(self.tableViewManager.tableView, viewForHeaderInSection: 0) - expect(view).to(beNil()) - } - - func testViewForFooterInSection() { - var view: UIView? - view = self.tableViewManager.tableView(self.tableViewManager.tableView, viewForFooterInSection: 0) - expect(view).to(beNil()) - - view = self.tableViewManager.tableView(self.tableViewManager.tableView, viewForFooterInSection: 1) - expect(view).notTo(beNil()) - } } diff --git a/TableViewKitTests/TableViewDelegateTests.swift b/TableViewKitTests/TableViewDelegateTests.swift index c249f56..44b6f4e 100644 --- a/TableViewKitTests/TableViewDelegateTests.swift +++ b/TableViewKitTests/TableViewDelegateTests.swift @@ -89,7 +89,8 @@ class EditableItem: SelectableItem, Editable { class TableViewDelegateTests: XCTestCase { - private var tableViewManager: TableViewManager! + fileprivate var tableViewManager: TableViewManager! + fileprivate var delegate: TableViewKitDelegate { return tableViewManager.delegate! } override func setUp() { super.setUp() @@ -109,41 +110,41 @@ class TableViewDelegateTests: XCTestCase { func testEstimatedHeightForHeader() { var height: CGFloat - height = tableViewManager.tableView(tableViewManager.tableView, estimatedHeightForHeaderInSection: 0) - expect(height).to(beGreaterThan(0.0)) + height = delegate.tableView(tableViewManager.tableView, estimatedHeightForHeaderInSection: 0) + expect(height) > 0.0 - height = tableViewManager.tableView(tableViewManager.tableView, estimatedHeightForHeaderInSection: 1) - expect(height).to(equal(tableViewManager.tableView.estimatedSectionHeaderHeight)) + height = delegate.tableView(tableViewManager.tableView, estimatedHeightForHeaderInSection: 1) + expect(height) == tableViewManager.tableView.estimatedSectionHeaderHeight } func testHeightForHeader() { var height: CGFloat - height = tableViewManager.tableView(tableViewManager.tableView, heightForHeaderInSection: 0) - expect(height).to(equal(UITableViewAutomaticDimension)) + height = delegate.tableView(tableViewManager.tableView, heightForHeaderInSection: 0) + expect(height) == UITableViewAutomaticDimension } func testEstimatedHeightForFooter() { var height: CGFloat - height = tableViewManager.tableView(tableViewManager.tableView, estimatedHeightForFooterInSection: 0) - expect(height).to(beGreaterThan(0.0)) + height = delegate.tableView(tableViewManager.tableView, estimatedHeightForFooterInSection: 0) + expect(height) > 0.0 - height = tableViewManager.tableView(tableViewManager.tableView, estimatedHeightForFooterInSection: 1) - expect(height).to(equal(tableViewManager.tableView.estimatedSectionFooterHeight)) + height = delegate.tableView(tableViewManager.tableView, estimatedHeightForFooterInSection: 1) + expect(height) == tableViewManager.tableView.estimatedSectionFooterHeight - height = tableViewManager.tableView(tableViewManager.tableView, estimatedHeightForFooterInSection: 2) - expect(height).to(equal(44.0)) + height = delegate.tableView(tableViewManager.tableView, estimatedHeightForFooterInSection: 2) + expect(height) == 44.0 } func testHeightForFooter() { var height: CGFloat - height = tableViewManager.tableView(tableViewManager.tableView, heightForFooterInSection: 0) - expect(height).to(equal(UITableViewAutomaticDimension)) + height = delegate.tableView(tableViewManager.tableView, heightForFooterInSection: 0) + expect(height) == UITableViewAutomaticDimension - height = tableViewManager.tableView(tableViewManager.tableView, heightForFooterInSection: 2) - expect(height).to(equal(UITableViewAutomaticDimension)) + height = delegate.tableView(tableViewManager.tableView, heightForFooterInSection: 2) + expect(height) == UITableViewAutomaticDimension } func testEstimatedHeightForRowAtIndexPath() { @@ -151,16 +152,16 @@ class TableViewDelegateTests: XCTestCase { var indexPath: IndexPath indexPath = IndexPath(row: 0, section: 0) - height = tableViewManager.tableView(tableViewManager.tableView, estimatedHeightForRowAt: indexPath) - expect(height).to(equal(44.0)) + height = delegate.tableView(tableViewManager.tableView, estimatedHeightForRowAt: indexPath) + expect(height) == 44.0 indexPath = IndexPath(row: 0, section: 1) - height = tableViewManager.tableView(tableViewManager.tableView, estimatedHeightForRowAt: indexPath) - expect(height).to(equal(tableViewManager.tableView.estimatedRowHeight)) + height = delegate.tableView(tableViewManager.tableView, estimatedHeightForRowAt: indexPath) + expect(height) == tableViewManager.tableView.estimatedRowHeight indexPath = IndexPath(row: 1, section: 1) - height = tableViewManager.tableView(tableViewManager.tableView, estimatedHeightForRowAt: indexPath) - expect(height).to(equal(20.0)) + height = delegate.tableView(tableViewManager.tableView, estimatedHeightForRowAt: indexPath) + expect(height) == 20.0 } func testHeightForRowAtIndexPath() { @@ -168,37 +169,37 @@ class TableViewDelegateTests: XCTestCase { var indexPath: IndexPath indexPath = IndexPath(row: 0, section: 0) - height = tableViewManager.tableView(tableViewManager.tableView, heightForRowAt: indexPath) - expect(height).to(equal(UITableViewAutomaticDimension)) + height = delegate.tableView(tableViewManager.tableView, heightForRowAt: indexPath) + expect(height) == UITableViewAutomaticDimension indexPath = IndexPath(row: 0, section: 1) - height = tableViewManager.tableView(tableViewManager.tableView, heightForRowAt: indexPath) - expect(height).to(equal(tableViewManager.tableView.rowHeight)) + height = delegate.tableView(tableViewManager.tableView, heightForRowAt: indexPath) + expect(height) == tableViewManager.tableView.rowHeight indexPath = IndexPath(row: 1, section: 1) - height = tableViewManager.tableView(tableViewManager.tableView, heightForRowAt: indexPath) - expect(height).to(equal(StaticHeigthItem.testStaticHeightValue)) + height = delegate.tableView(tableViewManager.tableView, heightForRowAt: indexPath) + expect(height) == StaticHeigthItem.testStaticHeightValue } func testSelectRow() { var indexPath: IndexPath indexPath = IndexPath(row: 0, section: 0) - tableViewManager.tableView(tableViewManager.tableView, didSelectRowAt: indexPath) + delegate.tableView(tableViewManager.tableView, didSelectRowAt: indexPath) let section = tableViewManager.sections[0] indexPath = IndexPath(row: section.items.count, section: 0) let item = SelectableItem() section.items.append(item) - tableViewManager.tableView(tableViewManager.tableView, didSelectRowAt: indexPath) - expect(item.check).to(equal(1)) + delegate.tableView(tableViewManager.tableView, didSelectRowAt: indexPath) + expect(item.check) == 1 - item.select(in: tableViewManager, animated: true) - expect(item.check).to(equal(2)) + item.select(animated: true) + expect(item.check) == 2 - item.deselect(in: tableViewManager, animated: true) - expect(item.check).to(equal(2)) + item.deselect(animated: true) + expect(item.check) == 2 } func testEditableRows() { @@ -210,13 +211,27 @@ class TableViewDelegateTests: XCTestCase { editableItem.actions = [deleteAction] section.items.append(editableItem) - let sectionIndex = section.index(in: tableViewManager)! - let rows = tableViewManager.tableView(tableViewManager.tableView, numberOfRowsInSection: sectionIndex) - XCTAssert(rows == 2) - - let indexPath = editableItem.indexPath(in: tableViewManager)! - let actions = tableViewManager.tableView(tableViewManager.tableView, editActionsForRowAt: indexPath) + let indexPath = editableItem.indexPath! + let actions = delegate.tableView(tableViewManager.tableView, editActionsForRowAt: indexPath) XCTAssertNotNil(actions) XCTAssert(actions!.count == 1) } + + func testViewForHeaderInSection() { + let view = delegate.tableView(self.tableViewManager.tableView, viewForHeaderInSection: 0) + expect(view).to(beNil()) + } + + func testViewForFooterInSection() { + var view: UIView? + view = delegate.tableView(self.tableViewManager.tableView, viewForFooterInSection: 0) + expect(view).to(beNil()) + + view = delegate.tableView(self.tableViewManager.tableView, viewForFooterInSection: 1) + expect(view).to(beNil()) + + view = delegate.tableView(self.tableViewManager.tableView, viewForFooterInSection: 2) + expect(view).toNot(beNil()) + + } } diff --git a/TableViewKitTests/TableViewKitTests.swift b/TableViewKitTests/TableViewKitTests.swift index f08e325..423f02c 100644 --- a/TableViewKitTests/TableViewKitTests.swift +++ b/TableViewKitTests/TableViewKitTests.swift @@ -9,6 +9,7 @@ class TestReloadDrawer: CellDrawer { static internal var type = CellType.class(UITableViewCell.self) static internal func draw(_ cell: UITableViewCell, with item: Any) { + // swiftlint:disable:next force_cast cell.textLabel?.text = (item as! TestReloadItem).title } } @@ -26,14 +27,14 @@ class EqualableItem: TestReloadItem, Equatable { self.title = title } - public static func ==(lhs: EqualableItem, rhs: EqualableItem) -> Bool { + public static func == (lhs: EqualableItem, rhs: EqualableItem) -> Bool { return lhs.title == rhs.title } } class EquatableSection: NoHeaderFooterSection, Equatable { - public static func ==(lhs: EquatableSection, rhs: EquatableSection) -> Bool { + public static func == (lhs: EquatableSection, rhs: EquatableSection) -> Bool { return lhs === rhs } } @@ -67,36 +68,39 @@ class TestRegisterHeaderFooterView: UITableViewHeaderFooterView { } class TableViewKitTests: XCTestCase { + var manager: TableViewManager! + override func setUp() { super.setUp() } override func tearDown() { super.tearDown() + manager = nil } func testAddSection() { - let tableViewManager = TableViewManager(tableView: UITableView()) + manager = TableViewManager(tableView: UITableView()) let section = HeaderFooterTitleSection() - tableViewManager.sections.append(section) + manager.sections.append(section) - expect(tableViewManager.sections.count).to(equal(1)) + expect(self.manager.sections.count) == 1 } func testAddItem() { - let tableViewManager = TableViewManager(tableView: UITableView()) + manager = TableViewManager(tableView: UITableView()) let item: Item = TestItem() let section = HeaderFooterTitleSection() section.items.append(item) - tableViewManager.sections.insert(section, at: 0) + manager.sections.insert(section, at: 0) - expect(section.items.count).to(equal(1)) - expect(item.section(in: tableViewManager)).notTo(beNil()) + expect(section.items.count) == 1 + expect(item.section).notTo(beNil()) section.items.remove(at: 0) section.items.append(item) @@ -142,22 +146,22 @@ class TableViewKitTests: XCTestCase { } func testRetainCycle() { - let tableViewManager = TableViewManager(tableView: UITableView()) - tableViewManager.sections.insert(HeaderFooterTitleSection(items: [TestItem()]), at: 0) + manager = TableViewManager(tableView: UITableView()) + manager.sections.insert(HeaderFooterTitleSection(items: [TestItem()]), at: 0) - weak var section: Section? = tableViewManager.sections.first + weak var section: Section? = manager.sections.first weak var item: Item? = section!.items.first expect(section).toNot(beNil()) expect(item).toNot(beNil()) - tableViewManager.sections.replace(with: [HeaderFooterTitleSection()]) + manager.sections.replace(with: [HeaderFooterTitleSection()]) expect(section).to(beNil()) expect(item).to(beNil()) } func testConvenienceInit() { - let tableViewManager = TableViewManager(tableView: UITableView(), sections: [HeaderFooterTitleSection()]) + manager = TableViewManager(tableView: UITableView(), sections: [HeaderFooterTitleSection()]) - expect(tableViewManager.sections.count).to(equal(1)) + expect(self.manager.sections.count) == 1 } func testUpdateRow() { @@ -166,19 +170,20 @@ class TableViewKitTests: XCTestCase { item.title = "Before" let section = HeaderFooterTitleSection(items: [item]) - let tableViewManager = TableViewManager(tableView: UITableView(), sections: [section]) + manager = TableViewManager(tableView: UITableView(), sections: [section]) - guard let indexPath = item.indexPath(in: tableViewManager) else { return } - var cell = tableViewManager.tableView(tableViewManager.tableView, cellForRowAt: indexPath) + guard let indexPath = item.indexPath else { return } + let dataSource = manager.dataSource! + var cell = dataSource.tableView(manager.tableView, cellForRowAt: indexPath) - expect(cell.textLabel?.text).to(equal(item.title)) + expect(cell.textLabel?.text) == item.title item.title = "After" - item.reload(in: tableViewManager) + item.reload() - cell = tableViewManager.tableView(tableViewManager.tableView, cellForRowAt: indexPath) + cell = dataSource.tableView(manager.tableView, cellForRowAt: indexPath) - expect(cell.textLabel?.text).to(equal(item.title)) + expect(cell.textLabel?.text) == item.title } func testMoveRows() { @@ -189,18 +194,18 @@ class TableViewKitTests: XCTestCase { let item2 = TestItem() let section = NoHeaderFooterSection(items: [item1, item2]) - let tableViewManager = TableViewManager(tableView: tableView, sections: [section]) + manager = TableViewManager(tableView: tableView, sections: [section]) - var indexPathItem1 = item1.indexPath(in: tableViewManager) - var indexPathItem2 = item2.indexPath(in: tableViewManager) + var indexPathItem1 = item1.indexPath + var indexPathItem2 = item2.indexPath XCTAssertNotNil(indexPathItem1) XCTAssertNotNil(indexPathItem2) section.items.replace(with: [item2, item1]) - indexPathItem1 = item1.indexPath(in: tableViewManager) - indexPathItem2 = item2.indexPath(in: tableViewManager) + indexPathItem1 = item1.indexPath + indexPathItem2 = item2.indexPath XCTAssert(indexPathItem2?.item == 0) XCTAssert(indexPathItem1?.item == 1) @@ -213,30 +218,31 @@ class TableViewKitTests: XCTestCase { let section1 = NoHeaderFooterSection() let section2 = NoHeaderFooterSection() - let tableViewManager = TableViewManager(tableView: tableView, sections: [section1, section2]) + manager = TableViewManager(tableView: tableView, sections: [section1, section2]) - XCTAssert(section1.index(in: tableViewManager) == 0) - XCTAssert(section2.index(in: tableViewManager) == 1) + XCTAssert(section1.index == 0) + XCTAssert(section2.index == 1) - tableViewManager.sections.replace(with: [section2, section1]) + manager.sections.replace(with: [section2, section1]) - XCTAssert(section1.index(in: tableViewManager) == 1) - XCTAssert(section2.index(in: tableViewManager) == 0) + XCTAssert(section1.index == 1) + XCTAssert(section2.index == 0) } func testNoCrashOnNonAddedItem() { - let tableViewManager = TableViewManager(tableView: UITableView(), sections: [HeaderFooterTitleSection()]) + manager = TableViewManager(tableView: UITableView(), sections: [HeaderFooterTitleSection()]) let item: Item = TestReloadItem() - item.reload(in: tableViewManager, with: .automatic) + item.reload(with: .automatic) - let section = item.section(in: tableViewManager) + let section = item.section expect(section).to(beNil()) } func testRegisterNibCells() { let testBundle = Bundle(for: TableViewKitTests.self) + // swiftlint:disable:next line_length let cellType = CellType.nib(UINib(nibName: String(describing: TestRegisterNibCell.self), bundle: testBundle), TestRegisterNibCell.self) let tableView = UITableView() @@ -244,30 +250,34 @@ class TableViewKitTests: XCTestCase { let cell = tableView.dequeueReusableCell(withIdentifier: cellType.reusableIdentifier) - expect(cell).toNot(equal(nil)) + expect(cell).toNot(beNil()) } func testRegisterNibHeaderFooter() { let testBundle = Bundle(for: TableViewKitTests.self) - let headerFooterType = HeaderFooterType.nib(UINib(nibName: String(describing: TestRegisterHeaderFooterView.self), bundle: testBundle), TestRegisterHeaderFooterView.self) + let nib = UINib(nibName: String(describing: TestRegisterHeaderFooterView.self), bundle: testBundle) + let headerFooterType = HeaderFooterType.nib(nib, TestRegisterHeaderFooterView.self) let tableView = UITableView() tableView.register(headerFooterType) + // swiftlint:disable:next line_length let headerFooterView = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerFooterType.reusableIdentifier) - expect(headerFooterView).toNot(equal(nil)) + expect(headerFooterView).toNot(beNil()) } func testNibClassTypeCells() { let testBundle = Bundle(for: TableViewKitTests.self) - let type = CellType.nib(UINib(nibName: String(describing: TestRegisterNibCell.self), bundle: testBundle), TestRegisterNibCell.self) + let nib = UINib(nibName: String(describing: TestRegisterNibCell.self), bundle: testBundle) + let type = CellType.nib(nib, TestRegisterNibCell.self) _ = type.cellType } func testNibClassTypeHeaderFooter() { let testBundle = Bundle(for: TableViewKitTests.self) - let type = HeaderFooterType.nib(UINib(nibName: String(describing: TestRegisterHeaderFooterView.self), bundle: testBundle), TestRegisterHeaderFooterView.self) + let nib = UINib(nibName: String(describing: TestRegisterHeaderFooterView.self), bundle: testBundle) + let type = HeaderFooterType.nib(nib, TestRegisterHeaderFooterView.self) _ = type.headerFooterType } @@ -290,4 +300,32 @@ class TableViewKitTests: XCTestCase { let registerItem = section.items.first as? TestReloadItem XCTAssert(registerItem?.title == "Register") } + + func testManagerProperty() { + let tableView = UITableView() + + let item1 = TestItem() + XCTAssertNil(item1.manager) + let section = NoHeaderFooterSection(items: [item1]) + XCTAssertNil(section.manager) + + manager = TableViewManager(tableView: tableView, sections: [section]) + XCTAssert(item1.manager === manager) + XCTAssert(section.manager === manager) + + let section2 = NoHeaderFooterSection(items: [item1]) + manager.sections.append(section2) + XCTAssert(section2.manager === manager) + + let item2 = TestItem() + section.items.append(item2) + XCTAssert(item2.manager === manager) + + let removedSection = manager.sections.removeFirst() + + XCTAssert(removedSection === section) + XCTAssertNil(removedSection.manager) + XCTAssertNil(removedSection.items[0].manager) + + } } diff --git a/build.sh b/build.sh index 02c9040..27f1a4c 100755 --- a/build.sh +++ b/build.sh @@ -4,6 +4,6 @@ set -o pipefail && time xcodebuild clean test \ -workspace TableViewKit.xcworkspace \ -scheme TableViewKit \ - -sdk iphonesimulator10.1 \ + -sdk iphonesimulator10.3 \ -destination 'platform=iOS Simulator,name=iPhone 6s,OS=10.0' \ | xcpretty