From b8a30a965fda97287dc7fa2a8b2c098b4cc5ef42 Mon Sep 17 00:00:00 2001 From: Fabian Geistert Date: Wed, 11 Sep 2019 11:05:08 +0200 Subject: [PATCH 01/31] update xcode project --- DataSource.xcodeproj/project.pbxproj | 6 ++-- .../xcschemes/DataSource.xcscheme | 28 ++++++++----------- .../xcschemes/DataSourceTests.xcscheme | 24 +++++++--------- .../xcshareddata/xcschemes/Example.xcscheme | 24 +++++++--------- 4 files changed, 35 insertions(+), 47 deletions(-) diff --git a/DataSource.xcodeproj/project.pbxproj b/DataSource.xcodeproj/project.pbxproj index 10c79ac..8c884d6 100644 --- a/DataSource.xcodeproj/project.pbxproj +++ b/DataSource.xcodeproj/project.pbxproj @@ -414,7 +414,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0820; - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1100; ORGANIZATIONNAME = "aaa - all about apps GmbH"; TargetAttributes = { 395D147B1B90610A00658680 = { @@ -756,7 +756,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -788,7 +788,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; diff --git a/DataSource.xcodeproj/xcshareddata/xcschemes/DataSource.xcscheme b/DataSource.xcodeproj/xcshareddata/xcschemes/DataSource.xcscheme index d0d4049..06fd8ac 100644 --- a/DataSource.xcodeproj/xcshareddata/xcschemes/DataSource.xcscheme +++ b/DataSource.xcodeproj/xcshareddata/xcschemes/DataSource.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + + + + @@ -40,17 +49,6 @@ - - - - - - - - + + + + @@ -39,17 +48,6 @@ - - - - - - - - + + + + @@ -49,17 +58,6 @@ - - - - - - - - Date: Wed, 11 Sep 2019 11:14:14 +0200 Subject: [PATCH 02/31] update gitignore (ignore .build/ for git) --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 19ccb4f..5da9a19 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ DerivedData # Carthage Carthage/Checkouts Carthage/Build + +# Swift Package Manager +.build/ \ No newline at end of file From 2bc90167b2f6c78819fa09fbf6d3578f5546239a Mon Sep 17 00:00:00 2001 From: Fabian Geistert Date: Wed, 11 Sep 2019 11:14:28 +0200 Subject: [PATCH 03/31] package resolve --- Package.resolved | 16 ++++++++++++++++ Package.swift | 23 +++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 Package.resolved create mode 100644 Package.swift diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..37925f1 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "Differ", + "repositoryURL": "https://github.com/tonyarnold/Differ.git", + "state": { + "branch": null, + "revision": "e2cca36e7258dd8add88ae46b5ea56509b066e21", + "version": "1.4.3" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..d38d498 --- /dev/null +++ b/Package.swift @@ -0,0 +1,23 @@ +// swift-tools-version:5.0 + +import PackageDescription + +let package = Package( + name: "DataSource", + platforms: [ + .macOS(.v10_12), + .iOS(.v11), + .tvOS(.v11), + .watchOS(.v3) + ], + products: [ + .library(name: "DataSource", targets: ["DataSource"]) + ], + dependencies: [ + .package(url: "https://github.com/tonyarnold/Differ.git", from: "1.4.3") + ], + targets: [ + .target(name: "DataSource", dependencies: ["Differ"], path: "DataSource/DataSource") + ], + swiftLanguageVersions: [.v5] +) From 6825d5098569054c3e24b13f461414729218f07b Mon Sep 17 00:00:00 2001 From: Fabian Geistert Date: Wed, 11 Sep 2019 11:17:15 +0200 Subject: [PATCH 04/31] fix custom target path in Package.swift --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index d38d498..d6de793 100644 --- a/Package.swift +++ b/Package.swift @@ -17,7 +17,7 @@ let package = Package( .package(url: "https://github.com/tonyarnold/Differ.git", from: "1.4.3") ], targets: [ - .target(name: "DataSource", dependencies: ["Differ"], path: "DataSource/DataSource") + .target(name: "DataSource", dependencies: ["Differ"], path: "DataSource/") ], swiftLanguageVersions: [.v5] ) From b4b294df0fd5b0920dac2c0a1ae5e0b69657a4ef Mon Sep 17 00:00:00 2001 From: Fabian Geistert Date: Wed, 11 Sep 2019 11:26:15 +0200 Subject: [PATCH 05/31] update readme --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 7ec02af..dc681ba 100644 --- a/README.md +++ b/README.md @@ -241,6 +241,14 @@ Current Swift compatibility breakdown: ## Installation +### Swift Package Manager (Recommended) + +Add the following dependency to your `Package.swift` file: + +``` +.package(url: "https://github.com/allaboutapps/DataSource.git", from: "7.1.0") +``` + ### Carthage Add the following line to your [Cartfile](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#cartfile). From 48c16a5f6bc2b70cfb3cecb0cebd3c42325dd15a Mon Sep 17 00:00:00 2001 From: Matthias Buchetics Date: Mon, 4 Nov 2019 14:28:03 +0100 Subject: [PATCH 06/31] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dc681ba..61b2c49 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,7 @@ Current Swift compatibility breakdown: | Swift Version | Framework Version | | ------------- | ----------------- | +| 5.1 | 8.x | | 5.0 | 7.x | | 4.2 | 6.x | | 4.1 | 5.x | @@ -246,7 +247,7 @@ Current Swift compatibility breakdown: Add the following dependency to your `Package.swift` file: ``` -.package(url: "https://github.com/allaboutapps/DataSource.git", from: "7.1.0") +.package(url: "https://github.com/allaboutapps/DataSource.git", from: "8.0.0") ``` ### Carthage @@ -254,7 +255,7 @@ Add the following dependency to your `Package.swift` file: Add the following line to your [Cartfile](https://github.com/Carthage/Carthage/blob/master/Documentation/Artifacts.md#cartfile). ``` -github "allaboutapps/DataSource", ~> 7.0 +github "allaboutapps/DataSource", ~> 8.0 ``` Then run `carthage update`. From 499ad05abc082fe5aba1c8f098c634caba166513 Mon Sep 17 00:00:00 2001 From: Stefan Draskovits Date: Fri, 3 Jan 2020 16:36:01 +0100 Subject: [PATCH 07/31] Remove separated section xib (#24) * add support for cell descriptors without xibs * remove SeparatorLineCell XIB --- DataSource.xcodeproj/project.pbxproj | 4 -- .../DataSource+UITableViewDataSource.swift | 11 +++-- .../SeparatedSection/SeparatorLineCell.swift | 46 ++++++++++++++----- .../SeparatedSection/SeparatorLineCell.xib | 43 ----------------- 4 files changed, 43 insertions(+), 61 deletions(-) delete mode 100644 DataSource/SeparatedSection/SeparatorLineCell.xib diff --git a/DataSource.xcodeproj/project.pbxproj b/DataSource.xcodeproj/project.pbxproj index 8c884d6..72b42c9 100644 --- a/DataSource.xcodeproj/project.pbxproj +++ b/DataSource.xcodeproj/project.pbxproj @@ -47,7 +47,6 @@ 4C6076D821490B00002E8BD1 /* SeparatedSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C6076D721490B00002E8BD1 /* SeparatedSection.swift */; }; 4C6076DC21490B4D002E8BD1 /* SeparatorLineCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C6076DA21490B4D002E8BD1 /* SeparatorLineCell.swift */; }; 4C6076DF21490C80002E8BD1 /* SeparatedSectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C6076DE21490C80002E8BD1 /* SeparatedSectionViewController.swift */; }; - 4C6076E121490FF3002E8BD1 /* SeparatorLineCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4C6076E021490FF3002E8BD1 /* SeparatorLineCell.xib */; }; 4CA65F60214F952E004F2F19 /* UIView+AutoLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA65F5F214F952E004F2F19 /* UIView+AutoLayout.swift */; }; 5C61C91820AF0AB0003A08B8 /* SwipeActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C61C91720AF0AB0003A08B8 /* SwipeActionViewController.swift */; }; D8D61BEE21E4DD3E00937D1C /* SeparatorLineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D61BED21E4DD3E00937D1C /* SeparatorLineViewModel.swift */; }; @@ -148,7 +147,6 @@ 4C6076D721490B00002E8BD1 /* SeparatedSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeparatedSection.swift; sourceTree = ""; }; 4C6076DA21490B4D002E8BD1 /* SeparatorLineCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeparatorLineCell.swift; sourceTree = ""; }; 4C6076DE21490C80002E8BD1 /* SeparatedSectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatedSectionViewController.swift; sourceTree = ""; }; - 4C6076E021490FF3002E8BD1 /* SeparatorLineCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SeparatorLineCell.xib; sourceTree = ""; }; 4CA65F5F214F952E004F2F19 /* UIView+AutoLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+AutoLayout.swift"; sourceTree = ""; }; 5C61C91720AF0AB0003A08B8 /* SwipeActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeActionViewController.swift; sourceTree = ""; }; D8D61BED21E4DD3E00937D1C /* SeparatorLineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorLineViewModel.swift; sourceTree = ""; }; @@ -322,7 +320,6 @@ D8D61BED21E4DD3E00937D1C /* SeparatorLineViewModel.swift */, D8D61BEF21E4E11300937D1C /* SeparatorCustomViewViewModel.swift */, 4C6076DA21490B4D002E8BD1 /* SeparatorLineCell.swift */, - 4C6076E021490FF3002E8BD1 /* SeparatorLineCell.xib */, ); path = SeparatedSection; sourceTree = ""; @@ -482,7 +479,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 4C6076E121490FF3002E8BD1 /* SeparatorLineCell.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/DataSource/DataSource+UITableViewDataSource.swift b/DataSource/DataSource+UITableViewDataSource.swift index 3fa848a..7e925a6 100644 --- a/DataSource/DataSource+UITableViewDataSource.swift +++ b/DataSource/DataSource+UITableViewDataSource.swift @@ -28,9 +28,14 @@ extension DataSource: UITableViewDataSource { let cellIdentifier = cellDescriptor.cellIdentifier let bundle = cellDescriptor.bundle ?? Bundle.main - if registerNibs && !reuseIdentifiers.contains(cellIdentifier) && bundle.path(forResource: cellIdentifier, ofType: "nib") != nil { - tableView.registerNib(cellIdentifier, bundle: bundle) - reuseIdentifiers.insert(cellIdentifier) + if registerNibs && !reuseIdentifiers.contains(cellIdentifier) { + if bundle.path(forResource: cellIdentifier, ofType: "nib") != nil { + tableView.registerNib(cellIdentifier, bundle: bundle) + reuseIdentifiers.insert(cellIdentifier) + } else { + tableView.register(cellDescriptor.cellClass, forCellReuseIdentifier: cellIdentifier) + reuseIdentifiers.insert(cellIdentifier) + } } if let closure = cellDescriptor.configureClosure ?? configure { diff --git a/DataSource/SeparatedSection/SeparatorLineCell.swift b/DataSource/SeparatedSection/SeparatorLineCell.swift index 9188003..f73ca9f 100644 --- a/DataSource/SeparatedSection/SeparatorLineCell.swift +++ b/DataSource/SeparatedSection/SeparatorLineCell.swift @@ -36,22 +36,46 @@ public struct SeparatorStyle: Equatable { } - - class SeparatorLineCell: UITableViewCell { - @IBOutlet weak var separator: UIView! - @IBOutlet weak var leftInsetConstraint: NSLayoutConstraint! - @IBOutlet weak var rightInsetConstraint: NSLayoutConstraint! - @IBOutlet weak var topInsetConstraint: NSLayoutConstraint! - @IBOutlet weak var bottomInsetConstraint: NSLayoutConstraint! - - override func awakeFromNib() { - super.awakeFromNib() + var separator: UIView! + var leftInsetConstraint: NSLayoutConstraint! + var rightInsetConstraint: NSLayoutConstraint! + var topInsetConstraint: NSLayoutConstraint! + var bottomInsetConstraint: NSLayoutConstraint! + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + customInit() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + + customInit() + } + + func customInit() { selectionStyle = .none - separator.backgroundColor = .clear backgroundColor = .clear + + separator = UIView() + separator.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(separator) + separator.backgroundColor = .clear + + leftInsetConstraint = separator.leftAnchor.constraint(equalTo: contentView.leftAnchor) + topInsetConstraint = separator.topAnchor.constraint(equalTo: contentView.topAnchor) + rightInsetConstraint = separator.rightAnchor.constraint(equalTo: contentView.rightAnchor) + bottomInsetConstraint = separator.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) + + NSLayoutConstraint.activate([ + leftInsetConstraint, + topInsetConstraint, + rightInsetConstraint, + bottomInsetConstraint + ]) } func configure(viewModel: SeparatorLineViewModel) { diff --git a/DataSource/SeparatedSection/SeparatorLineCell.xib b/DataSource/SeparatedSection/SeparatorLineCell.xib deleted file mode 100644 index 2fcbebd..0000000 --- a/DataSource/SeparatedSection/SeparatorLineCell.xib +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 7cee2310a906f9363a53c872039046910304301e Mon Sep 17 00:00:00 2001 From: Michael Wagner Date: Mon, 2 Mar 2020 09:42:13 +0100 Subject: [PATCH 08/31] fix: Return a typed Item instead of RowType like in the methods (e.g. editActions) (#25) --- DataSource/CellDescriptor.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/DataSource/CellDescriptor.swift b/DataSource/CellDescriptor.swift index 9f9222a..ed9c619 100644 --- a/DataSource/CellDescriptor.swift +++ b/DataSource/CellDescriptor.swift @@ -606,13 +606,17 @@ extension CellDescriptor: CellDescriptorTypeiOS11 { } } - public func leadingSwipeAction(_ closure: @escaping ((RowType, IndexPath) -> UISwipeActionsConfiguration?)) -> CellDescriptor { - _leadingSwipeActionsClosure = closure + public func leadingSwipeAction(_ closure: @escaping ((Item, IndexPath) -> UISwipeActionsConfiguration?)) -> CellDescriptor { + _leadingSwipeActionsClosure = { [unowned self] (row, indexPath) in + return closure(self.typedItem(row), indexPath) + } return self } - - public func trailingSwipeAction(_ closure: @escaping ((RowType, IndexPath) -> UISwipeActionsConfiguration?)) -> CellDescriptor { - _trailingSwipeActionsClosure = closure + + public func trailingSwipeAction(_ closure: @escaping ((Item, IndexPath) -> UISwipeActionsConfiguration?)) -> CellDescriptor { + _trailingSwipeActionsClosure = { [unowned self] (row, indexPath) in + return closure(self.typedItem(row), indexPath) + } return self } From c8a1b6f21a1f9ed430641a9ba716aab56263df28 Mon Sep 17 00:00:00 2001 From: mbuchetics Date: Tue, 10 Mar 2020 11:06:12 +0100 Subject: [PATCH 09/31] fixed issue with DataSource registering cells even if they are created using storyboards --- DataSource/DataSource+UITableViewDataSource.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DataSource/DataSource+UITableViewDataSource.swift b/DataSource/DataSource+UITableViewDataSource.swift index 7e925a6..9d337e4 100644 --- a/DataSource/DataSource+UITableViewDataSource.swift +++ b/DataSource/DataSource+UITableViewDataSource.swift @@ -32,7 +32,7 @@ extension DataSource: UITableViewDataSource { if bundle.path(forResource: cellIdentifier, ofType: "nib") != nil { tableView.registerNib(cellIdentifier, bundle: bundle) reuseIdentifiers.insert(cellIdentifier) - } else { + } else if cellIdentifier == SeparatorLineCell.cellIdentifier { tableView.register(cellDescriptor.cellClass, forCellReuseIdentifier: cellIdentifier) reuseIdentifiers.insert(cellIdentifier) } From 88ee2f81955c46b9e80c6afd0cd41276c6a4eb85 Mon Sep 17 00:00:00 2001 From: Stefan Wieland Date: Thu, 12 Mar 2020 21:13:21 +0100 Subject: [PATCH 10/31] Add protocol to automatically register cells created in code (#27) Co-authored-by: Stefan Wieland --- DataSource.xcodeproj/project.pbxproj | 4 ++++ DataSource/AutoregisterCell.swift | 12 ++++++++++++ DataSource/DataSource+UITableViewDataSource.swift | 2 +- .../SeparatedSection/SeparatorLineCell.swift | 2 +- Example/ViewControllers/StartViewController.swift | 15 +++++++++++++-- 5 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 DataSource/AutoregisterCell.swift diff --git a/DataSource.xcodeproj/project.pbxproj b/DataSource.xcodeproj/project.pbxproj index 72b42c9..9f52f45 100644 --- a/DataSource.xcodeproj/project.pbxproj +++ b/DataSource.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 196512982417A743004B77AF /* AutoregisterCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 196512972417A743004B77AF /* AutoregisterCell.swift */; }; 390CAF1C1E79AA560024E3E6 /* SectionDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390CAF1B1E79AA560024E3E6 /* SectionDescriptor.swift */; }; 395D14801B90610A00658680 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 395D147F1B90610A00658680 /* AppDelegate.swift */; }; 395D14871B90610A00658680 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 395D14861B90610A00658680 /* Assets.xcassets */; }; @@ -103,6 +104,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 196512972417A743004B77AF /* AutoregisterCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoregisterCell.swift; sourceTree = ""; }; 390CAF1B1E79AA560024E3E6 /* SectionDescriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionDescriptor.swift; sourceTree = ""; }; 395D147C1B90610A00658680 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 395D147F1B90610A00658680 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -237,6 +239,7 @@ 4C6076D921490B34002E8BD1 /* SeparatedSection */, 395D14A61B90612D00658680 /* DataSource.h */, 395D14A81B90612D00658680 /* Info.plist */, + 196512972417A743004B77AF /* AutoregisterCell.swift */, ); path = DataSource; sourceTree = ""; @@ -540,6 +543,7 @@ D8D61BF021E4E11300937D1C /* SeparatorCustomViewViewModel.swift in Sources */, 398248CB1E5CA74A00F802D1 /* UITableViewExtensions.swift in Sources */, 398248CD1E5CA9C600F802D1 /* DataSource+UITableViewDelegate.swift in Sources */, + 196512982417A743004B77AF /* AutoregisterCell.swift in Sources */, 398248C51E5C7A5600F802D1 /* CellDescriptor.swift in Sources */, 4CA65F60214F952E004F2F19 /* UIView+AutoLayout.swift in Sources */, 398248C31E5C7A4000F802D1 /* Section.swift in Sources */, diff --git a/DataSource/AutoregisterCell.swift b/DataSource/AutoregisterCell.swift new file mode 100644 index 0000000..aeab096 --- /dev/null +++ b/DataSource/AutoregisterCell.swift @@ -0,0 +1,12 @@ +// +// AutoregisterCell.swift +// DataSource +// +// Created by Stefan Wieland on 10.03.20. +// Copyright © 2020 aaa - all about apps GmbH. All rights reserved. +// + +import Foundation + +protocol AutoregisterCell { +} diff --git a/DataSource/DataSource+UITableViewDataSource.swift b/DataSource/DataSource+UITableViewDataSource.swift index 9d337e4..0cd0469 100644 --- a/DataSource/DataSource+UITableViewDataSource.swift +++ b/DataSource/DataSource+UITableViewDataSource.swift @@ -32,7 +32,7 @@ extension DataSource: UITableViewDataSource { if bundle.path(forResource: cellIdentifier, ofType: "nib") != nil { tableView.registerNib(cellIdentifier, bundle: bundle) reuseIdentifiers.insert(cellIdentifier) - } else if cellIdentifier == SeparatorLineCell.cellIdentifier { + } else if cellDescriptor.cellClass is AutoregisterCell.Type { tableView.register(cellDescriptor.cellClass, forCellReuseIdentifier: cellIdentifier) reuseIdentifiers.insert(cellIdentifier) } diff --git a/DataSource/SeparatedSection/SeparatorLineCell.swift b/DataSource/SeparatedSection/SeparatorLineCell.swift index f73ca9f..47a4fcb 100644 --- a/DataSource/SeparatedSection/SeparatorLineCell.swift +++ b/DataSource/SeparatedSection/SeparatorLineCell.swift @@ -36,7 +36,7 @@ public struct SeparatorStyle: Equatable { } -class SeparatorLineCell: UITableViewCell { +class SeparatorLineCell: UITableViewCell, AutoregisterCell { var separator: UIView! var leftInsetConstraint: NSLayoutConstraint! diff --git a/Example/ViewControllers/StartViewController.swift b/Example/ViewControllers/StartViewController.swift index 58053eb..e2c41a3 100644 --- a/Example/ViewControllers/StartViewController.swift +++ b/Example/ViewControllers/StartViewController.swift @@ -30,7 +30,7 @@ class StartViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() - + tableView.separatorStyle = .none tableView.dataSource = dataSource tableView.delegate = dataSource var items = [ @@ -42,12 +42,23 @@ class StartViewController: UITableViewController { Example(title: "Custom separators", segue: "showSeparatedSection"), ] + let separatorItems = [ + Example(title: "Random Persons", segue: "showRandomPersons"), + Example(title: "Form", segue: "showForm"), + ] + if #available(iOS 11, *) { items.append(Example(title: "Swipe Actions", segue: "showSwipeExample")) } dataSource.sections = [ - Section(items: items) + Section(items: items), + SeparatedSection(items: separatorItems, styleConfigureClosure: { transition -> SeparatorStyle? in + let leftInset: CGFloat = transition.isLast ? 0 : 20 + return SeparatorStyle(edgeEnsets: UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: -20), + color: UIColor.blue, + height: 1.0) + }) ] dataSource.reloadData(tableView, animated: false) From 7712e64d2a49ee49ecc50f9fb1cda79b9362ba0e Mon Sep 17 00:00:00 2001 From: mbuchetics Date: Thu, 12 Mar 2020 21:14:23 +0100 Subject: [PATCH 11/31] renamed AutoRegisterCell --- DataSource.xcodeproj/project.pbxproj | 8 ++++---- .../{AutoregisterCell.swift => AutoRegisterCell.swift} | 2 +- DataSource/DataSource+UITableViewDataSource.swift | 2 +- DataSource/SeparatedSection/SeparatorLineCell.swift | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) rename DataSource/{AutoregisterCell.swift => AutoRegisterCell.swift} (86%) diff --git a/DataSource.xcodeproj/project.pbxproj b/DataSource.xcodeproj/project.pbxproj index 9f52f45..96028c4 100644 --- a/DataSource.xcodeproj/project.pbxproj +++ b/DataSource.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 196512982417A743004B77AF /* AutoregisterCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 196512972417A743004B77AF /* AutoregisterCell.swift */; }; + 196512982417A743004B77AF /* AutoRegisterCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 196512972417A743004B77AF /* AutoRegisterCell.swift */; }; 390CAF1C1E79AA560024E3E6 /* SectionDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390CAF1B1E79AA560024E3E6 /* SectionDescriptor.swift */; }; 395D14801B90610A00658680 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 395D147F1B90610A00658680 /* AppDelegate.swift */; }; 395D14871B90610A00658680 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 395D14861B90610A00658680 /* Assets.xcassets */; }; @@ -104,7 +104,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 196512972417A743004B77AF /* AutoregisterCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoregisterCell.swift; sourceTree = ""; }; + 196512972417A743004B77AF /* AutoRegisterCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoRegisterCell.swift; sourceTree = ""; }; 390CAF1B1E79AA560024E3E6 /* SectionDescriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionDescriptor.swift; sourceTree = ""; }; 395D147C1B90610A00658680 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 395D147F1B90610A00658680 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -239,7 +239,7 @@ 4C6076D921490B34002E8BD1 /* SeparatedSection */, 395D14A61B90612D00658680 /* DataSource.h */, 395D14A81B90612D00658680 /* Info.plist */, - 196512972417A743004B77AF /* AutoregisterCell.swift */, + 196512972417A743004B77AF /* AutoRegisterCell.swift */, ); path = DataSource; sourceTree = ""; @@ -543,7 +543,7 @@ D8D61BF021E4E11300937D1C /* SeparatorCustomViewViewModel.swift in Sources */, 398248CB1E5CA74A00F802D1 /* UITableViewExtensions.swift in Sources */, 398248CD1E5CA9C600F802D1 /* DataSource+UITableViewDelegate.swift in Sources */, - 196512982417A743004B77AF /* AutoregisterCell.swift in Sources */, + 196512982417A743004B77AF /* AutoRegisterCell.swift in Sources */, 398248C51E5C7A5600F802D1 /* CellDescriptor.swift in Sources */, 4CA65F60214F952E004F2F19 /* UIView+AutoLayout.swift in Sources */, 398248C31E5C7A4000F802D1 /* Section.swift in Sources */, diff --git a/DataSource/AutoregisterCell.swift b/DataSource/AutoRegisterCell.swift similarity index 86% rename from DataSource/AutoregisterCell.swift rename to DataSource/AutoRegisterCell.swift index aeab096..74352b1 100644 --- a/DataSource/AutoregisterCell.swift +++ b/DataSource/AutoRegisterCell.swift @@ -8,5 +8,5 @@ import Foundation -protocol AutoregisterCell { +protocol AutoRegisterCell { } diff --git a/DataSource/DataSource+UITableViewDataSource.swift b/DataSource/DataSource+UITableViewDataSource.swift index 0cd0469..161e481 100644 --- a/DataSource/DataSource+UITableViewDataSource.swift +++ b/DataSource/DataSource+UITableViewDataSource.swift @@ -32,7 +32,7 @@ extension DataSource: UITableViewDataSource { if bundle.path(forResource: cellIdentifier, ofType: "nib") != nil { tableView.registerNib(cellIdentifier, bundle: bundle) reuseIdentifiers.insert(cellIdentifier) - } else if cellDescriptor.cellClass is AutoregisterCell.Type { + } else if cellDescriptor.cellClass is AutoRegisterCell.Type { tableView.register(cellDescriptor.cellClass, forCellReuseIdentifier: cellIdentifier) reuseIdentifiers.insert(cellIdentifier) } diff --git a/DataSource/SeparatedSection/SeparatorLineCell.swift b/DataSource/SeparatedSection/SeparatorLineCell.swift index 47a4fcb..c774ee4 100644 --- a/DataSource/SeparatedSection/SeparatorLineCell.swift +++ b/DataSource/SeparatedSection/SeparatorLineCell.swift @@ -36,7 +36,7 @@ public struct SeparatorStyle: Equatable { } -class SeparatorLineCell: UITableViewCell, AutoregisterCell { +class SeparatorLineCell: UITableViewCell, AutoRegisterCell { var separator: UIView! var leftInsetConstraint: NSLayoutConstraint! From 7dbf5f9b6c1c45a73542b81e5baddef499490574 Mon Sep 17 00:00:00 2001 From: mbuchetics Date: Fri, 13 Mar 2020 15:58:14 +0100 Subject: [PATCH 12/31] AutoRegisterCell protocol should be public --- DataSource/AutoRegisterCell.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DataSource/AutoRegisterCell.swift b/DataSource/AutoRegisterCell.swift index 74352b1..fa94710 100644 --- a/DataSource/AutoRegisterCell.swift +++ b/DataSource/AutoRegisterCell.swift @@ -8,5 +8,5 @@ import Foundation -protocol AutoRegisterCell { +public protocol AutoRegisterCell { } From 9d1e7b299024f55b65d2c801054cf5def6f927b7 Mon Sep 17 00:00:00 2001 From: Michael Wagner Date: Fri, 20 Mar 2020 09:21:23 +0100 Subject: [PATCH 13/31] Fix - TrailingSwipeAction + SeparatedSection (index out of range bug) (#29) * fix: Solve issue with using SeparatedSection together with TrailingSwipeAction * chore: Increase the Example target version to 11 and add an example code for trailingSwipeAction together with separatedSections --- DataSource.xcodeproj/project.pbxproj | 2 ++ DataSource/DataSource+UITableViewDelegate.swift | 4 ++-- .../SeparatedSectionViewController.swift | 13 ++++++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/DataSource.xcodeproj/project.pbxproj b/DataSource.xcodeproj/project.pbxproj index 96028c4..bbb7f76 100644 --- a/DataSource.xcodeproj/project.pbxproj +++ b/DataSource.xcodeproj/project.pbxproj @@ -721,6 +721,7 @@ "$(PROJECT_DIR)/Carthage/Build/iOS", ); INFOPLIST_FILE = Example/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.buchetics.DataSourceExample; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -742,6 +743,7 @@ "$(PROJECT_DIR)/Carthage/Build/iOS", ); INFOPLIST_FILE = Example/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.buchetics.DataSourceExample; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/DataSource/DataSource+UITableViewDelegate.swift b/DataSource/DataSource+UITableViewDelegate.swift index 9a743ad..9291a76 100644 --- a/DataSource/DataSource+UITableViewDelegate.swift +++ b/DataSource/DataSource+UITableViewDelegate.swift @@ -474,7 +474,7 @@ extension DataSource { public func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { let cellDescriptor = self.cellDescriptor(at: indexPath) as? CellDescriptorTypeiOS11 if let closure = cellDescriptor?.trailingSwipeActionsClosure { - return closure(row(at: indexPath), indexPath) + return closure(visibleRow(at: indexPath), indexPath) } else { return fallbackDelegate?.tableView?(tableView, trailingSwipeActionsConfigurationForRowAt: indexPath) } @@ -483,7 +483,7 @@ extension DataSource { public func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { let cellDescriptor = self.cellDescriptor(at: indexPath) as? CellDescriptorTypeiOS11 if let closure = cellDescriptor?.leadingSwipeActionsClosure { - return closure(row(at: indexPath), indexPath) + return closure(visibleRow(at: indexPath), indexPath) } else { return fallbackDelegate?.tableView?(tableView, leadingSwipeActionsConfigurationForRowAt: indexPath) } diff --git a/Example/ViewControllers/SeparatedSectionViewController.swift b/Example/ViewControllers/SeparatedSectionViewController.swift index b74bac3..29c1e6a 100644 --- a/Example/ViewControllers/SeparatedSectionViewController.swift +++ b/Example/ViewControllers/SeparatedSectionViewController.swift @@ -21,7 +21,18 @@ class SeparatedSectionViewController: UIViewController { CellDescriptor() .configure { (item, cell, indexPath) in cell.textLabel?.text = item.text - }, + } + .canEdit { [weak self] (_, _) -> Bool in + return true + } + .trailingSwipeAction { [weak self] (_, _) -> UISwipeActionsConfiguration? in + return UISwipeActionsConfiguration(actions: [ + UIContextualAction(style: .destructive, title: "TestAction", handler: { [weak self] (_, _, callback) in + + callback(true) + }) + ]) + }, CellDescriptor() .configure { (item, cell, indexPath) in cell.textLabel?.text = item.text From 7648464848fa6e8db3357c0f858471cce1167108 Mon Sep 17 00:00:00 2001 From: mbuchetics Date: Mon, 23 Mar 2020 12:21:06 +0100 Subject: [PATCH 14/31] added ExpandableCell example --- DataSource.xcodeproj/project.pbxproj | 12 +++ .../Storyboards/Base.lproj/Main.storyboard | 43 +++++++---- Example/ViewControllers/Cells/TextCell.swift | 15 ++++ Example/ViewControllers/Cells/TextCell.xib | 40 ++++++++++ .../ExpandableCellViewController.swift | 76 +++++++++++++++++++ .../ViewControllers/StartViewController.swift | 1 + 6 files changed, 174 insertions(+), 13 deletions(-) create mode 100644 Example/ViewControllers/Cells/TextCell.swift create mode 100644 Example/ViewControllers/Cells/TextCell.xib create mode 100644 Example/ViewControllers/ExpandableCellViewController.swift diff --git a/DataSource.xcodeproj/project.pbxproj b/DataSource.xcodeproj/project.pbxproj index 96028c4..4470e22 100644 --- a/DataSource.xcodeproj/project.pbxproj +++ b/DataSource.xcodeproj/project.pbxproj @@ -8,6 +8,9 @@ /* Begin PBXBuildFile section */ 196512982417A743004B77AF /* AutoRegisterCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 196512972417A743004B77AF /* AutoRegisterCell.swift */; }; + 3903C1832428C7410094EF50 /* ExpandableCellViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3903C1822428C7410094EF50 /* ExpandableCellViewController.swift */; }; + 3903C1852428C7DE0094EF50 /* TextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3903C1842428C7DE0094EF50 /* TextCell.swift */; }; + 3903C1872428C7E80094EF50 /* TextCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3903C1862428C7E80094EF50 /* TextCell.xib */; }; 390CAF1C1E79AA560024E3E6 /* SectionDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390CAF1B1E79AA560024E3E6 /* SectionDescriptor.swift */; }; 395D14801B90610A00658680 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 395D147F1B90610A00658680 /* AppDelegate.swift */; }; 395D14871B90610A00658680 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 395D14861B90610A00658680 /* Assets.xcassets */; }; @@ -105,6 +108,9 @@ /* Begin PBXFileReference section */ 196512972417A743004B77AF /* AutoRegisterCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoRegisterCell.swift; sourceTree = ""; }; + 3903C1822428C7410094EF50 /* ExpandableCellViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandableCellViewController.swift; sourceTree = ""; }; + 3903C1842428C7DE0094EF50 /* TextCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextCell.swift; sourceTree = ""; }; + 3903C1862428C7E80094EF50 /* TextCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TextCell.xib; sourceTree = ""; }; 390CAF1B1E79AA560024E3E6 /* SectionDescriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionDescriptor.swift; sourceTree = ""; }; 395D147C1B90610A00658680 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 395D147F1B90610A00658680 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -254,6 +260,7 @@ 396E02DF20A1B0090072291F /* SwipeToDeleteViewController.swift */, 5C61C91720AF0AB0003A08B8 /* SwipeActionViewController.swift */, 4C6076DE21490C80002E8BD1 /* SeparatedSectionViewController.swift */, + 3903C1822428C7410094EF50 /* ExpandableCellViewController.swift */, ); name = Examples; sourceTree = ""; @@ -312,6 +319,8 @@ 39E9E3AB1E659D6C00A3C300 /* TextFieldCell.swift */, 39E9E3A61E6566AD00A3C300 /* TitleCell.swift */, 39E9E3A71E6566AD00A3C300 /* TitleCell.xib */, + 3903C1842428C7DE0094EF50 /* TextCell.swift */, + 3903C1862428C7E80094EF50 /* TextCell.xib */, ); path = Cells; sourceTree = ""; @@ -473,6 +482,7 @@ files = ( 398248AC1E5C6DDC00F802D1 /* LaunchScreen.storyboard in Resources */, 395D14871B90610A00658680 /* Assets.xcassets in Resources */, + 3903C1872428C7E80094EF50 /* TextCell.xib in Resources */, 39E9E3AA1E6566AD00A3C300 /* TitleCell.xib in Resources */, 398248AD1E5C6DDC00F802D1 /* Main.storyboard in Resources */, ); @@ -521,9 +531,11 @@ 5C61C91820AF0AB0003A08B8 /* SwipeActionViewController.swift in Sources */, 3968B9141E7C103400EE876F /* LazyRowsViewController.swift in Sources */, 39913AB81E61BBCC00623635 /* SwiftRandom.swift in Sources */, + 3903C1852428C7DE0094EF50 /* TextCell.swift in Sources */, 396E02E020A1B0090072291F /* SwipeToDeleteViewController.swift in Sources */, 3968B9161E7C261600EE876F /* UILabel+Animation.swift in Sources */, 39E9E3AE1E659D7D00A3C300 /* SwitchCell.swift in Sources */, + 3903C1832428C7410094EF50 /* ExpandableCellViewController.swift in Sources */, 39E9E3A91E6566AD00A3C300 /* TitleCell.swift in Sources */, 3968B9111E7C103400EE876F /* RandomPersonsViewController.swift in Sources */, 395D14801B90610A00658680 /* AppDelegate.swift in Sources */, diff --git a/Example/Storyboards/Base.lproj/Main.storyboard b/Example/Storyboards/Base.lproj/Main.storyboard index 2a2cd06..69eed35 100644 --- a/Example/Storyboards/Base.lproj/Main.storyboard +++ b/Example/Storyboards/Base.lproj/Main.storyboard @@ -1,11 +1,9 @@ - - - - + + - + @@ -15,7 +13,7 @@ - + @@ -49,6 +47,7 @@ + @@ -112,7 +111,7 @@ - + @@ -212,12 +211,11 @@ - + - - + @@ -239,17 +237,17 @@ - + - + @@ -345,5 +343,24 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Example/ViewControllers/Cells/TextCell.swift b/Example/ViewControllers/Cells/TextCell.swift new file mode 100644 index 0000000..1cf480b --- /dev/null +++ b/Example/ViewControllers/Cells/TextCell.swift @@ -0,0 +1,15 @@ +// +// TextCell.swift +// Example +// +// Created by Matthias Buchetics on 23.03.20. +// Copyright © 2020 aaa - all about apps GmbH. All rights reserved. +// + +import UIKit + +class TextCell: UITableViewCell { + + @IBOutlet weak var descriptionLabel: UILabel! + +} diff --git a/Example/ViewControllers/Cells/TextCell.xib b/Example/ViewControllers/Cells/TextCell.xib new file mode 100644 index 0000000..dc8d5e5 --- /dev/null +++ b/Example/ViewControllers/Cells/TextCell.xib @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/ViewControllers/ExpandableCellViewController.swift b/Example/ViewControllers/ExpandableCellViewController.swift new file mode 100644 index 0000000..622de53 --- /dev/null +++ b/Example/ViewControllers/ExpandableCellViewController.swift @@ -0,0 +1,76 @@ +// +// ExpandableCellViewController.swift +// Example +// +// Created by Matthias Buchetics on 23.03.20. +// Copyright © 2020 aaa - all about apps GmbH. All rights reserved. +// + +import UIKit +import DataSource + +class ExpandableItem: Diffable { + + let id = UUID().uuidString + let text: String + var isExpanded: Bool = false + + init(text: String) { + self.text = text + } + + var diffIdentifier: String { + return id + } + + func isEqualToDiffable(_ other: Diffable?) -> Bool { + return false + } +} + +// MARK: - View Controller + +class ExpandableCellViewController: UITableViewController { + + lazy var dataSource: DataSource = { + DataSource( + cellDescriptors: [ + CellDescriptor() + .configure { (item, cell, indexPath) in + cell.descriptionLabel?.text = item.text + cell.descriptionLabel.numberOfLines = item.isExpanded ? 0 : 3 + } + .didSelect { [weak self] (item, _) -> SelectionResult in + self?.toggleItem(item) + return .deselect + } + .height { (item, _) -> CGFloat in + return UITableView.automaticDimension + //return item.isExpanded ? UITableView.automaticDimension : 80.0 + } + ]) + }() + + override func viewDidLoad() { + super.viewDidLoad() + + tableView.dataSource = dataSource + tableView.delegate = dataSource + + dataSource.sections = createSections() + dataSource.reloadData(tableView, animated: false) + } + + func toggleItem(_ item: ExpandableItem) { + item.isExpanded.toggle() + dataSource.reloadData(tableView, animated: true) + } + + func createSections() -> [Section] { + return [Section(items: items)] + } + + var items = [ + ExpandableItem(text: "Morbi accumsan fermentum posuere vulputate iaculis ac nulla rutrum cum tempus massa vel, erat elit ipsum justo ligula enim luctus conubia est torquent. Inceptos fusce convallis ultricies platea nibh sapien montes per id, eleifend mattis quam dictum dignissim potenti fringilla semper, cubilia nostra eros faucibus neque tortor tempor posuere. Class neque lacinia vitae integer et mi libero ullamcorper fames tortor nullam nulla cursus, placerat orci tristique ipsum sagittis vulputate morbi euismod molestie volutpat cum. Amet condimentum vitae fames vivamus sapien sociis in ligula eget non lectus, pretium libero dis imperdiet hendrerit diam aenean urna sed."), + ExpandableItem(text: "Ipsum ligula in ac sociis fringilla penatibus scelerisque mi himenaeos habitant commodo, phasellus fusce rutrum consectetur non duis sagittis elit platea. Ante molestie ac est tempus magna volutpat vulputate nisl tellus, posuere lorem potenti taciti facilisi venenatis lacinia sodales. Venenatis lacinia in purus commodo penatibus mollis, id feugiat ornare torquent nascetur lacus, nulla inceptos varius cras accumsan.")] +} diff --git a/Example/ViewControllers/StartViewController.swift b/Example/ViewControllers/StartViewController.swift index e2c41a3..2950738 100644 --- a/Example/ViewControllers/StartViewController.swift +++ b/Example/ViewControllers/StartViewController.swift @@ -40,6 +40,7 @@ class StartViewController: UITableViewController { Example(title: "Diff & Update", segue: "showDiff"), Example(title: "Swipe To Delete", segue: "showSwipeToDelete"), Example(title: "Custom separators", segue: "showSeparatedSection"), + Example(title: "Expandable Cell", segue: "showExpandableCell"), ] let separatorItems = [ From 3da20f2b059351ac09e0a506b3bc5478c534720f Mon Sep 17 00:00:00 2001 From: mbuchetics Date: Fri, 27 Mar 2020 10:33:03 +0100 Subject: [PATCH 15/31] fixed Xcode 11.4 warning --- Cartfile.resolved | 2 +- DataSource/UITableViewExtensions.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 9350cdf..28f8b96 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1 @@ -github "tonyarnold/Differ" "1.4.0" +github "tonyarnold/Differ" "1.4.4" diff --git a/DataSource/UITableViewExtensions.swift b/DataSource/UITableViewExtensions.swift index 2301e6c..3858968 100644 --- a/DataSource/UITableViewExtensions.swift +++ b/DataSource/UITableViewExtensions.swift @@ -41,8 +41,8 @@ struct Batch { sectionDeletions.insert(at) case let .insertSection(at): sectionInsertions.insert(at) - case let .moveSection(move): - sectionMoves.append((move.from, move.to)) + case let .moveSection(from, to): + sectionMoves.append((from, to)) } } From 1653ab2012d0892fe540c66e451305b5d5c6d5fa Mon Sep 17 00:00:00 2001 From: Oliver Ian Krakora Date: Mon, 17 Aug 2020 13:13:57 +0200 Subject: [PATCH 16/31] disabled accessibility for SeparatorLineCell --- DataSource/SeparatedSection/SeparatorLineCell.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/DataSource/SeparatedSection/SeparatorLineCell.swift b/DataSource/SeparatedSection/SeparatorLineCell.swift index c774ee4..3173d8f 100644 --- a/DataSource/SeparatedSection/SeparatorLineCell.swift +++ b/DataSource/SeparatedSection/SeparatorLineCell.swift @@ -56,7 +56,12 @@ class SeparatorLineCell: UITableViewCell, AutoRegisterCell { customInit() } + override func accessibilityElementCount() -> Int { + return 0 + } + func customInit() { + isAccessibilityElement = false selectionStyle = .none backgroundColor = .clear From 9a75e922a6e85937815b6810538d68c2991a4d3c Mon Sep 17 00:00:00 2001 From: Matthias Buchetics Date: Wed, 9 Sep 2020 09:04:56 +0200 Subject: [PATCH 17/31] added Differ via SPM, bumped version --- Cartfile | 1 - DataSource.xcodeproj/project.pbxproj | 117 +++++++++++------- .../contents.xcworkspacedata | 2 +- .../xcshareddata/swiftpm/Package.resolved | 16 +++ DataSource/Info.plist | 4 +- Example/Info.plist | 4 +- 6 files changed, 91 insertions(+), 53 deletions(-) delete mode 100644 Cartfile create mode 100644 DataSource.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/Cartfile b/Cartfile deleted file mode 100644 index 83454b0..0000000 --- a/Cartfile +++ /dev/null @@ -1 +0,0 @@ -github "tonyarnold/Differ" ~> 1.4 diff --git a/DataSource.xcodeproj/project.pbxproj b/DataSource.xcodeproj/project.pbxproj index 874137f..ab73915 100644 --- a/DataSource.xcodeproj/project.pbxproj +++ b/DataSource.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -40,8 +40,6 @@ 39A848691E5E1D9600D7DBC2 /* DataSource.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 395D14A41B90612D00658680 /* DataSource.framework */; }; 39A848751E5E2DEC00D7DBC2 /* DataSource.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 395D14A41B90612D00658680 /* DataSource.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 39B621801E6D757000BE18EE /* DataSource+UITableViewDataSourcePrefetching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B6217F1E6D757000BE18EE /* DataSource+UITableViewDataSourcePrefetching.swift */; }; - 39D22FF7221C403C00C12A01 /* Differ.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39D22FF6221C403C00C12A01 /* Differ.framework */; }; - 39D22FF8221C40BB00C12A01 /* Differ.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39D22FF6221C403C00C12A01 /* Differ.framework */; }; 39E9E3A81E6566AD00A3C300 /* PersonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39E9E3A51E6566AD00A3C300 /* PersonCell.swift */; }; 39E9E3A91E6566AD00A3C300 /* TitleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39E9E3A61E6566AD00A3C300 /* TitleCell.swift */; }; 39E9E3AA1E6566AD00A3C300 /* TitleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 39E9E3A71E6566AD00A3C300 /* TitleCell.xib */; }; @@ -53,6 +51,7 @@ 4C6076DF21490C80002E8BD1 /* SeparatedSectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C6076DE21490C80002E8BD1 /* SeparatedSectionViewController.swift */; }; 4CA65F60214F952E004F2F19 /* UIView+AutoLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA65F5F214F952E004F2F19 /* UIView+AutoLayout.swift */; }; 5C61C91820AF0AB0003A08B8 /* SwipeActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C61C91720AF0AB0003A08B8 /* SwipeActionViewController.swift */; }; + 5FD722022500F36800835AA1 /* Differ in Frameworks */ = {isa = PBXBuildFile; productRef = 5FD722012500F36800835AA1 /* Differ */; }; D8D61BEE21E4DD3E00937D1C /* SeparatorLineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D61BED21E4DD3E00937D1C /* SeparatorLineViewModel.swift */; }; D8D61BF021E4E11300937D1C /* SeparatorCustomViewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D61BEF21E4E11300937D1C /* SeparatorCustomViewViewModel.swift */; }; /* End PBXBuildFile section */ @@ -130,7 +129,6 @@ 398248A91E5C6DDC00F802D1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 398248AB1E5C6DDC00F802D1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 398248AE1E5C6EFB00F802D1 /* DataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataSource.swift; sourceTree = ""; }; - 398248B31E5C704700F802D1 /* Cartfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Cartfile; sourceTree = ""; }; 398248B41E5C705000F802D1 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 398248C01E5C7A3500F802D1 /* Row.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Row.swift; sourceTree = ""; }; 398248C21E5C7A4000F802D1 /* Section.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = ""; }; @@ -145,7 +143,6 @@ 39A848661E5E1D9600D7DBC2 /* DataSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSourceTests.swift; sourceTree = ""; }; 39A848681E5E1D9600D7DBC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39B6217F1E6D757000BE18EE /* DataSource+UITableViewDataSourcePrefetching.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataSource+UITableViewDataSourcePrefetching.swift"; sourceTree = ""; }; - 39D22FF6221C403C00C12A01 /* Differ.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Differ.framework; path = Carthage/Build/iOS/Differ.framework; sourceTree = ""; }; 39E9E3A51E6566AD00A3C300 /* PersonCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonCell.swift; sourceTree = ""; }; 39E9E3A61E6566AD00A3C300 /* TitleCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TitleCell.swift; sourceTree = ""; }; 39E9E3A71E6566AD00A3C300 /* TitleCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TitleCell.xib; sourceTree = ""; }; @@ -166,7 +163,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 39D22FF8221C40BB00C12A01 /* Differ.framework in Frameworks */, 395D14B91B90612D00658680 /* DataSource.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -175,7 +171,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 39D22FF7221C403C00C12A01 /* Differ.framework in Frameworks */, + 5FD722022500F36800835AA1 /* Differ in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -193,12 +189,10 @@ 395D14731B90610A00658680 = { isa = PBXGroup; children = ( - 398248B31E5C704700F802D1 /* Cartfile */, 398248B41E5C705000F802D1 /* README.md */, 395D14A51B90612D00658680 /* DataSource */, 39A848651E5E1D9600D7DBC2 /* DataSourceTests */, 395D147E1B90610A00658680 /* Example */, - 398248B51E5C707A00F802D1 /* Frameworks */, 395D147D1B90610A00658680 /* Products */, ); sourceTree = ""; @@ -284,14 +278,6 @@ path = Storyboards; sourceTree = ""; }; - 398248B51E5C707A00F802D1 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 39D22FF6221C403C00C12A01 /* Differ.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; 39913AB61E61BBB900623635 /* Utilities */ = { isa = PBXGroup; children = ( @@ -366,7 +352,6 @@ 395D14791B90610A00658680 /* Frameworks */, 395D147A1B90610A00658680 /* Resources */, 395D14BE1B90612D00658680 /* Embed Frameworks */, - 398248B81E5C70B900F802D1 /* Carthage */, ); buildRules = ( ); @@ -392,6 +377,9 @@ dependencies = ( ); name = DataSource; + packageProductDependencies = ( + 5FD722012500F36800835AA1 /* Differ */, + ); productName = DataSource; productReference = 395D14A41B90612D00658680 /* DataSource.framework */; productType = "com.apple.product-type.framework"; @@ -464,6 +452,9 @@ Base, ); mainGroup = 395D14731B90610A00658680; + packageReferences = ( + 5FD722002500F36800835AA1 /* XCRemoteSwiftPackageReference "Differ" */, + ); productRefGroup = 395D147D1B90610A00658680 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -504,24 +495,6 @@ }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - 398248B81E5C70B900F802D1 /* Carthage */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "$(SRCROOT)/Carthage/Build/iOS/Differ.framework", - ); - name = Carthage; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/usr/local/bin/carthage copy-frameworks\n"; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ 395D14781B90610A00658680 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -727,6 +700,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 80; DEVELOPMENT_TEAM = M8F9QH57A6; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -734,7 +708,11 @@ ); INFOPLIST_FILE = Example/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 8.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.buchetics.DataSourceExample; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; @@ -749,6 +727,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 80; DEVELOPMENT_TEAM = M8F9QH57A6; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -756,12 +735,17 @@ ); INFOPLIST_FILE = Example/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 8.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.buchetics.DataSourceExample; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; }; name = Release; @@ -773,7 +757,7 @@ CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 80; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = M8F9QH57A6; DYLIB_COMPATIBILITY_VERSION = 1; @@ -785,7 +769,12 @@ ); INFOPLIST_FILE = DataSource/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 8.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.buchetics.DataSource; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -805,7 +794,7 @@ CODE_SIGN_IDENTITY = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 80; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = M8F9QH57A6; DYLIB_COMPATIBILITY_VERSION = 1; @@ -817,12 +806,18 @@ ); INFOPLIST_FILE = DataSource/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 8.1.0; PRODUCT_BUNDLE_IDENTIFIER = com.buchetics.DataSource; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; @@ -843,7 +838,11 @@ ); INFOPLIST_FILE = DataSourceTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.2; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.buchetics.DataSourceTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; @@ -865,10 +864,15 @@ ); INFOPLIST_FILE = DataSourceTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.2; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); PRODUCT_BUNDLE_IDENTIFIER = com.buchetics.DataSourceTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; }; @@ -914,6 +918,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 5FD722002500F36800835AA1 /* XCRemoteSwiftPackageReference "Differ" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/tonyarnold/Differ"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.4.5; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 5FD722012500F36800835AA1 /* Differ */ = { + isa = XCSwiftPackageProductDependency; + package = 5FD722002500F36800835AA1 /* XCRemoteSwiftPackageReference "Differ" */; + productName = Differ; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 395D14741B90610A00658680 /* Project object */; } diff --git a/DataSource.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/DataSource.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 106019c..919434a 100644 --- a/DataSource.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/DataSource.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/DataSource.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DataSource.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..4f1abe8 --- /dev/null +++ b/DataSource.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "Differ", + "repositoryURL": "https://github.com/tonyarnold/Differ", + "state": { + "branch": null, + "revision": "4c3eb4d76ad5c14075829397a5741b0badb08dce", + "version": "1.4.5" + } + } + ] + }, + "version": 1 +} diff --git a/DataSource/Info.plist b/DataSource/Info.plist index 9e6da47..ca23c84 100644 --- a/DataSource/Info.plist +++ b/DataSource/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 5.2.0 + $(MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion - 50 + $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/Example/Info.plist b/Example/Info.plist index b72b83f..64e2267 100644 --- a/Example/Info.plist +++ b/Example/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 5.2.0 + $(MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion - 50 + $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS UILaunchStoryboardName From faf6e4bef61654e53454828851842e8799284e1f Mon Sep 17 00:00:00 2001 From: Sandin Dulic Date: Thu, 10 Sep 2020 09:34:05 +0200 Subject: [PATCH 18/31] add context menu (#33) * added support for context menus * created example for the context menu Co-authored-by: Sandin Co-authored-by: Matthias Buchetics --- DataSource.xcodeproj/project.pbxproj | 4 + DataSource/CellDescriptor.swift | 241 ++++++++++-------- .../DataSource+UITableViewDelegate.swift | 65 +++-- DataSource/DataSource.swift | 161 +++++++----- .../Storyboards/Base.lproj/Main.storyboard | 28 +- .../ContextMenuViewController.swift | 67 +++++ .../Examples/DiffViewController.swift | 18 +- .../ViewControllers/StartViewController.swift | 1 + 8 files changed, 363 insertions(+), 222 deletions(-) create mode 100644 Example/ViewControllers/ContextMenuViewController.swift diff --git a/DataSource.xcodeproj/project.pbxproj b/DataSource.xcodeproj/project.pbxproj index ab73915..657dcd5 100644 --- a/DataSource.xcodeproj/project.pbxproj +++ b/DataSource.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ 4C6076DF21490C80002E8BD1 /* SeparatedSectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C6076DE21490C80002E8BD1 /* SeparatedSectionViewController.swift */; }; 4CA65F60214F952E004F2F19 /* UIView+AutoLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA65F5F214F952E004F2F19 /* UIView+AutoLayout.swift */; }; 5C61C91820AF0AB0003A08B8 /* SwipeActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C61C91720AF0AB0003A08B8 /* SwipeActionViewController.swift */; }; + 5F91D0C625091B8300CF5053 /* ContextMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F91D0C525091B8300CF5053 /* ContextMenuViewController.swift */; }; 5FD722022500F36800835AA1 /* Differ in Frameworks */ = {isa = PBXBuildFile; productRef = 5FD722012500F36800835AA1 /* Differ */; }; D8D61BEE21E4DD3E00937D1C /* SeparatorLineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D61BED21E4DD3E00937D1C /* SeparatorLineViewModel.swift */; }; D8D61BF021E4E11300937D1C /* SeparatorCustomViewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D61BEF21E4E11300937D1C /* SeparatorCustomViewViewModel.swift */; }; @@ -154,6 +155,7 @@ 4C6076DE21490C80002E8BD1 /* SeparatedSectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatedSectionViewController.swift; sourceTree = ""; }; 4CA65F5F214F952E004F2F19 /* UIView+AutoLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+AutoLayout.swift"; sourceTree = ""; }; 5C61C91720AF0AB0003A08B8 /* SwipeActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeActionViewController.swift; sourceTree = ""; }; + 5F91D0C525091B8300CF5053 /* ContextMenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuViewController.swift; sourceTree = ""; }; D8D61BED21E4DD3E00937D1C /* SeparatorLineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorLineViewModel.swift; sourceTree = ""; }; D8D61BEF21E4E11300937D1C /* SeparatorCustomViewViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorCustomViewViewModel.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -255,6 +257,7 @@ 5C61C91720AF0AB0003A08B8 /* SwipeActionViewController.swift */, 4C6076DE21490C80002E8BD1 /* SeparatedSectionViewController.swift */, 3903C1822428C7410094EF50 /* ExpandableCellViewController.swift */, + 5F91D0C525091B8300CF5053 /* ContextMenuViewController.swift */, ); name = Examples; sourceTree = ""; @@ -507,6 +510,7 @@ 3903C1852428C7DE0094EF50 /* TextCell.swift in Sources */, 396E02E020A1B0090072291F /* SwipeToDeleteViewController.swift in Sources */, 3968B9161E7C261600EE876F /* UILabel+Animation.swift in Sources */, + 5F91D0C625091B8300CF5053 /* ContextMenuViewController.swift in Sources */, 39E9E3AE1E659D7D00A3C300 /* SwitchCell.swift in Sources */, 3903C1832428C7410094EF50 /* ExpandableCellViewController.swift in Sources */, 39E9E3A91E6566AD00A3C300 /* TitleCell.swift in Sources */, diff --git a/DataSource/CellDescriptor.swift b/DataSource/CellDescriptor.swift index ed9c619..63440dc 100644 --- a/DataSource/CellDescriptor.swift +++ b/DataSource/CellDescriptor.swift @@ -9,24 +9,22 @@ import UIKit public enum SelectionResult { - case deselect case keepSelected } -// MARK - CellDescriptorType +// MARK: - CellDescriptorType public protocol CellDescriptorType { - var rowIdentifier: String { get } var cellIdentifier: String { get } var bundle: Bundle? { get } var cellClass: UITableViewCell.Type { get } - + // UITableViewDataSource var configureClosure: ((RowType, UITableViewCell, IndexPath) -> Void)? { get } - + var canEditClosure: ((RowType, IndexPath) -> Bool)? { get } var canMoveClosure: ((RowType, IndexPath) -> Bool)? { get } var commitEditingClosure: ((RowType, UITableViewCell.EditingStyle, IndexPath) -> Void)? { get } @@ -53,7 +51,7 @@ public protocol CellDescriptorType { var editActionsClosure: ((RowType, IndexPath) -> [UITableViewRowAction]?)? { get } var shouldIndentWhileEditingClosure: ((RowType, IndexPath) -> Bool)? { get } var willBeginEditingClosure: ((RowType, IndexPath) -> Void)? { get } - + var targetIndexPathForMoveClosure: ((RowType, (IndexPath, IndexPath)) -> IndexPath)? { get } var indentationLevelClosure: ((RowType, IndexPath) -> Int)? { get } var shouldShowMenuClosure: ((RowType, IndexPath) -> Bool)? { get } @@ -73,10 +71,14 @@ public protocol CellDescriptorTypeiOS11: CellDescriptorType { var trailingSwipeActionsClosure: ((RowType, IndexPath) -> UISwipeActionsConfiguration?)? { get } } -// MARK - CellDescriptor +@available(iOS 13.0, *) +public protocol CellDescriptorTypeiOS13: CellDescriptorType { + var configurationForMenuAtLocationClosure: ((RowType, IndexPath) -> UIContextMenuConfiguration?)? { get } +} + +// MARK: - CellDescriptor public class CellDescriptor: CellDescriptorType { - public let rowIdentifier: String public let cellIdentifier: String public let bundle: Bundle? @@ -104,7 +106,7 @@ public class CellDescriptor: CellDescriptorType { } return cell } - + // MARK: - UITableViewDataSource // MARK: configure @@ -112,7 +114,7 @@ public class CellDescriptor: CellDescriptorType { public private(set) var configureClosure: ((RowType, UITableViewCell, IndexPath) -> Void)? public func configure(_ closure: @escaping (Item, Cell, IndexPath) -> Void) -> CellDescriptor { - configureClosure = { [unowned self] (row, cell, indexPath) in + configureClosure = { [unowned self] row, cell, indexPath in closure(self.typedItem(row), self.typedCell(cell), indexPath) } return self @@ -123,14 +125,14 @@ public class CellDescriptor: CellDescriptorType { public private(set) var canEditClosure: ((RowType, IndexPath) -> Bool)? public func canEdit(_ closure: @escaping (Item, IndexPath) -> Bool) -> CellDescriptor { - canEditClosure = { [unowned self] (row, indexPath) in + canEditClosure = { [unowned self] row, indexPath in closure(self.typedItem(row), indexPath) } return self } public func canEdit(_ closure: @escaping () -> Bool) -> CellDescriptor { - canEditClosure = { (_, _) in + canEditClosure = { _, _ in closure() } return self @@ -141,14 +143,14 @@ public class CellDescriptor: CellDescriptorType { public private(set) var canMoveClosure: ((RowType, IndexPath) -> Bool)? public func canMove(_ closure: @escaping (Item, IndexPath) -> Bool) -> CellDescriptor { - canMoveClosure = { [unowned self] (row, indexPath) in + canMoveClosure = { [unowned self] row, indexPath in closure(self.typedItem(row), indexPath) } return self } public func canMove(_ closure: @escaping () -> Bool) -> CellDescriptor { - canMoveClosure = { (_, _) in + canMoveClosure = { _, _ in closure() } return self @@ -159,15 +161,15 @@ public class CellDescriptor: CellDescriptorType { public private(set) var heightClosure: ((RowType, IndexPath) -> CGFloat)? public func height(_ closure: @escaping (Item, IndexPath) -> CGFloat) -> CellDescriptor { - heightClosure = { [unowned self] (row, indexPath) in - return closure(self.typedItem(row), indexPath) + heightClosure = { [unowned self] row, indexPath in + closure(self.typedItem(row), indexPath) } return self } public func height(_ closure: @escaping () -> CGFloat) -> CellDescriptor { - heightClosure = { (_, _) in - return closure() + heightClosure = { _, _ in + closure() } return self } @@ -177,15 +179,15 @@ public class CellDescriptor: CellDescriptorType { public private(set) var estimatedHeightClosure: ((RowType, IndexPath) -> CGFloat)? public func estimatedHeight(_ closure: @escaping (Item, IndexPath) -> CGFloat) -> CellDescriptor { - estimatedHeightClosure = { [unowned self] (row, indexPath) in - return closure(self.typedItem(row), indexPath) + estimatedHeightClosure = { [unowned self] row, indexPath in + closure(self.typedItem(row), indexPath) } return self } public func estimatedHeight(_ closure: @escaping () -> CGFloat) -> CellDescriptor { - estimatedHeightClosure = { (_, _) in - return closure() + estimatedHeightClosure = { _, _ in + closure() } return self } @@ -195,8 +197,8 @@ public class CellDescriptor: CellDescriptorType { public private(set) var commitEditingClosure: ((RowType, UITableViewCell.EditingStyle, IndexPath) -> Void)? public func commitEditing(_ closure: @escaping (Item, UITableViewCell.EditingStyle, IndexPath) -> Void) -> CellDescriptor { - commitEditingClosure = { [unowned self] (row, editingStyle, indexPath) in - return closure(self.typedItem(row), editingStyle, indexPath) + commitEditingClosure = { [unowned self] row, editingStyle, indexPath in + closure(self.typedItem(row), editingStyle, indexPath) } return self } @@ -206,8 +208,8 @@ public class CellDescriptor: CellDescriptorType { public private(set) var moveRowClosure: ((RowType, (IndexPath, IndexPath)) -> Void)? public func moveRow(_ closure: @escaping (Item, (IndexPath, IndexPath)) -> Void) -> CellDescriptor { - moveRowClosure = { [unowned self] (row, indexPaths) in - return closure(self.typedItem(row), indexPaths) + moveRowClosure = { [unowned self] row, indexPaths in + closure(self.typedItem(row), indexPaths) } return self } @@ -219,15 +221,15 @@ public class CellDescriptor: CellDescriptorType { public private(set) var shouldHighlightClosure: ((RowType, IndexPath) -> Bool)? public func shouldHighlight(_ closure: @escaping (Item, IndexPath) -> Bool) -> CellDescriptor { - shouldHighlightClosure = { [unowned self] (row, indexPath) in - return closure(self.typedItem(row), indexPath) + shouldHighlightClosure = { [unowned self] row, indexPath in + closure(self.typedItem(row), indexPath) } return self } public func shouldHighlight(_ closure: @escaping () -> Bool) -> CellDescriptor { - shouldHighlightClosure = { (_, _) in - return closure() + shouldHighlightClosure = { _, _ in + closure() } return self } @@ -237,15 +239,15 @@ public class CellDescriptor: CellDescriptorType { public private(set) var didHighlightClosure: ((RowType, IndexPath) -> Void)? public func didHighlight(_ closure: @escaping (Item, IndexPath) -> Void) -> CellDescriptor { - didHighlightClosure = { [unowned self] (row, indexPath) in - return closure(self.typedItem(row), indexPath) + didHighlightClosure = { [unowned self] row, indexPath in + closure(self.typedItem(row), indexPath) } return self } public func didHighlight(_ closure: @escaping () -> Void) -> CellDescriptor { - didHighlightClosure = { (_, _) in - return closure() + didHighlightClosure = { _, _ in + closure() } return self } @@ -255,15 +257,15 @@ public class CellDescriptor: CellDescriptorType { public private(set) var didUnhighlightClosure: ((RowType, IndexPath) -> Void)? public func didUnhighlight(_ closure: @escaping (Item, IndexPath) -> Void) -> CellDescriptor { - didUnhighlightClosure = { [unowned self] (row, indexPath) in - return closure(self.typedItem(row), indexPath) + didUnhighlightClosure = { [unowned self] row, indexPath in + closure(self.typedItem(row), indexPath) } return self } public func didUnhighlight(_ closure: @escaping () -> Void) -> CellDescriptor { - didUnhighlightClosure = { (_, _) in - return closure() + didUnhighlightClosure = { _, _ in + closure() } return self } @@ -273,15 +275,15 @@ public class CellDescriptor: CellDescriptorType { public private(set) var willSelectClosure: ((RowType, IndexPath) -> IndexPath?)? public func willSelect(_ closure: @escaping (Item, IndexPath) -> IndexPath?) -> CellDescriptor { - willSelectClosure = { [unowned self] (row, indexPath) in - return closure(self.typedItem(row), indexPath) + willSelectClosure = { [unowned self] row, indexPath in + closure(self.typedItem(row), indexPath) } return self } public func willSelect(_ closure: @escaping () -> IndexPath?) -> CellDescriptor { - willSelectClosure = { (_, _) in - return closure() + willSelectClosure = { _, _ in + closure() } return self } @@ -291,15 +293,15 @@ public class CellDescriptor: CellDescriptorType { public private(set) var willDeselectClosure: ((RowType, IndexPath) -> IndexPath?)? public func willDeselect(_ closure: @escaping (Item, IndexPath) -> IndexPath?) -> CellDescriptor { - willDeselectClosure = { [unowned self] (row, indexPath) in - return closure(self.typedItem(row), indexPath) + willDeselectClosure = { [unowned self] row, indexPath in + closure(self.typedItem(row), indexPath) } return self } public func willDeselect(_ closure: @escaping () -> IndexPath?) -> CellDescriptor { - willDeselectClosure = { (_, _) in - return closure() + willDeselectClosure = { _, _ in + closure() } return self } @@ -309,15 +311,15 @@ public class CellDescriptor: CellDescriptorType { public private(set) var didSelectClosure: ((RowType, IndexPath) -> SelectionResult)? public func didSelect(_ closure: @escaping (Item, IndexPath) -> SelectionResult) -> CellDescriptor { - didSelectClosure = { [unowned self] (row, indexPath) in - return closure(self.typedItem(row), indexPath) + didSelectClosure = { [unowned self] row, indexPath in + closure(self.typedItem(row), indexPath) } return self } public func didSelect(_ closure: @escaping () -> SelectionResult) -> CellDescriptor { - didSelectClosure = { (_, _) in - return closure() + didSelectClosure = { _, _ in + closure() } return self } @@ -327,15 +329,15 @@ public class CellDescriptor: CellDescriptorType { public private(set) var didDeselectClosure: ((RowType, IndexPath) -> Void)? public func didDeselect(_ closure: @escaping (Item, IndexPath) -> Void) -> CellDescriptor { - didDeselectClosure = { [unowned self] (row, indexPath) in - return closure(self.typedItem(row), indexPath) + didDeselectClosure = { [unowned self] row, indexPath in + closure(self.typedItem(row), indexPath) } return self } public func didDeselect(_ closure: @escaping () -> Void) -> CellDescriptor { - didDeselectClosure = { (_, _) in - return closure() + didDeselectClosure = { _, _ in + closure() } return self } @@ -345,8 +347,8 @@ public class CellDescriptor: CellDescriptorType { public private(set) var willDisplayClosure: ((RowType, UITableViewCell, IndexPath) -> Void)? public func willDisplay(_ closure: @escaping (Item, Cell, IndexPath) -> Void) -> CellDescriptor { - willDisplayClosure = { [unowned self] (row, cell, indexPath) in - return closure(self.typedItem(row), self.typedCell(cell), indexPath) + willDisplayClosure = { [unowned self] row, cell, indexPath in + closure(self.typedItem(row), self.typedCell(cell), indexPath) } return self } @@ -356,15 +358,15 @@ public class CellDescriptor: CellDescriptorType { public private(set) var editingStyleClosure: ((RowType, IndexPath) -> UITableViewCell.EditingStyle)? public func editingStyle(_ closure: @escaping (Item, IndexPath) -> UITableViewCell.EditingStyle) -> CellDescriptor { - editingStyleClosure = { [unowned self] (row, indexPath) in - return closure(self.typedItem(row), indexPath) + editingStyleClosure = { [unowned self] row, indexPath in + closure(self.typedItem(row), indexPath) } return self } public func editingStyle(_ closure: @escaping () -> UITableViewCell.EditingStyle) -> CellDescriptor { - editingStyleClosure = { (_, _) in - return closure() + editingStyleClosure = { _, _ in + closure() } return self } @@ -374,15 +376,15 @@ public class CellDescriptor: CellDescriptorType { public private(set) var titleForDeleteConfirmationButtonClosure: ((RowType, IndexPath) -> String?)? public func titleForDeleteConfirmationButton(_ closure: @escaping (Item, IndexPath) -> String?) -> CellDescriptor { - titleForDeleteConfirmationButtonClosure = { [unowned self] (row, indexPath) in - return closure(self.typedItem(row), indexPath) + titleForDeleteConfirmationButtonClosure = { [unowned self] row, indexPath in + closure(self.typedItem(row), indexPath) } return self } public func titleForDeleteConfirmationButton(_ closure: @escaping () -> String?) -> CellDescriptor { - titleForDeleteConfirmationButtonClosure = { (_, _) in - return closure() + titleForDeleteConfirmationButtonClosure = { _, _ in + closure() } return self } @@ -392,20 +394,24 @@ public class CellDescriptor: CellDescriptorType { private var _leadingSwipeActionsClosure: ((RowType, IndexPath) -> Any?)? private var _trailingSwipeActionsClosure: ((RowType, IndexPath) -> Any?)? + // MARK: contextMenu + + public var _configurationForMenuAtLocationClosure: ((RowType, IndexPath) -> Any)? + // MARK: editActions public private(set) var editActionsClosure: ((RowType, IndexPath) -> [UITableViewRowAction]?)? public func editActions(_ closure: @escaping (Item, IndexPath) -> [UITableViewRowAction]?) -> CellDescriptor { - editActionsClosure = { [unowned self] (row, indexPath) in - return closure(self.typedItem(row), indexPath) + editActionsClosure = { [unowned self] row, indexPath in + closure(self.typedItem(row), indexPath) } return self } public func editActions(_ closure: @escaping () -> [UITableViewRowAction]?) -> CellDescriptor { - editActionsClosure = { (_, _) in - return closure() + editActionsClosure = { _, _ in + closure() } return self } @@ -415,15 +421,15 @@ public class CellDescriptor: CellDescriptorType { public private(set) var shouldIndentWhileEditingClosure: ((RowType, IndexPath) -> Bool)? public func shouldIndentWhileEditing(_ closure: @escaping (Item, IndexPath) -> Bool) -> CellDescriptor { - shouldIndentWhileEditingClosure = { [unowned self] (row, indexPath) in - return closure(self.typedItem(row), indexPath) + shouldIndentWhileEditingClosure = { [unowned self] row, indexPath in + closure(self.typedItem(row), indexPath) } return self } public func shouldIndentWhileEditing(_ closure: @escaping () -> Bool) -> CellDescriptor { - shouldIndentWhileEditingClosure = { (_, _) in - return closure() + shouldIndentWhileEditingClosure = { _, _ in + closure() } return self } @@ -433,14 +439,14 @@ public class CellDescriptor: CellDescriptorType { public private(set) var willBeginEditingClosure: ((RowType, IndexPath) -> Void)? public func willBeginEditing(_ closure: @escaping (Item, IndexPath) -> Void) -> CellDescriptor { - willBeginEditingClosure = { [unowned self] (row, indexPath) in + willBeginEditingClosure = { [unowned self] row, indexPath in closure(self.typedItem(row), indexPath) } return self } public func willBeginEditing(_ closure: @escaping () -> Void) -> CellDescriptor { - willBeginEditingClosure = { (_, _) in + willBeginEditingClosure = { _, _ in closure() } return self @@ -451,7 +457,7 @@ public class CellDescriptor: CellDescriptorType { public private(set) var targetIndexPathForMoveClosure: ((RowType, (IndexPath, IndexPath)) -> IndexPath)? public func targetIndexPathForMove(_ closure: @escaping (Item, (IndexPath, IndexPath)) -> IndexPath) -> CellDescriptor { - targetIndexPathForMoveClosure = { [unowned self] (row, indexPaths) in + targetIndexPathForMoveClosure = { [unowned self] row, indexPaths in closure(self.typedItem(row), indexPaths) } return self @@ -462,14 +468,14 @@ public class CellDescriptor: CellDescriptorType { public private(set) var indentationLevelClosure: ((RowType, IndexPath) -> Int)? public func indentationLevel(_ closure: @escaping (Item, IndexPath) -> Int) -> CellDescriptor { - indentationLevelClosure = { [unowned self] (row, indexPath) in + indentationLevelClosure = { [unowned self] row, indexPath in closure(self.typedItem(row), indexPath) } return self } public func indentationLevel(_ closure: @escaping () -> Int) -> CellDescriptor { - indentationLevelClosure = { (_, _) in + indentationLevelClosure = { _, _ in closure() } return self @@ -480,14 +486,14 @@ public class CellDescriptor: CellDescriptorType { public private(set) var shouldShowMenuClosure: ((RowType, IndexPath) -> Bool)? public func shouldShowMenu(_ closure: @escaping (Item, IndexPath) -> Bool) -> CellDescriptor { - shouldShowMenuClosure = { [unowned self] (row, indexPath) in + shouldShowMenuClosure = { [unowned self] row, indexPath in closure(self.typedItem(row), indexPath) } return self } public func shouldShowMenu(_ closure: @escaping () -> Bool) -> CellDescriptor { - shouldShowMenuClosure = { (_, _) in + shouldShowMenuClosure = { _, _ in closure() } return self @@ -498,14 +504,14 @@ public class CellDescriptor: CellDescriptorType { public private(set) var canPerformActionClosure: ((RowType, Selector, Any?, IndexPath) -> Bool)? public func canPerformAction(_ closure: @escaping (Item, Selector, Any?, IndexPath) -> Bool) -> CellDescriptor { - canPerformActionClosure = { [unowned self] (row, selector, sender, indexPath) in + canPerformActionClosure = { [unowned self] row, selector, sender, indexPath in closure(self.typedItem(row), selector, sender, indexPath) } return self } public func canPerformAction(_ closure: @escaping (Selector, Any?) -> Bool) -> CellDescriptor { - canPerformActionClosure = { (_, selector, sender, _) in + canPerformActionClosure = { _, selector, sender, _ in closure(selector, sender) } return self @@ -516,14 +522,14 @@ public class CellDescriptor: CellDescriptorType { public private(set) var performActionClosure: ((RowType, Selector, Any?, IndexPath) -> Void)? public func performAction(_ closure: @escaping (Item, Selector, Any?, IndexPath) -> Void) -> CellDescriptor { - performActionClosure = { [unowned self] (row, selector, sender, indexPath) in + performActionClosure = { [unowned self] row, selector, sender, indexPath in closure(self.typedItem(row), selector, sender, indexPath) } return self } public func performAction(_ closure: @escaping (Selector, Any?) -> Void) -> CellDescriptor { - performActionClosure = { (_, selector, sender, _) in + performActionClosure = { _, selector, sender, _ in closure(selector, sender) } return self @@ -534,14 +540,14 @@ public class CellDescriptor: CellDescriptorType { public private(set) var canFocusClosure: ((RowType, IndexPath) -> Bool)? public func canFocus(_ closure: @escaping (Item, IndexPath) -> Bool) -> CellDescriptor { - canFocusClosure = { [unowned self] (row, indexPath) in + canFocusClosure = { [unowned self] row, indexPath in closure(self.typedItem(row), indexPath) } return self } public func canFocus(_ closure: @escaping () -> Bool) -> CellDescriptor { - canFocusClosure = { (_, _) in + canFocusClosure = { _, _ in closure() } return self @@ -554,14 +560,14 @@ public class CellDescriptor: CellDescriptorType { public private(set) var isHiddenClosure: ((RowType, IndexPath) -> Bool)? public func isHidden(_ closure: @escaping (Item, IndexPath) -> Bool) -> CellDescriptor { - isHiddenClosure = { [unowned self] (row, indexPath) in + isHiddenClosure = { [unowned self] row, indexPath in closure(self.typedItem(row), indexPath) } return self } public func isHidden(_ closure: @escaping () -> Bool) -> CellDescriptor { - isHiddenClosure = { (_, _) in + isHiddenClosure = { _, _ in closure() } return self @@ -572,7 +578,7 @@ public class CellDescriptor: CellDescriptorType { public private(set) var updateClosure: ((RowType, UITableViewCell, IndexPath) -> Void)? public func update(_ closure: @escaping (Item, Cell, IndexPath) -> Void) -> CellDescriptor { - updateClosure = { [unowned self] (row, cell, indexPath) in + updateClosure = { [unowned self] row, cell, indexPath in closure(self.typedItem(row), self.typedCell(cell), indexPath) } return self @@ -581,41 +587,36 @@ public class CellDescriptor: CellDescriptorType { @available(iOS 11, *) extension CellDescriptor: CellDescriptorTypeiOS11 { - public var leadingSwipeActionsClosure: ((RowType, IndexPath) -> UISwipeActionsConfiguration?)? { - get { - if _leadingSwipeActionsClosure == nil { - return nil - } - - return { [weak self] (rowType, indexPath) in - return self?._leadingSwipeActionsClosure?(rowType, indexPath) as? UISwipeActionsConfiguration - } + if _leadingSwipeActionsClosure == nil { + return nil + } + + return { [weak self] rowType, indexPath in + self?._leadingSwipeActionsClosure?(rowType, indexPath) as? UISwipeActionsConfiguration } } public var trailingSwipeActionsClosure: ((RowType, IndexPath) -> UISwipeActionsConfiguration?)? { - get { - if _trailingSwipeActionsClosure == nil { - return nil - } - - return { [weak self] (rowType, indexPath) in - return self?._trailingSwipeActionsClosure?(rowType, indexPath) as? UISwipeActionsConfiguration - } + if _trailingSwipeActionsClosure == nil { + return nil + } + + return { [weak self] rowType, indexPath in + self?._trailingSwipeActionsClosure?(rowType, indexPath) as? UISwipeActionsConfiguration } } public func leadingSwipeAction(_ closure: @escaping ((Item, IndexPath) -> UISwipeActionsConfiguration?)) -> CellDescriptor { - _leadingSwipeActionsClosure = { [unowned self] (row, indexPath) in - return closure(self.typedItem(row), indexPath) + _leadingSwipeActionsClosure = { [unowned self] row, indexPath in + closure(self.typedItem(row), indexPath) } return self } - + public func trailingSwipeAction(_ closure: @escaping ((Item, IndexPath) -> UISwipeActionsConfiguration?)) -> CellDescriptor { - _trailingSwipeActionsClosure = { [unowned self] (row, indexPath) in - return closure(self.typedItem(row), indexPath) + _trailingSwipeActionsClosure = { [unowned self] row, indexPath in + closure(self.typedItem(row), indexPath) } return self } @@ -628,3 +629,23 @@ extension CellDescriptor: CellDescriptorTypeiOS11 { return _trailingSwipeActionsClosure != nil } } + +@available(iOS 13, *) +extension CellDescriptor: CellDescriptorTypeiOS13 { + public var configurationForMenuAtLocationClosure: ((RowType, IndexPath) -> UIContextMenuConfiguration?)? { + if _configurationForMenuAtLocationClosure == nil { + return nil + } + + return { [weak self] rowType, indexPath in + self?._configurationForMenuAtLocationClosure?(rowType, indexPath) as? UIContextMenuConfiguration + } + } + + public func configurationForMenuAtLocation(_ closure: @escaping ((Item, IndexPath) -> UIContextMenuConfiguration)) -> CellDescriptor { + _configurationForMenuAtLocationClosure = { [unowned self] row, indexPath in + closure(self.typedItem(row), indexPath) + } + return self + } +} diff --git a/DataSource/DataSource+UITableViewDelegate.swift b/DataSource/DataSource+UITableViewDelegate.swift index 9291a76..16b1eed 100644 --- a/DataSource/DataSource+UITableViewDelegate.swift +++ b/DataSource/DataSource+UITableViewDelegate.swift @@ -6,11 +6,10 @@ // Copyright © 2017 aaa - all about apps GmbH. All rights reserved. // -import UIKit import Differ +import UIKit extension DataSource: UITableViewDelegate { - // MARK: Highlighting public func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { @@ -42,7 +41,7 @@ extension DataSource: UITableViewDelegate { closure(visibleRow(at: indexPath), indexPath) return } - + fallbackDelegate?.tableView?(tableView, didUnhighlightRowAt: indexPath) } @@ -73,7 +72,6 @@ extension DataSource: UITableViewDelegate { public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let cellDescriptor = self.cellDescriptor(at: indexPath) - if let closure = cellDescriptor.didSelectClosure ?? didSelect { let selectionResult = closure(visibleRow(at: indexPath), indexPath) @@ -151,13 +149,13 @@ extension DataSource: UITableViewDelegate { return fallbackDelegate?.tableView?(tableView, heightForRowAt: indexPath) ?? tableView.rowHeight } - + public func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { let index = indexPath.section let section = visibleSection(at: index) - // Do not get the CellDescriptor if we are dealing with a LazySection because it would instaniate all the rows, - // i.e. estimatedHeightClosure will not be called for LazySections. Instead, use the estimatedHeight on the + // Do not get the CellDescriptor if we are dealing with a LazySection because it would instantiate all the rows, + // i.e. estimatedHeightClosure will not be called for LazySections. Instead, use the estimatedHeight on the // DataSource, the fallback delegate or a constant. if !(section is LazySectionType) { @@ -185,12 +183,12 @@ extension DataSource: UITableViewDelegate { public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { let sectionDescriptor = self.sectionDescriptor(at: section) - + if let closure = sectionDescriptor?.headerHeightClosure ?? sectionHeaderHeight { return closure(visibleSection(at: section), section).floatValue(for: tableView.style) } - if let result = fallbackDelegate?.tableView?(tableView, heightForHeaderInSection: section) { + if let result = fallbackDelegate?.tableView?(tableView, heightForHeaderInSection: section) { return result } @@ -259,7 +257,7 @@ extension DataSource: UITableViewDelegate { return } - fallbackDelegate?.tableView?(tableView, willDisplay:cell, forRowAt:indexPath) + fallbackDelegate?.tableView?(tableView, willDisplay: cell, forRowAt: indexPath) } public func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { @@ -270,7 +268,7 @@ extension DataSource: UITableViewDelegate { return } - fallbackDelegate?.tableView?(tableView, willDisplayHeaderView:view, forSection:section) + fallbackDelegate?.tableView?(tableView, willDisplayHeaderView: view, forSection: section) } public func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) { @@ -281,7 +279,7 @@ extension DataSource: UITableViewDelegate { return } - fallbackDelegate?.tableView?(tableView, willDisplayFooterView:view, forSection:section) + fallbackDelegate?.tableView?(tableView, willDisplayFooterView: view, forSection: section) } // the didEnd delegate methods are only supported on a "fallback" level as we may not have the row or section @@ -293,7 +291,7 @@ extension DataSource: UITableViewDelegate { return } - fallbackDelegate?.tableView?(tableView, didEndDisplaying:cell, forRowAt:indexPath) + fallbackDelegate?.tableView?(tableView, didEndDisplaying: cell, forRowAt: indexPath) } public func tableView(_ tableView: UITableView, didEndDisplayingHeaderView view: UIView, forSection section: Int) { @@ -302,7 +300,7 @@ extension DataSource: UITableViewDelegate { return } - fallbackDelegate?.tableView?(tableView, didEndDisplayingHeaderView:view, forSection:section) + fallbackDelegate?.tableView?(tableView, didEndDisplayingHeaderView: view, forSection: section) } public func tableView(_ tableView: UITableView, didEndDisplayingFooterView view: UIView, forSection section: Int) { @@ -311,7 +309,7 @@ extension DataSource: UITableViewDelegate { return } - fallbackDelegate?.tableView?(tableView, didEndDisplayingFooterView:view, forSection:section) + fallbackDelegate?.tableView?(tableView, didEndDisplayingFooterView: view, forSection: section) } // MARK: Editing @@ -323,7 +321,7 @@ extension DataSource: UITableViewDelegate { return closure(visibleRow(at: indexPath), indexPath) } - if let result = fallbackDelegate?.tableView?(tableView, editingStyleForRowAt:indexPath) { + if let result = fallbackDelegate?.tableView?(tableView, editingStyleForRowAt: indexPath) { return result } @@ -341,7 +339,7 @@ extension DataSource: UITableViewDelegate { return closure(visibleRow(at: indexPath), indexPath) } - return fallbackDelegate?.tableView?(tableView, titleForDeleteConfirmationButtonForRowAt:indexPath) + return fallbackDelegate?.tableView?(tableView, titleForDeleteConfirmationButtonForRowAt: indexPath) } public func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { @@ -351,7 +349,7 @@ extension DataSource: UITableViewDelegate { return closure(visibleRow(at: indexPath), indexPath) } - return fallbackDelegate?.tableView?(tableView, editActionsForRowAt:indexPath) + return fallbackDelegate?.tableView?(tableView, editActionsForRowAt: indexPath) } public func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool { @@ -361,7 +359,7 @@ extension DataSource: UITableViewDelegate { return closure(visibleRow(at: indexPath), indexPath) } - return fallbackDelegate?.tableView?(tableView, shouldIndentWhileEditingRowAt:indexPath) + return fallbackDelegate?.tableView?(tableView, shouldIndentWhileEditingRowAt: indexPath) ?? true } @@ -373,7 +371,7 @@ extension DataSource: UITableViewDelegate { return } - fallbackDelegate?.tableView?(tableView, willBeginEditingRowAt:indexPath) + fallbackDelegate?.tableView?(tableView, willBeginEditingRowAt: indexPath) } public func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) { @@ -382,9 +380,9 @@ extension DataSource: UITableViewDelegate { return } - fallbackDelegate?.tableView?(tableView, didEndEditingRowAt:indexPath) + fallbackDelegate?.tableView?(tableView, didEndEditingRowAt: indexPath) } - + // MARK: Moving & Reordering public func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath { @@ -394,11 +392,10 @@ extension DataSource: UITableViewDelegate { return closure(visibleRow(at: sourceIndexPath), (sourceIndexPath, proposedDestinationIndexPath)) } - return fallbackDelegate?.tableView?(tableView, targetIndexPathForMoveFromRowAt:sourceIndexPath, toProposedIndexPath: proposedDestinationIndexPath) + return fallbackDelegate?.tableView?(tableView, targetIndexPathForMoveFromRowAt: sourceIndexPath, toProposedIndexPath: proposedDestinationIndexPath) ?? proposedDestinationIndexPath } - // MARK: Indentation public func tableView(_ tableView: UITableView, indentationLevelForRowAt indexPath: IndexPath) -> Int { @@ -450,23 +447,22 @@ extension DataSource: UITableViewDelegate { // MARK: Focus public func tableView(_ tableView: UITableView, canFocusRowAt indexPath: IndexPath) -> Bool { - return fallbackDelegate?.tableView?(tableView, canFocusRowAt:indexPath) + return fallbackDelegate?.tableView?(tableView, canFocusRowAt: indexPath) ?? true } public func tableView(_ tableView: UITableView, shouldUpdateFocusIn context: UITableViewFocusUpdateContext) -> Bool { - return fallbackDelegate?.tableView?(tableView, shouldUpdateFocusIn:context) + return fallbackDelegate?.tableView?(tableView, shouldUpdateFocusIn: context) ?? true } public func tableView(_ tableView: UITableView, didUpdateFocusIn context: UITableViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) { - fallbackDelegate?.tableView?(tableView, didUpdateFocusIn:context, with:coordinator) + fallbackDelegate?.tableView?(tableView, didUpdateFocusIn: context, with: coordinator) } public func indexPathForPreferredFocusedView(in tableView: UITableView) -> IndexPath? { return fallbackDelegate?.indexPathForPreferredFocusedView?(in: tableView) } - } @available(iOS 11,*) @@ -489,3 +485,16 @@ extension DataSource { } } } + +@available(iOS 13,*) +extension DataSource { + public func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + let cellDescriptor = self.cellDescriptor(at: indexPath) as? CellDescriptorTypeiOS13 + + if let closure = cellDescriptor?.configurationForMenuAtLocationClosure { + return closure(visibleRow(at: indexPath), indexPath) + } else { + return fallbackDelegate?.tableView?(tableView, contextMenuConfigurationForRowAt: indexPath, point: CGPoint(x: 0, y: 0)) + } + } +} diff --git a/DataSource/DataSource.swift b/DataSource/DataSource.swift index 7b8d2ce..f88f379 100644 --- a/DataSource/DataSource.swift +++ b/DataSource/DataSource.swift @@ -6,88 +6,90 @@ // Copyright © 2017 aaa - all about apps GmbH. All rights reserved. // -import UIKit import Differ +import UIKit public class DataSource: NSObject { - public var sections: [SectionType] = [] public internal(set) var visibleSections: [SectionType] = [] // MARK: UITableViewDataSource - public var configure: ((RowType, UITableViewCell, IndexPath) -> Void)? = nil - public var canEdit: ((RowType, IndexPath) -> Bool)? = nil - public var canMove: ((RowType, IndexPath) -> Bool)? = nil - public var sectionIndexTitles: (() -> [String]?)? = nil - public var sectionForSectionIndex: ((String, Int) -> Int)? = nil - public var commitEditing: ((RowType, UITableViewCell.EditingStyle, IndexPath) -> Void)? = nil - public var moveRow: ((RowType, (IndexPath, IndexPath)) -> Void)? = nil + public var configure: ((RowType, UITableViewCell, IndexPath) -> Void)? + public var canEdit: ((RowType, IndexPath) -> Bool)? + public var canMove: ((RowType, IndexPath) -> Bool)? + public var sectionIndexTitles: (() -> [String]?)? + public var sectionForSectionIndex: ((String, Int) -> Int)? + public var commitEditing: ((RowType, UITableViewCell.EditingStyle, IndexPath) -> Void)? + public var moveRow: ((RowType, (IndexPath, IndexPath)) -> Void)? - public var fallbackDataSource: UITableViewDataSource? = nil + public var fallbackDataSource: UITableViewDataSource? // MARK: UITableViewDelegate - public var height: ((RowType, IndexPath) -> CGFloat)? = nil + public var height: ((RowType, IndexPath) -> CGFloat)? // no RowType parameter for estimatedHeight because we do not want to potentially instantiate // a LazyRow just to get the height estimate - public var estimatedHeight: ((IndexPath) -> CGFloat)? = nil + public var estimatedHeight: ((IndexPath) -> CGFloat)? - public var shouldHighlight: ((RowType, IndexPath) -> Bool)? = nil - public var didHighlight: ((RowType, IndexPath) -> Void)? = nil - public var didUnhighlight: ((RowType, IndexPath) -> Void)? = nil + public var shouldHighlight: ((RowType, IndexPath) -> Bool)? + public var didHighlight: ((RowType, IndexPath) -> Void)? + public var didUnhighlight: ((RowType, IndexPath) -> Void)? - public var willSelect: ((RowType, IndexPath) -> IndexPath?)? = nil - public var willDeselect: ((RowType, IndexPath) -> IndexPath?)? = nil - public var didSelect: ((RowType, IndexPath) -> SelectionResult)? = nil - public var didDeselect: ((RowType, IndexPath) -> Void)? = nil + public var willSelect: ((RowType, IndexPath) -> IndexPath?)? + public var willDeselect: ((RowType, IndexPath) -> IndexPath?)? + public var didSelect: ((RowType, IndexPath) -> SelectionResult)? + public var didDeselect: ((RowType, IndexPath) -> Void)? - public var willDisplay: ((RowType, UITableViewCell, IndexPath) -> Void)? = nil - public var didEndDisplaying: ((UITableViewCell, IndexPath) -> Void)? = nil + public var willDisplay: ((RowType, UITableViewCell, IndexPath) -> Void)? + public var didEndDisplaying: ((UITableViewCell, IndexPath) -> Void)? - public var editingStyle: ((RowType, IndexPath) -> UITableViewCell.EditingStyle)? = nil - public var titleForDeleteConfirmationButton: ((RowType, IndexPath) -> String?)? = nil - public var editActions: ((RowType, IndexPath) -> [UITableViewRowAction]?)? = nil - public var shouldIndentWhileEditing: ((RowType, IndexPath) -> Bool)? = nil - public var willBeginEditing: ((RowType, IndexPath) -> Void)? = nil - public var didEndEditing: ((IndexPath?) -> Void)? = nil + public var editingStyle: ((RowType, IndexPath) -> UITableViewCell.EditingStyle)? + public var titleForDeleteConfirmationButton: ((RowType, IndexPath) -> String?)? + public var editActions: ((RowType, IndexPath) -> [UITableViewRowAction]?)? + public var shouldIndentWhileEditing: ((RowType, IndexPath) -> Bool)? + public var willBeginEditing: ((RowType, IndexPath) -> Void)? + public var didEndEditing: ((IndexPath?) -> Void)? - public var sectionHeader: ((SectionType, Int) -> HeaderFooter)? = nil - public var sectionFooter: ((SectionType, Int) -> HeaderFooter)? = nil + public var sectionHeader: ((SectionType, Int) -> HeaderFooter)? + public var sectionFooter: ((SectionType, Int) -> HeaderFooter)? - public var sectionHeaderHeight: ((SectionType, Int) -> SectionHeight)? = nil - public var sectionFooterHeight: ((SectionType, Int) -> SectionHeight)? = nil + public var sectionHeaderHeight: ((SectionType, Int) -> SectionHeight)? + public var sectionFooterHeight: ((SectionType, Int) -> SectionHeight)? - public var willDisplaySectionHeader: ((SectionType, UIView, Int) -> Void)? = nil - public var willDisplaySectionFooter: ((SectionType, UIView, Int) -> Void)? = nil + public var willDisplaySectionHeader: ((SectionType, UIView, Int) -> Void)? + public var willDisplaySectionFooter: ((SectionType, UIView, Int) -> Void)? - public var didEndDisplayingSectionHeader: ((UIView, Int) -> Void)? = nil - public var didEndDisplayingSectionFooter: ((UIView, Int) -> Void)? = nil + public var didEndDisplayingSectionHeader: ((UIView, Int) -> Void)? + public var didEndDisplayingSectionFooter: ((UIView, Int) -> Void)? - public var targetIndexPathForMove: ((RowType, (IndexPath, IndexPath)) -> IndexPath)? = nil - public var indentationLevel: ((RowType, IndexPath) -> Int)? = nil - public var shouldShowMenu: ((RowType, IndexPath) -> Bool)? = nil - public var canPerformAction: ((RowType, Selector, Any?, IndexPath) -> Bool)? = nil - public var performAction: ((RowType, Selector, Any?, IndexPath) -> Void)? = nil - public var canFocus: ((RowType, IndexPath) -> Bool)? = nil + public var targetIndexPathForMove: ((RowType, (IndexPath, IndexPath)) -> IndexPath)? + public var indentationLevel: ((RowType, IndexPath) -> Int)? + public var shouldShowMenu: ((RowType, IndexPath) -> Bool)? + public var canPerformAction: ((RowType, Selector, Any?, IndexPath) -> Bool)? + public var performAction: ((RowType, Selector, Any?, IndexPath) -> Void)? + public var canFocus: ((RowType, IndexPath) -> Bool)? // MARK: Swipe Actions (iOS 11+ only) - private var _leadingSwipeActions: ((RowType, IndexPath) -> Any?)? = nil - private var _trailingSwipeActions: ((RowType, IndexPath) -> Any?)? = nil + + private var _leadingSwipeActions: ((RowType, IndexPath) -> Any?)? + private var _trailingSwipeActions: ((RowType, IndexPath) -> Any?)? + + public var _configurationForMenuAtLocationClosure: ((RowType, IndexPath) -> Any)? // MARK: UITableViewDataSourcePrefetching - public var prefetchRows: (([IndexPath]) -> Void)? = nil - public var cancelPrefetching: (([IndexPath]) -> Void)? = nil + public var prefetchRows: (([IndexPath]) -> Void)? + public var cancelPrefetching: (([IndexPath]) -> Void)? - public weak var fallbackDataSourcePrefetching: UITableViewDataSourcePrefetching? = nil + public weak var fallbackDataSourcePrefetching: UITableViewDataSourcePrefetching? // MARK: Fallback delegate /// Fallback used when DataSource doesn't handle delegate method itself. /// - Note: The fallback delegate needs to be set *before* setting the table view's delegate, otherwise certain delegate methods will never be called. - public weak var fallbackDelegate: UITableViewDelegate? = nil + public weak var fallbackDelegate: UITableViewDelegate? public override func forwardingTarget(for aSelector: Selector!) -> Any? { return fallbackDelegate @@ -135,11 +137,13 @@ public class DataSource: NSObject { return cellDescriptors.values.contains(where: { ($0 as? CellDescriptorTypeiOS11)?.trailingSwipeActionsClosure != nil }) } + // MARK: ContextMenu + // MARK: Additional - public var isRowHidden: ((RowType, IndexPath) -> Bool)? = nil - public var isSectionHidden: ((SectionType, Int) -> Bool)? = nil - public var update: ((RowType, UITableViewCell, IndexPath) -> Void)? = nil + public var isRowHidden: ((RowType, IndexPath) -> Bool)? + public var isSectionHidden: ((SectionType, Int) -> Bool)? + public var update: ((RowType, UITableViewCell, IndexPath) -> Void)? // MARK: Internal @@ -159,15 +163,15 @@ public class DataSource: NSObject { self.cellDescriptors[d.rowIdentifier] = d } - self.addDescriptorIfNotSet(SeparatorLineCell.descriptorLine) - self.addDescriptorIfNotSet(SeparatorLineCell.descriptorCustomView) + addDescriptorIfNotSet(SeparatorLineCell.descriptorLine) + addDescriptorIfNotSet(SeparatorLineCell.descriptorCustomView) let defaultSectionDescriptors: [SectionDescriptorType] = [ SectionDescriptor(), SectionDescriptor() - .header { (title, _) in + .header { title, _ in .title(title) - } + } ] for d in defaultSectionDescriptors { @@ -180,8 +184,8 @@ public class DataSource: NSObject { } private func addDescriptorIfNotSet(_ descriptor: CellDescriptorType) { - if self.cellDescriptors[descriptor.rowIdentifier] == nil { - self.cellDescriptors[descriptor.rowIdentifier] = descriptor + if cellDescriptors[descriptor.rowIdentifier] == nil { + cellDescriptors[descriptor.rowIdentifier] = descriptor } } @@ -244,7 +248,7 @@ public class DataSource: NSObject { return nil } } - + // MARK: Visibility internal func updateVisibility() { @@ -266,7 +270,7 @@ public class DataSource: NSObject { isHidden = false } - if isHidden == false && section.numberOfVisibleRows > 0 { + if isHidden == false, section.numberOfVisibleRows > 0 { visibleSections.append(section) } } @@ -300,8 +304,7 @@ public class DataSource: NSObject { rowReloadAnimation: UITableView.RowAnimation = .none, sectionDeletionAnimation: UITableView.RowAnimation = .fade, sectionInsertionAnimation: UITableView.RowAnimation = .fade - ) { - + ) { let oldSections = visibleSections.map { $0.diffableSection } let newSections = updateSectionVisiblity() let diffableNewSections = newSections.map { $0.diffableSection } @@ -309,7 +312,7 @@ public class DataSource: NSObject { let diff = computeDiff(oldSections: oldSections, newSections: diffableNewSections) let updates = computeUpdate(oldSections: oldSections, newSections: diffableNewSections) - self.visibleSections = newSections + visibleSections = newSections if rowReloadAnimation == .none { for update in updates { @@ -323,7 +326,8 @@ public class DataSource: NSObject { rowInsertionAnimation: rowInsertionAnimation, rowReloadAnimation: rowReloadAnimation, sectionDeletionAnimation: sectionDeletionAnimation, - sectionInsertionAnimation: sectionInsertionAnimation) + sectionInsertionAnimation: sectionInsertionAnimation + ) } public func updateRow(_ tableView: UITableView, row: RowType, at indexPath: IndexPath) { @@ -331,9 +335,9 @@ public class DataSource: NSObject { let closure = cellDescriptor.updateClosure - ?? update - ?? cellDescriptor.configureClosure - ?? configure + ?? update + ?? cellDescriptor.configureClosure + ?? configure if let cell = tableView.cellForRow(at: indexPath), let closure = closure { closure(row, cell, indexPath) @@ -349,8 +353,8 @@ extension DataSource { return nil } - return { [weak self] (rowType, indexPath) in - return self?._leadingSwipeActions?(rowType, indexPath) as? UISwipeActionsConfiguration + return { [weak self] rowType, indexPath in + self?._leadingSwipeActions?(rowType, indexPath) as? UISwipeActionsConfiguration } } set { @@ -364,14 +368,31 @@ extension DataSource { return nil } - return { [weak self] (rowType, indexPath) in - return self?._trailingSwipeActions?(rowType, indexPath) as? UISwipeActionsConfiguration + return { [weak self] rowType, indexPath in + self?._trailingSwipeActions?(rowType, indexPath) as? UISwipeActionsConfiguration } } set { _trailingSwipeActions = newValue } } - } +@available(iOS 13,*) +extension DataSource { + public var configurationForMenuAtLocationClosure: ((RowType, IndexPath) -> UIContextMenuConfiguration?)? { + get { + if _configurationForMenuAtLocationClosure == nil { + return nil + } + + return { [weak self] rowType, indexPath in + self?._configurationForMenuAtLocationClosure?(rowType, indexPath) as? UIContextMenuConfiguration + } + } + + set { + _configurationForMenuAtLocationClosure = newValue + } + } +} diff --git a/Example/Storyboards/Base.lproj/Main.storyboard b/Example/Storyboards/Base.lproj/Main.storyboard index 69eed35..4944c47 100644 --- a/Example/Storyboards/Base.lproj/Main.storyboard +++ b/Example/Storyboards/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -48,6 +48,7 @@ + @@ -215,7 +216,7 @@ - + @@ -360,7 +361,26 @@ - + + + + + + + + + + + + + + + + + + + + diff --git a/Example/ViewControllers/ContextMenuViewController.swift b/Example/ViewControllers/ContextMenuViewController.swift new file mode 100644 index 0000000..c4b23bc --- /dev/null +++ b/Example/ViewControllers/ContextMenuViewController.swift @@ -0,0 +1,67 @@ +// +// ContextMenuViewController.swift +// Example +// +// Created by Sandin Dulić on 09.09.20. +// Copyright © 2020 aaa - all about apps GmbH. All rights reserved. +// + +import UIKit +import DataSource + +@available(iOS 13.0, *) +class ContextMenuViewController: UITableViewController { + lazy var dataSource: DataSource = { + DataSource( + cellDescriptors: [ + CellDescriptor() + .configure { item, cell, _ in + cell.textLabel?.text = item + } + .configurationForMenuAtLocation { [weak self] _, indexPath -> UIContextMenuConfiguration in + guard let self = self else { return UIContextMenuConfiguration() } + let index = indexPath.row + + let identifier = "\(index)" as NSString + return self.createContextMenuConfiguration(with: identifier) + } + ]) + }() + + override func viewDidLoad() { + super.viewDidLoad() + + title = "Context menu example" + + tableView.dataSource = dataSource + tableView.delegate = dataSource + + dataSource.sections = createSections() + dataSource.reloadData(tableView, animated: false) + } + + func createSections() -> [Section] { + let items = ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"] + + + return [ + Section(items: items) + ] + } + + private func createContextMenuConfiguration(with identifier: NSString) -> UIContextMenuConfiguration { + UIContextMenuConfiguration(identifier: identifier, previewProvider: nil) { _ in + let mapAction = UIAction( + title: "Some title", + image: UIImage(systemName: "map")) { _ in + } + + let shareAction = UIAction( + title: "Some title 2", + image: UIImage(systemName: "square.and.arrow.up")) { _ in + } + + return UIMenu(title: "", image: nil, children: [mapAction, shareAction]) + } + } +} diff --git a/Example/ViewControllers/Examples/DiffViewController.swift b/Example/ViewControllers/Examples/DiffViewController.swift index 914aced..819a05b 100644 --- a/Example/ViewControllers/Examples/DiffViewController.swift +++ b/Example/ViewControllers/Examples/DiffViewController.swift @@ -6,23 +6,22 @@ // Copyright © 2017 aaa - all about apps GmbH. All rights reserved. // -import UIKit import DataSource +import UIKit // MARK: - View Controller class DiffViewController: UITableViewController { - var counter = 0 lazy var dataSource: DataSource = { DataSource( cellDescriptors: [ CellDescriptor() - .configure { (item, cell, indexPath) in + .configure { item, cell, _ in cell.textLabel?.text = item.text } - .update { (item, cell, indexPath) in + .update { item, cell, _ in cell.textLabel?.update(text: item.text, animated: true) } ]) @@ -56,22 +55,21 @@ class DiffViewController: UITableViewController { }) ] } - + @IBAction func refresh(_ sender: Any) { counter += 1 dataSource.sections = createSections() dataSource.reloadDataAnimated(tableView, - rowDeletionAnimation: .automatic, - rowInsertionAnimation: .automatic, - rowReloadAnimation: .none) + rowDeletionAnimation: .automatic, + rowInsertionAnimation: .automatic, + rowReloadAnimation: .none) } } // MARK: - Diff Item struct DiffItem: Hashable { - let value: Int let text: String @@ -85,4 +83,4 @@ struct DiffItem: Hashable { } } -extension DiffItem: Diffable { } +extension DiffItem: Diffable {} diff --git a/Example/ViewControllers/StartViewController.swift b/Example/ViewControllers/StartViewController.swift index 2950738..ea37b48 100644 --- a/Example/ViewControllers/StartViewController.swift +++ b/Example/ViewControllers/StartViewController.swift @@ -41,6 +41,7 @@ class StartViewController: UITableViewController { Example(title: "Swipe To Delete", segue: "showSwipeToDelete"), Example(title: "Custom separators", segue: "showSeparatedSection"), Example(title: "Expandable Cell", segue: "showExpandableCell"), + Example(title: "Context Menu", segue: "showContextMenuExample") ] let separatorItems = [ From 9f845d29ab58b79221a8fe9604e7319e14db4335 Mon Sep 17 00:00:00 2001 From: Matthias Buchetics Date: Fri, 30 Oct 2020 09:08:50 +0100 Subject: [PATCH 19/31] re-added Cartfile --- Cartfile | 1 + 1 file changed, 1 insertion(+) create mode 100644 Cartfile diff --git a/Cartfile b/Cartfile new file mode 100644 index 0000000..9e7e149 --- /dev/null +++ b/Cartfile @@ -0,0 +1 @@ +github "tonyarnold/Differ" ~> 1.4 \ No newline at end of file From 2eece6ed7499dc8ef51aa7da095901fdc54631d7 Mon Sep 17 00:00:00 2001 From: michaelpeternell Date: Thu, 17 Jun 2021 10:37:18 +0200 Subject: [PATCH 20/31] Fix rare crash that happens when iOS calls certain methods with invalid IndexPath values. (#36) --- .../DataSource+UITableViewDelegate.swift | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/DataSource/DataSource+UITableViewDelegate.swift b/DataSource/DataSource+UITableViewDelegate.swift index 16b1eed..0c81ae3 100644 --- a/DataSource/DataSource+UITableViewDelegate.swift +++ b/DataSource/DataSource+UITableViewDelegate.swift @@ -468,6 +468,13 @@ extension DataSource: UITableViewDelegate { @available(iOS 11,*) extension DataSource { public func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + if indexPath.indices.count != 2 { + // Problem: "Crash in IndexPath.section.getter" + // When an indexPath has not exactly two indices, the getter for + // `section` traps, causing crashes. This should not happen, but + // sometimes it does anyways. + return nil + } let cellDescriptor = self.cellDescriptor(at: indexPath) as? CellDescriptorTypeiOS11 if let closure = cellDescriptor?.trailingSwipeActionsClosure { return closure(visibleRow(at: indexPath), indexPath) @@ -477,6 +484,13 @@ extension DataSource { } public func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + if indexPath.indices.count != 2 { + // Problem: "Crash in IndexPath.section.getter" + // When an indexPath has not exactly two indices, the getter for + // `section` traps, causing crashes. This should not happen, but + // sometimes it does anyways. + return nil + } let cellDescriptor = self.cellDescriptor(at: indexPath) as? CellDescriptorTypeiOS11 if let closure = cellDescriptor?.leadingSwipeActionsClosure { return closure(visibleRow(at: indexPath), indexPath) @@ -489,6 +503,13 @@ extension DataSource { @available(iOS 13,*) extension DataSource { public func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + if indexPath.indices.count != 2 { + // Problem: "Crash in IndexPath.section.getter" + // When an indexPath has not exactly two indices, the getter for + // `section` traps, causing crashes. This should not happen, but + // sometimes it does anyways. + return nil + } let cellDescriptor = self.cellDescriptor(at: indexPath) as? CellDescriptorTypeiOS13 if let closure = cellDescriptor?.configurationForMenuAtLocationClosure { From 9e8693e0d4a5053436981cb5186a4266d3b1094f Mon Sep 17 00:00:00 2001 From: Oliver Krakora Date: Fri, 19 Nov 2021 11:42:04 +0100 Subject: [PATCH 21/31] updated docs (#37) --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 61b2c49..d716bba 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # DataSource -[![Swift 4](https://img.shields.io/badge/Language-Swift%204-orange.svg)](https://developer.apple.com/swift) +[![Swift 5](https://img.shields.io/badge/Language-Swift%205-orange.svg)](https://developer.apple.com/swift) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-brightgreen.svg)](https://github.com/Carthage/Carthage) [![CocoaPods compatible](https://img.shields.io/cocoapods/v/MBDataSource.svg)](https://cocoapods.org/pods/MBDataSource) @@ -226,6 +226,10 @@ Cells can be registered from custom bundles. You can specify in the cell descrip let descriptor = CellDescriptor(bundle: customBundle) ``` +### Cell Registration +If you define your cell types in a separate xib(outside your tableView definition in a storyboard) or entirely in code your cell needs to be registered with the tableView you want to use it with. +You can either register the cell with the tableView manually(see UITableView docs) or let DataSource do that for you by conforming to the `AutoRegisterCell` protocol. + ## Version Compatibility Current Swift compatibility breakdown: From c09f63bfbb5b38e5838dc662a94825bd752be2a8 Mon Sep 17 00:00:00 2001 From: Matthias Buchetics Date: Tue, 15 Mar 2022 14:06:36 +0100 Subject: [PATCH 22/31] fixed crash introduced with Swift 5.6 --- DataSource.xcodeproj/project.pbxproj | 18 ++++++++++-------- .../xcshareddata/xcschemes/DataSource.xcscheme | 2 +- .../xcschemes/DataSourceTests.xcscheme | 2 +- .../xcshareddata/xcschemes/Example.xcscheme | 2 +- DataSource/DataSource.swift | 9 ++++----- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/DataSource.xcodeproj/project.pbxproj b/DataSource.xcodeproj/project.pbxproj index 657dcd5..d0d3491 100644 --- a/DataSource.xcodeproj/project.pbxproj +++ b/DataSource.xcodeproj/project.pbxproj @@ -370,9 +370,9 @@ isa = PBXNativeTarget; buildConfigurationList = 395D14BB1B90612D00658680 /* Build configuration list for PBXNativeTarget "DataSource" */; buildPhases = ( + 395D14A11B90612D00658680 /* Headers */, 395D149F1B90612D00658680 /* Sources */, 395D14A01B90612D00658680 /* Frameworks */, - 395D14A11B90612D00658680 /* Headers */, 395D14A21B90612D00658680 /* Resources */, ); buildRules = ( @@ -414,7 +414,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0820; - LastUpgradeCheck = 1100; + LastUpgradeCheck = 1330; ORGANIZATIONNAME = "aaa - all about apps GmbH"; TargetAttributes = { 395D147B1B90610A00658680 = { @@ -619,6 +619,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -643,7 +644,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -673,6 +674,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -691,7 +693,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; @@ -711,7 +713,7 @@ "$(PROJECT_DIR)/Carthage/Build/iOS", ); INFOPLIST_FILE = Example/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -738,7 +740,7 @@ "$(PROJECT_DIR)/Carthage/Build/iOS", ); INFOPLIST_FILE = Example/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -841,7 +843,7 @@ "$(PROJECT_DIR)/Carthage/Build/iOS", ); INFOPLIST_FILE = DataSourceTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.2; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -867,7 +869,7 @@ "$(PROJECT_DIR)/Carthage/Build/iOS", ); INFOPLIST_FILE = DataSourceTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.2; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/DataSource.xcodeproj/xcshareddata/xcschemes/DataSource.xcscheme b/DataSource.xcodeproj/xcshareddata/xcschemes/DataSource.xcscheme index 06fd8ac..e074ccd 100644 --- a/DataSource.xcodeproj/xcshareddata/xcschemes/DataSource.xcscheme +++ b/DataSource.xcodeproj/xcshareddata/xcschemes/DataSource.xcscheme @@ -1,6 +1,6 @@ Date: Mon, 5 Dec 2022 18:20:44 +0100 Subject: [PATCH 23/31] Make separatorlineviewmodel publicy available to type check against it --- .../xcode/package.xcworkspace/contents.xcworkspacedata | 7 +++++++ DataSource/SeparatedSection/SeparatorLineViewModel.swift | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/DataSource/SeparatedSection/SeparatorLineViewModel.swift b/DataSource/SeparatedSection/SeparatorLineViewModel.swift index 8945614..f8652f0 100644 --- a/DataSource/SeparatedSection/SeparatorLineViewModel.swift +++ b/DataSource/SeparatedSection/SeparatorLineViewModel.swift @@ -8,9 +8,9 @@ import UIKit -class SeparatorLineViewModel: Diffable { +public class SeparatorLineViewModel: Diffable { - let diffIdentifier: String + public let diffIdentifier: String let style: SeparatorStyle init(diffIdentifier: String, style: SeparatorStyle) { @@ -22,7 +22,7 @@ class SeparatorLineViewModel: Diffable { return Row(self) } - func isEqualToDiffable(_ other: Diffable?) -> Bool { + public func isEqualToDiffable(_ other: Diffable?) -> Bool { guard let other = other as? SeparatorLineViewModel else { return false } return other.style == self.style } From 4f891201d6112b2d0d493d7cba992977d319ad62 Mon Sep 17 00:00:00 2001 From: Mathias Poimer Date: Mon, 5 Dec 2022 21:10:06 +0100 Subject: [PATCH 24/31] remove committed file --- .../xcode/package.xcworkspace/contents.xcworkspacedata | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - From e08f4af2d2054ef267ea4e2a7b895af8a60783fe Mon Sep 17 00:00:00 2001 From: Matthias Buchetics Date: Tue, 21 Feb 2023 14:17:44 +0100 Subject: [PATCH 25/31] Update LICENSE --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index c9a2f2c..8c55433 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 - 2018 Matthias Buchetics +Copyright (c) 2016 - 2023 all about apps GmbH Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 500a3c47971e234292d59ebe092c8fa00e4d391b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Mu=CC=88hleder?= Date: Thu, 13 Jul 2023 11:23:22 +0200 Subject: [PATCH 26/31] Fix crashes on concurrent select + reload --- DataSource.xcodeproj/project.pbxproj | 10 +- .../DataSource+UITableViewDataSource.swift | 143 +++--- .../DataSource+UITableViewDelegate.swift | 478 +++++++++++------- DataSource/DataSource.swift | 258 +++++----- DataSource/Row.swift | 40 +- DataSource/Section.swift | 9 + .../ConcurrentReloadSelectTest.swift | 125 +++++ DataSourceTests/DataSourceTests.swift | 37 -- 8 files changed, 671 insertions(+), 429 deletions(-) create mode 100644 DataSourceTests/ConcurrentReloadSelectTest.swift delete mode 100644 DataSourceTests/DataSourceTests.swift diff --git a/DataSource.xcodeproj/project.pbxproj b/DataSource.xcodeproj/project.pbxproj index d0d3491..e0741ec 100644 --- a/DataSource.xcodeproj/project.pbxproj +++ b/DataSource.xcodeproj/project.pbxproj @@ -3,11 +3,12 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 196512982417A743004B77AF /* AutoRegisterCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 196512972417A743004B77AF /* AutoRegisterCell.swift */; }; + 274B6CD42A5FC9C500D3ECAE /* ConcurrentReloadSelectTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274B6CD32A5FC9C500D3ECAE /* ConcurrentReloadSelectTest.swift */; }; 3903C1832428C7410094EF50 /* ExpandableCellViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3903C1822428C7410094EF50 /* ExpandableCellViewController.swift */; }; 3903C1852428C7DE0094EF50 /* TextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3903C1842428C7DE0094EF50 /* TextCell.swift */; }; 3903C1872428C7E80094EF50 /* TextCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3903C1862428C7E80094EF50 /* TextCell.xib */; }; @@ -36,7 +37,6 @@ 398248CB1E5CA74A00F802D1 /* UITableViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398248CA1E5CA74A00F802D1 /* UITableViewExtensions.swift */; }; 398248CD1E5CA9C600F802D1 /* DataSource+UITableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398248CC1E5CA9C600F802D1 /* DataSource+UITableViewDelegate.swift */; }; 39913AB81E61BBCC00623635 /* SwiftRandom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39913AB71E61BBCC00623635 /* SwiftRandom.swift */; }; - 39A848671E5E1D9600D7DBC2 /* DataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A848661E5E1D9600D7DBC2 /* DataSourceTests.swift */; }; 39A848691E5E1D9600D7DBC2 /* DataSource.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 395D14A41B90612D00658680 /* DataSource.framework */; }; 39A848751E5E2DEC00D7DBC2 /* DataSource.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 395D14A41B90612D00658680 /* DataSource.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 39B621801E6D757000BE18EE /* DataSource+UITableViewDataSourcePrefetching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39B6217F1E6D757000BE18EE /* DataSource+UITableViewDataSourcePrefetching.swift */; }; @@ -108,6 +108,7 @@ /* Begin PBXFileReference section */ 196512972417A743004B77AF /* AutoRegisterCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoRegisterCell.swift; sourceTree = ""; }; + 274B6CD32A5FC9C500D3ECAE /* ConcurrentReloadSelectTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConcurrentReloadSelectTest.swift; sourceTree = ""; }; 3903C1822428C7410094EF50 /* ExpandableCellViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandableCellViewController.swift; sourceTree = ""; }; 3903C1842428C7DE0094EF50 /* TextCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextCell.swift; sourceTree = ""; }; 3903C1862428C7E80094EF50 /* TextCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TextCell.xib; sourceTree = ""; }; @@ -141,7 +142,6 @@ 398987531F386B0700C96BF9 /* Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Example.entitlements; sourceTree = ""; }; 39913AB71E61BBCC00623635 /* SwiftRandom.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftRandom.swift; sourceTree = ""; }; 39A848641E5E1D9600D7DBC2 /* DataSourceTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DataSourceTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 39A848661E5E1D9600D7DBC2 /* DataSourceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSourceTests.swift; sourceTree = ""; }; 39A848681E5E1D9600D7DBC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39B6217F1E6D757000BE18EE /* DataSource+UITableViewDataSourcePrefetching.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataSource+UITableViewDataSourcePrefetching.swift"; sourceTree = ""; }; 39E9E3A51E6566AD00A3C300 /* PersonCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonCell.swift; sourceTree = ""; }; @@ -294,8 +294,8 @@ 39A848651E5E1D9600D7DBC2 /* DataSourceTests */ = { isa = PBXGroup; children = ( - 39A848661E5E1D9600D7DBC2 /* DataSourceTests.swift */, 39A848681E5E1D9600D7DBC2 /* Info.plist */, + 274B6CD32A5FC9C500D3ECAE /* ConcurrentReloadSelectTest.swift */, ); path = DataSourceTests; sourceTree = ""; @@ -553,7 +553,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 39A848671E5E1D9600D7DBC2 /* DataSourceTests.swift in Sources */, + 274B6CD42A5FC9C500D3ECAE /* ConcurrentReloadSelectTest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/DataSource/DataSource+UITableViewDataSource.swift b/DataSource/DataSource+UITableViewDataSource.swift index 161e481..ae7e8fb 100644 --- a/DataSource/DataSource+UITableViewDataSource.swift +++ b/DataSource/DataSource+UITableViewDataSource.swift @@ -1,34 +1,29 @@ -// -// DataSource+UITableView.swift -// DataSource -// -// Created by Matthias Buchetics on 21/02/2017. -// Copyright © 2017 aaa - all about apps GmbH. All rights reserved. -// - -import UIKit import Differ +import UIKit extension DataSource: UITableViewDataSource { - + // MARK: Counts - - public func numberOfSections(in tableView: UITableView) -> Int { + + public func numberOfSections(in _: UITableView) -> Int { return visibleSections.count } - - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + + public func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int { return visibleSections[section].numberOfVisibleRows } - + // MARK: Configuration - + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cellDescriptor = self.cellDescriptor(at: indexPath) + guard let cellDescriptor = cellDescriptor(at: indexPath) else { + fatalError("[DataSource] no cellDescriptor found for indexPath \(indexPath) with identifier \(visibleRow(at: indexPath)?.identifier)") + } + let cellIdentifier = cellDescriptor.cellIdentifier let bundle = cellDescriptor.bundle ?? Bundle.main - - if registerNibs && !reuseIdentifiers.contains(cellIdentifier) { + + if registerNibs, !reuseIdentifiers.contains(cellIdentifier) { if bundle.path(forResource: cellIdentifier, ofType: "nib") != nil { tableView.registerNib(cellIdentifier, bundle: bundle) reuseIdentifiers.insert(cellIdentifier) @@ -37,13 +32,15 @@ extension DataSource: UITableViewDataSource { reuseIdentifiers.insert(cellIdentifier) } } - + if let closure = cellDescriptor.configureClosure ?? configure { - let row = self.visibleRow(at: indexPath) + guard let row = visibleRow(at: indexPath) else { + fatalError("[DataSource] no visible row found for indexPath \(indexPath)") + } let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) - + closure(row, cell, indexPath) - + return cell } else if let fallbackDataSource = fallbackDataSource { return fallbackDataSource.tableView(tableView, cellForRowAt: indexPath) @@ -53,13 +50,17 @@ extension DataSource: UITableViewDataSource { } // MARK: Header & Footer - - public func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + + public func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? { let sectionDescriptor = self.sectionDescriptor(at: section) - + if let closure = sectionDescriptor?.headerClosure ?? sectionHeader { - let header = closure(visibleSection(at: section), section) - + guard let visibleSection = visibleSection(at: section) else { + return nil + } + + let header = closure(visibleSection, section) + switch header { case .title(let title): return title @@ -67,16 +68,20 @@ extension DataSource: UITableViewDataSource { return nil } } - + return nil } - - public func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + + public func tableView(_: UITableView, titleForFooterInSection section: Int) -> String? { let sectionDescriptor = self.sectionDescriptor(at: section) - + if let closure = sectionDescriptor?.footerClosure ?? sectionFooter { - let footer = closure(visibleSection(at: section), section) - + guard let visibleSection = visibleSection(at: section) else { + return nil + } + + let footer = closure(visibleSection, section) + switch footer { case .title(let title): return title @@ -84,70 +89,86 @@ extension DataSource: UITableViewDataSource { return nil } } - + return nil } - + // MARK: Editing - + public func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.canEditClosure ?? canEdit { - return closure(visibleRow(at: indexPath), indexPath) + + guard let visibleRow = visibleRow(at: indexPath) else { + return false } - + + if let closure = cellDescriptor?.canEditClosure ?? canEdit { + return closure(visibleRow, indexPath) + } + return fallbackDataSource?.tableView?(tableView, canEditRowAt: indexPath) ?? false } - + // MARK: Moving & Reordering - + public func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.canMoveClosure ?? canMove { - return closure(visibleRow(at: indexPath), indexPath) + + if let closure = cellDescriptor?.canMoveClosure ?? canMove { + guard let visibleRow = visibleRow(at: indexPath) else { + return false + } + + return closure(visibleRow, indexPath) } - + return fallbackDataSource?.tableView?(tableView, canMoveRowAt: indexPath) ?? false } - + // MARK: Index - + public func sectionIndexTitles(for tableView: UITableView) -> [String]? { return sectionIndexTitles?() ?? fallbackDataSource?.sectionIndexTitles?(for: tableView) } - + public func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int { return sectionForSectionIndex?(title, index) ?? fallbackDataSource?.tableView?(tableView, sectionForSectionIndexTitle: title, at: index) ?? index } - + // MARK: Data manipulation - + public func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.commitEditingClosure ?? commitEditing { - closure(visibleRow(at: indexPath), editingStyle, indexPath) + + if let closure = cellDescriptor?.commitEditingClosure ?? commitEditing { + guard let visibleRow = visibleRow(at: indexPath) else { + return + } + + closure(visibleRow, editingStyle, indexPath) return } - + fallbackDataSource?.tableView?(tableView, commit: editingStyle, forRowAt: indexPath) } - + public func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { let cellDescriptor = self.cellDescriptor(at: sourceIndexPath) - - if let closure = cellDescriptor.moveRowClosure ?? moveRow { - closure(visibleRow(at: sourceIndexPath), (sourceIndexPath, destinationIndexPath)) + + if let closure = cellDescriptor?.moveRowClosure ?? moveRow { + guard let visibleRow = visibleRow(at: sourceIndexPath) else { + return + } + + closure(visibleRow, (sourceIndexPath, destinationIndexPath)) return } - + fallbackDataSource?.tableView?(tableView, moveRowAt: sourceIndexPath, to: destinationIndexPath) } } diff --git a/DataSource/DataSource+UITableViewDelegate.swift b/DataSource/DataSource+UITableViewDelegate.swift index 0c81ae3..f93110b 100644 --- a/DataSource/DataSource+UITableViewDelegate.swift +++ b/DataSource/DataSource+UITableViewDelegate.swift @@ -1,80 +1,96 @@ -// -// DataSource+UITableViewDelegate.swift -// DataSource -// -// Created by Matthias Buchetics on 21/02/2017. -// Copyright © 2017 aaa - all about apps GmbH. All rights reserved. -// - import Differ import UIKit extension DataSource: UITableViewDelegate { // MARK: Highlighting - + public func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.shouldHighlightClosure ?? shouldHighlight { - return closure(visibleRow(at: indexPath), indexPath) + + if let closure = cellDescriptor?.shouldHighlightClosure ?? shouldHighlight { + guard let visibleRow = visibleRow(at: indexPath) else { + return false + } + + return closure(visibleRow, indexPath) } - + return fallbackDelegate?.tableView?(tableView, shouldHighlightRowAt: indexPath) ?? true } - + public func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.didHighlightClosure ?? didHighlight { - closure(visibleRow(at: indexPath), indexPath) + + if let closure = cellDescriptor?.didHighlightClosure ?? didHighlight { + guard let visibleRow = visibleRow(at: indexPath) else { + return + } + + closure(visibleRow, indexPath) return } - + fallbackDelegate?.tableView?(tableView, didHighlightRowAt: indexPath) } - + public func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath) { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.didUnhighlightClosure ?? didUnhighlight { - closure(visibleRow(at: indexPath), indexPath) + + if let closure = cellDescriptor?.didUnhighlightClosure ?? didUnhighlight { + guard let visibleRow = visibleRow(at: indexPath) else { + return + } + + closure(visibleRow, indexPath) return } - + fallbackDelegate?.tableView?(tableView, didUnhighlightRowAt: indexPath) } - + // MARK: Selection - + public func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.willSelectClosure ?? willSelect { - return closure(visibleRow(at: indexPath), indexPath) + + if let closure = cellDescriptor?.willSelectClosure ?? willSelect { + guard let visibleRow = visibleRow(at: indexPath) else { + return nil + } + + return closure(visibleRow, indexPath) } - + return fallbackDelegate?.tableView?(tableView, willSelectRowAt: indexPath) ?? indexPath } - + public func tableView(_ tableView: UITableView, willDeselectRowAt indexPath: IndexPath) -> IndexPath? { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.willDeselectClosure ?? willDeselect { - return closure(visibleRow(at: indexPath), indexPath) + + if let closure = cellDescriptor?.willDeselectClosure ?? willDeselect { + guard let visibleRow = visibleRow(at: indexPath) else { + return nil + } + + return closure(visibleRow, indexPath) } - + return fallbackDelegate?.tableView?(tableView, willDeselectRowAt: indexPath) ?? indexPath } - + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.didSelectClosure ?? didSelect { - let selectionResult = closure(visibleRow(at: indexPath), indexPath) + + if let closure = cellDescriptor?.didSelectClosure ?? didSelect { + guard let visibleRow = visibleRow(at: indexPath) else { + return + } + let selectionResult = closure(visibleRow, indexPath) + switch selectionResult { case .deselect: if let selectedIndexPath = tableView.indexPathForSelectedRow { @@ -87,26 +103,34 @@ extension DataSource: UITableViewDelegate { fallbackDelegate?.tableView?(tableView, didSelectRowAt: indexPath) } } - + public func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.didDeselectClosure ?? didDeselect { - closure(visibleRow(at: indexPath), indexPath) + + if let closure = cellDescriptor?.didDeselectClosure ?? didDeselect { + guard let visibleRow = visibleRow(at: indexPath) else { + return + } + + closure(visibleRow, indexPath) return } - + fallbackDelegate?.tableView?(tableView, didDeselectRowAt: indexPath) } - + // MARK: Header & Footer - - public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + + public func tableView(_: UITableView, viewForHeaderInSection section: Int) -> UIView? { let sectionDescriptor = self.sectionDescriptor(at: section) - + if let closure = sectionDescriptor?.headerClosure ?? sectionHeader { - let header = closure(visibleSection(at: section), section) + guard let visibleSection = visibleSection(at: section) else { + return nil + } + let header = closure(visibleSection, section) + switch header { case .view(let view): return view @@ -114,16 +138,20 @@ extension DataSource: UITableViewDelegate { return nil } } - + return nil } - - public func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + + public func tableView(_: UITableView, viewForFooterInSection section: Int) -> UIView? { let sectionDescriptor = self.sectionDescriptor(at: section) - + if let closure = sectionDescriptor?.footerClosure ?? sectionFooter { - let footer = closure(visibleSection(at: section), section) + guard let visibleSection = visibleSection(at: section) else { + return nil + } + let footer = closure(visibleSection, section) + switch footer { case .view(let view): return view @@ -131,76 +159,92 @@ extension DataSource: UITableViewDelegate { return nil } } - + return nil } - + // MARK: Height - + // NOTE: estimated section header and footer heights are not supported - + public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.heightClosure ?? height { - return closure(visibleRow(at: indexPath), indexPath) + + if let closure = cellDescriptor?.heightClosure ?? height { + guard let visibleRow = visibleRow(at: indexPath) else { + return tableView.rowHeight + } + + return closure(visibleRow, indexPath) } - + return fallbackDelegate?.tableView?(tableView, heightForRowAt: indexPath) ?? tableView.rowHeight } - + public func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { let index = indexPath.section let section = visibleSection(at: index) - + // Do not get the CellDescriptor if we are dealing with a LazySection because it would instantiate all the rows, // i.e. estimatedHeightClosure will not be called for LazySections. Instead, use the estimatedHeight on the // DataSource, the fallback delegate or a constant. - + if !(section is LazySectionType) { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.estimatedHeightClosure { - return closure(visibleRow(at: indexPath), indexPath) + + if let closure = cellDescriptor?.estimatedHeightClosure { + guard let visibleRow = visibleRow(at: indexPath) else { + return UITableView.automaticDimension + } + + return closure(visibleRow, indexPath) } } - + if let closure = estimatedHeight { return closure(indexPath) } - + if let result = fallbackDelegate?.tableView?(tableView, estimatedHeightForRowAt: indexPath) { return result } - + if tableView.estimatedRowHeight > 0 { return tableView.estimatedRowHeight } - + return UITableView.automaticDimension } - + public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { let sectionDescriptor = self.sectionDescriptor(at: section) - + if let closure = sectionDescriptor?.headerHeightClosure ?? sectionHeaderHeight { - return closure(visibleSection(at: section), section).floatValue(for: tableView.style) + guard let visibleSection = visibleSection(at: section) else { + return UITableView.automaticDimension + } + + return closure(visibleSection, section).floatValue(for: tableView.style) } - + if let result = fallbackDelegate?.tableView?(tableView, heightForHeaderInSection: section) { return result } - + if let headerClosure = sectionDescriptor?.headerClosure ?? sectionHeader { - let header = headerClosure(visibleSection(at: section), section) + guard let visibleSection = visibleSection(at: section) else { + return UITableView.automaticDimension + } + let header = headerClosure(visibleSection, section) + switch header { case .title: return UITableView.automaticDimension case .view(let view): let height = view.bounds.height - + if height == 0 { return tableView.sectionHeaderHeight > 0 ? tableView.sectionHeaderHeight : UITableView.automaticDimension } else { @@ -210,30 +254,38 @@ extension DataSource: UITableViewDelegate { return 0.0 } } - + return 0.0 } - + public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { let sectionDescriptor = self.sectionDescriptor(at: section) - + if let closure = sectionDescriptor?.footerHeightClosure ?? sectionFooterHeight { - return closure(visibleSection(at: section), section).floatValue(for: tableView.style) + guard let visibleSection = visibleSection(at: section) else { + return UITableView.automaticDimension + } + + return closure(visibleSection, section).floatValue(for: tableView.style) } - + if let result = fallbackDelegate?.tableView?(tableView, heightForFooterInSection: section) { return result } - + if let footerClosure = sectionDescriptor?.footerClosure ?? sectionFooter { - let footer = footerClosure(visibleSection(at: section), section) + guard let visibleSection = visibleSection(at: section) else { + return UITableView.automaticDimension + } + let footer = footerClosure(visibleSection, section) + switch footer { case .title: return UITableView.automaticDimension case .view(let view): let height = view.bounds.height - + if height == 0 { return tableView.sectionFooterHeight > 0 ? tableView.sectionFooterHeight : UITableView.automaticDimension } else { @@ -243,231 +295,283 @@ extension DataSource: UITableViewDelegate { return 0.0 } } - + return 0.0 } - + // MARK: Display customization - + public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.willDisplayClosure ?? willDisplay { - closure(visibleRow(at: indexPath), cell, indexPath) + + if let closure = cellDescriptor?.willDisplayClosure ?? willDisplay { + guard let visibleRow = visibleRow(at: indexPath) else { + return + } + + closure(visibleRow, cell, indexPath) return } - + fallbackDelegate?.tableView?(tableView, willDisplay: cell, forRowAt: indexPath) } - + public func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { let sectionDescriptor = self.sectionDescriptor(at: section) - + if let closure = sectionDescriptor?.willDisplayHeaderClosure ?? willDisplaySectionHeader { - closure(visibleSection(at: section), view, section) + guard let visibleSection = visibleSection(at: section) else { + return + } + + closure(visibleSection, view, section) return } - + fallbackDelegate?.tableView?(tableView, willDisplayHeaderView: view, forSection: section) } - + public func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) { let sectionDescriptor = self.sectionDescriptor(at: section) - + if let closure = sectionDescriptor?.willDisplayFooterClosure ?? willDisplaySectionFooter { - closure(visibleSection(at: section), view, section) + guard let visibleSection = visibleSection(at: section) else { + return + } + + closure(visibleSection, view, section) return } - + fallbackDelegate?.tableView?(tableView, willDisplayFooterView: view, forSection: section) } - + // the didEnd delegate methods are only supported on a "fallback" level as we may not have the row or section // available at this point anymore and we don't want to keep a reference to old data - + public func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { if let closure = didEndDisplaying { closure(cell, indexPath) return } - + fallbackDelegate?.tableView?(tableView, didEndDisplaying: cell, forRowAt: indexPath) } - + public func tableView(_ tableView: UITableView, didEndDisplayingHeaderView view: UIView, forSection section: Int) { if let closure = didEndDisplayingSectionHeader { closure(view, section) return } - + fallbackDelegate?.tableView?(tableView, didEndDisplayingHeaderView: view, forSection: section) } - + public func tableView(_ tableView: UITableView, didEndDisplayingFooterView view: UIView, forSection section: Int) { if let closure = didEndDisplayingSectionFooter { closure(view, section) return } - + fallbackDelegate?.tableView?(tableView, didEndDisplayingFooterView: view, forSection: section) } - + // MARK: Editing - + public func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.editingStyleClosure ?? editingStyle { - return closure(visibleRow(at: indexPath), indexPath) + + if let closure = cellDescriptor?.editingStyleClosure ?? editingStyle { + guard let visibleRow = visibleRow(at: indexPath) else { + return .none + } + + return closure(visibleRow, indexPath) } - + if let result = fallbackDelegate?.tableView?(tableView, editingStyleForRowAt: indexPath) { return result } - + if self.tableView(tableView, canEditRowAt: indexPath) { return .delete } - + return .none } - + public func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.titleForDeleteConfirmationButtonClosure ?? titleForDeleteConfirmationButton { - return closure(visibleRow(at: indexPath), indexPath) + + if let closure = cellDescriptor?.titleForDeleteConfirmationButtonClosure ?? titleForDeleteConfirmationButton { + guard let visibleRow = visibleRow(at: indexPath) else { + return nil + } + + return closure(visibleRow, indexPath) } - + return fallbackDelegate?.tableView?(tableView, titleForDeleteConfirmationButtonForRowAt: indexPath) } - + public func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.editActionsClosure ?? editActions { - return closure(visibleRow(at: indexPath), indexPath) + + if let closure = cellDescriptor?.editActionsClosure ?? editActions { + guard let visibleRow = visibleRow(at: indexPath) else { + return nil + } + + return closure(visibleRow, indexPath) } - + return fallbackDelegate?.tableView?(tableView, editActionsForRowAt: indexPath) } - + public func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.shouldIndentWhileEditingClosure ?? shouldIndentWhileEditing { - return closure(visibleRow(at: indexPath), indexPath) + + if let closure = cellDescriptor?.shouldIndentWhileEditingClosure ?? shouldIndentWhileEditing { + guard let visibleRow = visibleRow(at: indexPath) else { + return true + } + + return closure(visibleRow, indexPath) } - + return fallbackDelegate?.tableView?(tableView, shouldIndentWhileEditingRowAt: indexPath) ?? true } - + public func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.willBeginEditingClosure ?? willBeginEditing { - closure(visibleRow(at: indexPath), indexPath) + + if let closure = cellDescriptor?.willBeginEditingClosure ?? willBeginEditing { + guard let visibleRow = visibleRow(at: indexPath) else { + return + } + + closure(visibleRow, indexPath) return } - + fallbackDelegate?.tableView?(tableView, willBeginEditingRowAt: indexPath) } - + public func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) { if let closure = didEndEditing { closure(indexPath) return } - + fallbackDelegate?.tableView?(tableView, didEndEditingRowAt: indexPath) } - + // MARK: Moving & Reordering - + public func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath { let cellDescriptor = self.cellDescriptor(at: sourceIndexPath) - - if let closure = cellDescriptor.targetIndexPathForMoveClosure ?? targetIndexPathForMove { - return closure(visibleRow(at: sourceIndexPath), (sourceIndexPath, proposedDestinationIndexPath)) + + if let closure = cellDescriptor?.targetIndexPathForMoveClosure ?? targetIndexPathForMove { + guard let visibleRow = visibleRow(at: sourceIndexPath) else { + return proposedDestinationIndexPath + } + + return closure(visibleRow, (sourceIndexPath, proposedDestinationIndexPath)) } - + return fallbackDelegate?.tableView?(tableView, targetIndexPathForMoveFromRowAt: sourceIndexPath, toProposedIndexPath: proposedDestinationIndexPath) ?? proposedDestinationIndexPath } - + // MARK: Indentation - + public func tableView(_ tableView: UITableView, indentationLevelForRowAt indexPath: IndexPath) -> Int { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.indentationLevelClosure ?? indentationLevel { - return closure(visibleRow(at: indexPath), indexPath) + + if let closure = cellDescriptor?.indentationLevelClosure ?? indentationLevel { + guard let visibleRow = visibleRow(at: indexPath) else { + return 0 + } + + return closure(visibleRow, indexPath) } - + return fallbackDelegate?.tableView?(tableView, indentationLevelForRowAt: indexPath) ?? 0 } - + // MARK: Copy & Paste - + public func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.shouldShowMenuClosure ?? shouldShowMenu { - return closure(visibleRow(at: indexPath), indexPath) + + if let closure = cellDescriptor?.shouldShowMenuClosure ?? shouldShowMenu { + guard let visibleRow = visibleRow(at: indexPath) else { + return false + } + + return closure(visibleRow, indexPath) } - + return fallbackDelegate?.tableView?(tableView, shouldShowMenuForRowAt: indexPath) ?? false } - + public func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.canPerformActionClosure ?? canPerformAction { - return closure(visibleRow(at: indexPath), action, sender, indexPath) + + if let closure = cellDescriptor?.canPerformActionClosure ?? canPerformAction { + guard let visibleRow = visibleRow(at: indexPath) else { + return false + } + + return closure(visibleRow, action, sender, indexPath) } - + return fallbackDelegate?.tableView?(tableView, canPerformAction: action, forRowAt: indexPath, withSender: sender) ?? false } - + public func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) { let cellDescriptor = self.cellDescriptor(at: indexPath) - - if let closure = cellDescriptor.performActionClosure ?? performAction { - closure(visibleRow(at: indexPath), action, sender, indexPath) + + if let closure = cellDescriptor?.performActionClosure ?? performAction { + guard let visibleRow = visibleRow(at: indexPath) else { + return + } + + closure(visibleRow, action, sender, indexPath) return } - + fallbackDelegate?.tableView?(tableView, performAction: action, forRowAt: indexPath, withSender: sender) } - + // MARK: Focus - + public func tableView(_ tableView: UITableView, canFocusRowAt indexPath: IndexPath) -> Bool { return fallbackDelegate?.tableView?(tableView, canFocusRowAt: indexPath) ?? true } - + public func tableView(_ tableView: UITableView, shouldUpdateFocusIn context: UITableViewFocusUpdateContext) -> Bool { return fallbackDelegate?.tableView?(tableView, shouldUpdateFocusIn: context) ?? true } - + public func tableView(_ tableView: UITableView, didUpdateFocusIn context: UITableViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) { fallbackDelegate?.tableView?(tableView, didUpdateFocusIn: context, with: coordinator) } - + public func indexPathForPreferredFocusedView(in tableView: UITableView) -> IndexPath? { return fallbackDelegate?.indexPathForPreferredFocusedView?(in: tableView) } } @available(iOS 11,*) -extension DataSource { - public func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { +public extension DataSource { + func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { if indexPath.indices.count != 2 { // Problem: "Crash in IndexPath.section.getter" // When an indexPath has not exactly two indices, the getter for @@ -477,13 +581,17 @@ extension DataSource { } let cellDescriptor = self.cellDescriptor(at: indexPath) as? CellDescriptorTypeiOS11 if let closure = cellDescriptor?.trailingSwipeActionsClosure { - return closure(visibleRow(at: indexPath), indexPath) + guard let visibleRow = visibleRow(at: indexPath) else { + return nil + } + + return closure(visibleRow, indexPath) } else { return fallbackDelegate?.tableView?(tableView, trailingSwipeActionsConfigurationForRowAt: indexPath) } } - - public func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + + func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { if indexPath.indices.count != 2 { // Problem: "Crash in IndexPath.section.getter" // When an indexPath has not exactly two indices, the getter for @@ -493,7 +601,11 @@ extension DataSource { } let cellDescriptor = self.cellDescriptor(at: indexPath) as? CellDescriptorTypeiOS11 if let closure = cellDescriptor?.leadingSwipeActionsClosure { - return closure(visibleRow(at: indexPath), indexPath) + guard let visibleRow = visibleRow(at: indexPath) else { + return nil + } + + return closure(visibleRow, indexPath) } else { return fallbackDelegate?.tableView?(tableView, leadingSwipeActionsConfigurationForRowAt: indexPath) } @@ -501,8 +613,8 @@ extension DataSource { } @available(iOS 13,*) -extension DataSource { - public func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { +public extension DataSource { + func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point _: CGPoint) -> UIContextMenuConfiguration? { if indexPath.indices.count != 2 { // Problem: "Crash in IndexPath.section.getter" // When an indexPath has not exactly two indices, the getter for @@ -511,9 +623,13 @@ extension DataSource { return nil } let cellDescriptor = self.cellDescriptor(at: indexPath) as? CellDescriptorTypeiOS13 - + if let closure = cellDescriptor?.configurationForMenuAtLocationClosure { - return closure(visibleRow(at: indexPath), indexPath) + guard let visibleRow = visibleRow(at: indexPath) else { + return nil + } + + return closure(visibleRow, indexPath) } else { return fallbackDelegate?.tableView?(tableView, contextMenuConfigurationForRowAt: indexPath, point: CGPoint(x: 0, y: 0)) } diff --git a/DataSource/DataSource.swift b/DataSource/DataSource.swift index c7eb78f..1d0d454 100644 --- a/DataSource/DataSource.swift +++ b/DataSource/DataSource.swift @@ -1,20 +1,12 @@ -// -// DataSource.swift -// DataSource -// -// Created by Matthias Buchetics on 21/02/2017. -// Copyright © 2017 aaa - all about apps GmbH. All rights reserved. -// - import Differ import UIKit public class DataSource: NSObject { public var sections: [SectionType] = [] public internal(set) var visibleSections: [SectionType] = [] - + // MARK: UITableViewDataSource - + public var configure: ((RowType, UITableViewCell, IndexPath) -> Void)? public var canEdit: ((RowType, IndexPath) -> Bool)? public var canMove: ((RowType, IndexPath) -> Bool)? @@ -22,80 +14,80 @@ public class DataSource: NSObject { public var sectionForSectionIndex: ((String, Int) -> Int)? public var commitEditing: ((RowType, UITableViewCell.EditingStyle, IndexPath) -> Void)? public var moveRow: ((RowType, (IndexPath, IndexPath)) -> Void)? - + public var fallbackDataSource: UITableViewDataSource? - + // MARK: UITableViewDelegate - + public var height: ((RowType, IndexPath) -> CGFloat)? - + // no RowType parameter for estimatedHeight because we do not want to potentially instantiate // a LazyRow just to get the height estimate public var estimatedHeight: ((IndexPath) -> CGFloat)? - + public var shouldHighlight: ((RowType, IndexPath) -> Bool)? public var didHighlight: ((RowType, IndexPath) -> Void)? public var didUnhighlight: ((RowType, IndexPath) -> Void)? - + public var willSelect: ((RowType, IndexPath) -> IndexPath?)? public var willDeselect: ((RowType, IndexPath) -> IndexPath?)? public var didSelect: ((RowType, IndexPath) -> SelectionResult)? public var didDeselect: ((RowType, IndexPath) -> Void)? - + public var willDisplay: ((RowType, UITableViewCell, IndexPath) -> Void)? public var didEndDisplaying: ((UITableViewCell, IndexPath) -> Void)? - + public var editingStyle: ((RowType, IndexPath) -> UITableViewCell.EditingStyle)? public var titleForDeleteConfirmationButton: ((RowType, IndexPath) -> String?)? public var editActions: ((RowType, IndexPath) -> [UITableViewRowAction]?)? public var shouldIndentWhileEditing: ((RowType, IndexPath) -> Bool)? public var willBeginEditing: ((RowType, IndexPath) -> Void)? public var didEndEditing: ((IndexPath?) -> Void)? - + public var sectionHeader: ((SectionType, Int) -> HeaderFooter)? public var sectionFooter: ((SectionType, Int) -> HeaderFooter)? - + public var sectionHeaderHeight: ((SectionType, Int) -> SectionHeight)? public var sectionFooterHeight: ((SectionType, Int) -> SectionHeight)? - + public var willDisplaySectionHeader: ((SectionType, UIView, Int) -> Void)? public var willDisplaySectionFooter: ((SectionType, UIView, Int) -> Void)? - + public var didEndDisplayingSectionHeader: ((UIView, Int) -> Void)? public var didEndDisplayingSectionFooter: ((UIView, Int) -> Void)? - + public var targetIndexPathForMove: ((RowType, (IndexPath, IndexPath)) -> IndexPath)? public var indentationLevel: ((RowType, IndexPath) -> Int)? public var shouldShowMenu: ((RowType, IndexPath) -> Bool)? public var canPerformAction: ((RowType, Selector, Any?, IndexPath) -> Bool)? public var performAction: ((RowType, Selector, Any?, IndexPath) -> Void)? public var canFocus: ((RowType, IndexPath) -> Bool)? - + // MARK: Swipe Actions (iOS 11+ only) - + private var _leadingSwipeActions: ((RowType, IndexPath) -> Any?)? private var _trailingSwipeActions: ((RowType, IndexPath) -> Any?)? - + public var _configurationForMenuAtLocationClosure: ((RowType, IndexPath) -> Any)? - + // MARK: UITableViewDataSourcePrefetching - + public var prefetchRows: (([IndexPath]) -> Void)? public var cancelPrefetching: (([IndexPath]) -> Void)? - + public weak var fallbackDataSourcePrefetching: UITableViewDataSourcePrefetching? - + // MARK: Fallback delegate - + /// Fallback used when DataSource doesn't handle delegate method itself. /// - Note: The fallback delegate needs to be set *before* setting the table view's delegate, otherwise certain delegate methods will never be called. public weak var fallbackDelegate: UITableViewDelegate? - - public override func forwardingTarget(for aSelector: Selector!) -> Any? { + + override public func forwardingTarget(for _: Selector!) -> Any? { return fallbackDelegate } - - public override func responds(to aSelector: Selector!) -> Bool { + + override public func responds(to aSelector: Selector!) -> Bool { if super.responds(to: aSelector) { if #available(iOS 11.0, *) { switch aSelector { @@ -110,128 +102,152 @@ public class DataSource: NSObject { return true } } - + return fallbackDelegateResponds(to: aSelector) } - + private func fallbackDelegateResponds(to aSelector: Selector!) -> Bool { let result = fallbackDelegate?.responds(to: aSelector) ?? false return result } - + @available(iOS 11.0, *) private var isLeadingSwipeActionsImplemented: Bool { if _leadingSwipeActions != nil { return true } - + return cellDescriptors.values.contains(where: { ($0 as? CellDescriptorTypeiOS11)?.leadingSwipeActionsClosure != nil }) } - + @available(iOS 11.0, *) private var isTrailingSwipeActionsImplemented: Bool { if _trailingSwipeActions != nil { return true } - + return cellDescriptors.values.contains(where: { ($0 as? CellDescriptorTypeiOS11)?.trailingSwipeActionsClosure != nil }) } - + // MARK: ContextMenu - + // MARK: Additional - + public var isRowHidden: ((RowType, IndexPath) -> Bool)? public var isSectionHidden: ((SectionType, Int) -> Bool)? public var update: ((RowType, UITableViewCell, IndexPath) -> Void)? - + // MARK: Internal - + var cellDescriptors: [String: CellDescriptorType] = [:] var sectionDescriptors: [String: SectionDescriptorType] = [:] - + var reuseIdentifiers: Set = [] let registerNibs: Bool - + // MARK: Init - + public init(cellDescriptors: [CellDescriptorType], sectionDescriptors: [SectionDescriptorType] = [], registerNibs: Bool = true) { self.registerNibs = registerNibs super.init() - + for d in cellDescriptors { self.cellDescriptors[d.rowIdentifier] = d } - + addDescriptorIfNotSet(SeparatorLineCell.descriptorLine) addDescriptorIfNotSet(SeparatorLineCell.descriptorCustomView) - + let defaultSectionDescriptors: [SectionDescriptorType] = [ SectionDescriptor(), SectionDescriptor() .header { title, _ in .title(title) - } + }, ] - + for d in defaultSectionDescriptors { self.sectionDescriptors[d.identifier] = d } - + for d in sectionDescriptors { self.sectionDescriptors[d.identifier] = d } } - + private func addDescriptorIfNotSet(_ descriptor: CellDescriptorType) { if cellDescriptors[descriptor.rowIdentifier] == nil { cellDescriptors[descriptor.rowIdentifier] = descriptor } } - + // MARK: Getters & Setters - - public func section(at index: Int) -> SectionType { + + public func section(at index: Int) -> SectionType? { + guard sections.count > index else { + return nil + } + return sections[index] } - - public func row(at indexPath: IndexPath) -> RowType { - return sections[indexPath.section].row(at: indexPath.row) + + public func row(at indexPath: IndexPath) -> RowType? { + guard sections.count > indexPath.section else { + return nil + } + + let section = sections[indexPath.section] + + guard section.numberOfRows > indexPath.row else { + return nil + } + + return section.row(at: indexPath.row) } - - public func visibleSection(at index: Int) -> SectionType { + + public func visibleSection(at index: Int) -> SectionType? { + guard visibleSections.count > index else { + return nil + } + return visibleSections[index] } - - public func visibleRow(at indexPath: IndexPath) -> RowType { - return visibleSections[indexPath.section].visibleRow(at: indexPath.row) - } - - // MARK: Cell Descriptors - - public func cellDescriptor(at indexPath: IndexPath) -> CellDescriptorType { - let row = visibleRow(at: indexPath) + + public func visibleRow(at indexPath: IndexPath) -> RowType? { + guard visibleSections.count > indexPath.section else { + return nil + } - if let cellDescriptor = cellDescriptors[row.identifier] { - return cellDescriptor - } else { - fatalError("[DataSource] no cellDescriptor found for indexPath \(indexPath) with identifier \(row.identifier)") + let visibleSection = visibleSections[indexPath.section] + + guard visibleSection.numberOfVisibleRows > indexPath.row else { + return nil } + + return visibleSection.visibleRow(at: indexPath.row) } - - public func cellDescriptor(for rowIdentifier: String) -> CellDescriptorType { - if let cellDescriptor = cellDescriptors[rowIdentifier] { - return cellDescriptor - } else { - fatalError("[DataSource] no cellDescriptor found for rowIdentifier \(rowIdentifier)") + + // MARK: Cell Descriptors + + public func cellDescriptor(at indexPath: IndexPath) -> CellDescriptorType? { + guard let row = visibleRow(at: indexPath) else { + return nil } + + return cellDescriptors[row.identifier] + } + + public func cellDescriptor(for rowIdentifier: String) -> CellDescriptorType? { + cellDescriptors[rowIdentifier] } - + // MARK: Section Descriptors - + public func sectionDescriptor(at index: Int) -> SectionDescriptorType? { - let section = visibleSection(at: index) - + guard let section = visibleSection(at: index) else { + return nil + } + if let sectionDescriptor = sectionDescriptors[section.identifier] { return sectionDescriptor } else { @@ -239,7 +255,7 @@ public class DataSource: NSObject { return nil } } - + public func sectionDescriptor(for identifier: String) -> SectionDescriptorType? { if let sectionDescriptor = sectionDescriptors[identifier] { return sectionDescriptor @@ -248,47 +264,47 @@ public class DataSource: NSObject { return nil } } - + // MARK: Visibility - + internal func updateVisibility() { visibleSections = updateSectionVisiblity() } - + internal func updateSectionVisiblity() -> [SectionType] { var visibleSections = [SectionType]() - + for (index, section) in sections.enumerated() { section.updateVisibility(sectionIndex: index, dataSource: self) - + let sectionDescriptor = self.sectionDescriptor(for: section.identifier) let isHidden: Bool - + if let closure = sectionDescriptor?.isHiddenClosure ?? isSectionHidden { isHidden = closure(section, index) } else { isHidden = false } - + if isHidden == false, section.numberOfVisibleRows > 0 { visibleSections.append(section) } } - + return visibleSections } - + // MARK: Reload Data - + public func reloadData(_ tableView: UITableView, animated: Bool) { if tableView.dataSource == nil { tableView.dataSource = self } - + if tableView.delegate == nil { tableView.delegate = self } - + if animated { reloadDataAnimated(tableView) } else { @@ -296,7 +312,7 @@ public class DataSource: NSObject { tableView.reloadData() } } - + public func reloadDataAnimated( _ tableView: UITableView, rowDeletionAnimation: UITableView.RowAnimation = .fade, @@ -308,18 +324,18 @@ public class DataSource: NSObject { let oldSections = visibleSections.map { $0.diffableSection } let newSections = updateSectionVisiblity() let diffableNewSections = newSections.map { $0.diffableSection } - + let diff = computeDiff(oldSections: oldSections, newSections: diffableNewSections) let updates = computeUpdate(oldSections: oldSections, newSections: diffableNewSections) - + visibleSections = newSections - + if rowReloadAnimation == .none { for update in updates { updateRow(tableView, row: update.to.row, at: update.from.indexPath) } } - + tableView.apply( batch: Batch(diff: diff, updates: updates), rowDeletionAnimation: rowDeletionAnimation, @@ -329,15 +345,15 @@ public class DataSource: NSObject { sectionInsertionAnimation: sectionInsertionAnimation ) } - + public func updateRow(_ tableView: UITableView, row: RowType, at indexPath: IndexPath) { let cellDescriptor = self.cellDescriptor(for: row.identifier) - - let updateClosure = cellDescriptor.updateClosure ?? update - let configureClosure = cellDescriptor.configureClosure ?? configure - + + let updateClosure = cellDescriptor?.updateClosure ?? update + let configureClosure = cellDescriptor?.configureClosure ?? configure + let closure = updateClosure ?? configureClosure - + if let cell = tableView.cellForRow(at: indexPath), let closure = closure { closure(row, cell, indexPath) } @@ -345,13 +361,13 @@ public class DataSource: NSObject { } @available(iOS 11,*) -extension DataSource { - public var leadingSwipeActions: ((RowType, IndexPath) -> UISwipeActionsConfiguration?)? { +public extension DataSource { + var leadingSwipeActions: ((RowType, IndexPath) -> UISwipeActionsConfiguration?)? { get { if _leadingSwipeActions == nil { return nil } - + return { [weak self] rowType, indexPath in self?._leadingSwipeActions?(rowType, indexPath) as? UISwipeActionsConfiguration } @@ -360,13 +376,13 @@ extension DataSource { _leadingSwipeActions = newValue } } - - public var trailingSwipeActions: ((RowType, IndexPath) -> UISwipeActionsConfiguration?)? { + + var trailingSwipeActions: ((RowType, IndexPath) -> UISwipeActionsConfiguration?)? { get { if _trailingSwipeActions == nil { return nil } - + return { [weak self] rowType, indexPath in self?._trailingSwipeActions?(rowType, indexPath) as? UISwipeActionsConfiguration } @@ -378,18 +394,18 @@ extension DataSource { } @available(iOS 13,*) -extension DataSource { - public var configurationForMenuAtLocationClosure: ((RowType, IndexPath) -> UIContextMenuConfiguration?)? { +public extension DataSource { + var configurationForMenuAtLocationClosure: ((RowType, IndexPath) -> UIContextMenuConfiguration?)? { get { if _configurationForMenuAtLocationClosure == nil { return nil } - + return { [weak self] rowType, indexPath in self?._configurationForMenuAtLocationClosure?(rowType, indexPath) as? UIContextMenuConfiguration } } - + set { _configurationForMenuAtLocationClosure = newValue } diff --git a/DataSource/Row.swift b/DataSource/Row.swift index 410bb27..b8a6bb0 100644 --- a/DataSource/Row.swift +++ b/DataSource/Row.swift @@ -1,17 +1,9 @@ -// -// Row.swift -// DataSource -// -// Created by Matthias Buchetics on 21/02/2017. -// Copyright © 2017 aaa - all about apps GmbH. All rights reserved. -// - import Foundation // MARK: - RowType public protocol RowType { - + var identifier: String { get } var item: Any { get } var diffableItem: Diffable? { get } @@ -20,19 +12,19 @@ public protocol RowType { // MARK: - Row public struct Row: RowType { - + public let identifier: String public let item: Any - + public var diffableItem: Diffable? { return item as? Diffable } - + public init(_ item: Any, identifier: String? = nil) { self.identifier = identifier ?? String(describing: type(of: item)) self.item = item } - + public func with(identifier: String) -> Row { return Row(item, identifier: identifier) } @@ -41,34 +33,34 @@ public struct Row: RowType { // MARK: - LazyRowType public protocol LazyRowType: RowType { - + var anyItemClosure: () -> Any { get } } // MARK: - LazyRow public struct LazyRow: LazyRowType { - + public let identifier: String public let itemClosure: () -> Item public var item: Any { return itemClosure() } - + public var anyItemClosure: () -> Any { return itemClosure } - + public var diffableItem: Diffable? { return nil } - + public init(_ item: @escaping () -> Item, identifier: String? = nil) { self.identifier = identifier ?? String(describing: Item.self) - self.itemClosure = item + itemClosure = item } - + public func with(identifier: String) -> LazyRow { return LazyRow(itemClosure, identifier: identifier) } @@ -76,9 +68,9 @@ public struct LazyRow: LazyRowType { // MARK: - Extensions -extension Array { - - public func rows(_ identifier: String? = nil) -> [Row] { - return self.map { Row($0, identifier: identifier) } +public extension Array { + + func rows(_ identifier: String? = nil) -> [Row] { + return map { Row($0, identifier: identifier) } } } diff --git a/DataSource/Section.swift b/DataSource/Section.swift index d95870d..528d604 100644 --- a/DataSource/Section.swift +++ b/DataSource/Section.swift @@ -15,6 +15,7 @@ public protocol SectionType { var identifier: String { get } var content: Any? { get } var numberOfVisibleRows: Int { get } + var numberOfRows: Int { get } var diffableSection: DiffableSection { get } @@ -60,6 +61,10 @@ open class Section: SectionType { return visibleRows.count } + public var numberOfRows: Int { + return rows.count + } + public func row(at index: Int) -> RowType { return rows[index] } @@ -141,6 +146,10 @@ public class LazySection: LazySectionType { return rowCount() } + public var numberOfRows: Int { + rowCount() + } + // there is no difference between row and visibleRow for on-demand sections, // you should only return visible rows diff --git a/DataSourceTests/ConcurrentReloadSelectTest.swift b/DataSourceTests/ConcurrentReloadSelectTest.swift new file mode 100644 index 0000000..a490670 --- /dev/null +++ b/DataSourceTests/ConcurrentReloadSelectTest.swift @@ -0,0 +1,125 @@ +import Foundation +import XCTest + +@testable import DataSource + +public extension XCTestCase { + var given: Given { Given() } + var when: When { When() } + var then: Then { Then() } +} + +public struct Given {} +public struct When {} +public struct Then {} + +class ConcurrentReloadSelectTest: XCTestCase { + class State { + var tableView: UITableView! + + lazy var dataSource: DataSource = { + DataSource(cellDescriptors: [MockCell.descriptor]) + }() + } + + var state: State! + + private lazy var dataSource: DataSource = { + DataSource(cellDescriptors: []) + }() + + override func setUp() { + super.setUp() + state = State() + } + + override func tearDown() { + state = nil + super.tearDown() + } + + func testSectionRemoved() async { + await given + .createTableView(state: state) + .setupTableView(state: state) + + await when + .setMockCells(state: state) + + await then + .selectCellAt(50, state: state) + .emptyTableView(state: state) + } + + func testElementRemoved() async { + await given + .createTableView(state: state) + .setupTableView(state: state) + + await when + .setMockCells(state: state) + + await then + .selectCellAt(50, state: state) + .removeItemsFromSection(state: state) + } +} + +extension Given { + @discardableResult @MainActor + func createTableView(state: ConcurrentReloadSelectTest.State) -> Given { + state.tableView = UITableView() + return self + } + + @discardableResult @MainActor + func setupTableView(state: ConcurrentReloadSelectTest.State) -> Given { + state.tableView.delegate = state.dataSource + state.tableView.dataSource = state.dataSource + return self + } +} + +extension When { + @discardableResult + func setMockCells(state: ConcurrentReloadSelectTest.State) async -> When { + let section = Section(items: [MockCellViewModel].init(repeating: .init(), count: 100)) + state.dataSource.sections = [section] + state.dataSource.reloadData(state.tableView, animated: true) + return self + } +} + +extension Then { + @discardableResult + func selectCellAt(_ index: Int, state: ConcurrentReloadSelectTest.State) async -> Then { + await state.tableView.selectRow(at: .init(row: index, section: 0), animated: true, scrollPosition: .none) + return self + } + + @discardableResult + func emptyTableView(state: ConcurrentReloadSelectTest.State) async -> Then { + state.dataSource.sections = [] + state.dataSource.reloadData(state.tableView, animated: true) + return self + } + + @discardableResult + func removeItemsFromSection(state: ConcurrentReloadSelectTest.State) async -> Then { + state.dataSource.sections = [Section(items: [Section(items: [MockCellViewModel()])])] + state.dataSource.reloadData(state.tableView, animated: true) + return self + } +} + +class MockCell: UITableViewCell, AutoRegisterCell { + public func configure(viewModel: MockCellViewModel) { } + + static var descriptor: CellDescriptor { + return CellDescriptor().configure { viewModel, cell, _ in + cell.configure(viewModel: viewModel) + } + } +} + +class MockCellViewModel {} diff --git a/DataSourceTests/DataSourceTests.swift b/DataSourceTests/DataSourceTests.swift deleted file mode 100644 index e4d8cf0..0000000 --- a/DataSourceTests/DataSourceTests.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// DataSourceTests.swift -// DataSourceTests -// -// Created by Matthias Buchetics on 22/02/2017. -// Copyright © 2017 aaa - all about apps GmbH. All rights reserved. -// - -import XCTest - -@testable import DataSource - -class DataSourceTests: XCTestCase { - - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - - func testExample() { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testPerformanceExample() { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} From bae2a88f0afad36e9543375cf3764f8a756f6c83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Mu=CC=88hleder?= Date: Thu, 13 Jul 2023 11:36:29 +0200 Subject: [PATCH 27/31] Adapt fallback return values --- DataSource/DataSource+UITableViewDelegate.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DataSource/DataSource+UITableViewDelegate.swift b/DataSource/DataSource+UITableViewDelegate.swift index f93110b..809e9fc 100644 --- a/DataSource/DataSource+UITableViewDelegate.swift +++ b/DataSource/DataSource+UITableViewDelegate.swift @@ -56,7 +56,7 @@ extension DataSource: UITableViewDelegate { if let closure = cellDescriptor?.willSelectClosure ?? willSelect { guard let visibleRow = visibleRow(at: indexPath) else { - return nil + return indexPath } return closure(visibleRow, indexPath) @@ -71,7 +71,7 @@ extension DataSource: UITableViewDelegate { if let closure = cellDescriptor?.willDeselectClosure ?? willDeselect { guard let visibleRow = visibleRow(at: indexPath) else { - return nil + return indexPath } return closure(visibleRow, indexPath) @@ -234,7 +234,7 @@ extension DataSource: UITableViewDelegate { if let headerClosure = sectionDescriptor?.headerClosure ?? sectionHeader { guard let visibleSection = visibleSection(at: section) else { - return UITableView.automaticDimension + return 0.0 } let header = headerClosure(visibleSection, section) @@ -275,7 +275,7 @@ extension DataSource: UITableViewDelegate { if let footerClosure = sectionDescriptor?.footerClosure ?? sectionFooter { guard let visibleSection = visibleSection(at: section) else { - return UITableView.automaticDimension + return 0.0 } let footer = footerClosure(visibleSection, section) From b5e567a16536ebd7f7a7875c3d5747a5070e5317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Mu=CC=88hleder?= Date: Thu, 13 Jul 2023 11:40:57 +0200 Subject: [PATCH 28/31] Remove unused data source from test --- .../xcode/package.xcworkspace/contents.xcworkspacedata | 7 +++++++ DataSourceTests/ConcurrentReloadSelectTest.swift | 6 +----- 2 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/DataSourceTests/ConcurrentReloadSelectTest.swift b/DataSourceTests/ConcurrentReloadSelectTest.swift index a490670..09a7789 100644 --- a/DataSourceTests/ConcurrentReloadSelectTest.swift +++ b/DataSourceTests/ConcurrentReloadSelectTest.swift @@ -22,11 +22,7 @@ class ConcurrentReloadSelectTest: XCTestCase { }() } - var state: State! - - private lazy var dataSource: DataSource = { - DataSource(cellDescriptors: []) - }() + var state: State!return 0.0return 0.0 override func setUp() { super.setUp() From 36e0c99b21ab55b574bfbad42e0da65af221117d Mon Sep 17 00:00:00 2001 From: Stefan Draskovits Date: Thu, 20 Jul 2023 11:44:17 +0200 Subject: [PATCH 29/31] refactor unit tests --- .../ConcurrentReloadSelectTest.swift | 130 ++++++++++++------ Package.swift | 3 +- 2 files changed, 89 insertions(+), 44 deletions(-) diff --git a/DataSourceTests/ConcurrentReloadSelectTest.swift b/DataSourceTests/ConcurrentReloadSelectTest.swift index 09a7789..39c9d6c 100644 --- a/DataSourceTests/ConcurrentReloadSelectTest.swift +++ b/DataSourceTests/ConcurrentReloadSelectTest.swift @@ -1,8 +1,7 @@ +@testable import DataSource import Foundation import XCTest -@testable import DataSource - public extension XCTestCase { var given: Given { Given() } var when: When { When() } @@ -15,60 +14,76 @@ public struct Then {} class ConcurrentReloadSelectTest: XCTestCase { class State { - var tableView: UITableView! - + let tableView = UITableView() + lazy var dataSource: DataSource = { - DataSource(cellDescriptors: [MockCell.descriptor]) + DataSource(cellDescriptors: [ + MockCell.descriptor + .didSelect { [weak self] viewModel, _ in + self?.selectedViewModel = viewModel + return .deselect + }, + ]) }() + + var selectedViewModel: MockCellViewModel? } - - var state: State!return 0.0return 0.0 - + + var state: State! + override func setUp() { super.setUp() state = State() } - + override func tearDown() { state = nil super.tearDown() } - + func testSectionRemoved() async { await given - .createTableView(state: state) .setupTableView(state: state) - + await when - .setMockCells(state: state) + .populateData(state: state, numberOfRows: 100) + .emptyTableView(state: state) + .selectCellAt(150, state: state) await then - .selectCellAt(50, state: state) - .emptyTableView(state: state) + .nothingSelected(state: state) } - func testElementRemoved() async { + func testSelection() async { await given - .createTableView(state: state) .setupTableView(state: state) - + await when - .setMockCells(state: state) + .populateData(state: state, numberOfRows: 100) + .selectCellAt(50, state: state) - await then + then + .selectedId(state: state, id: 50) + } + + func testElementRemoved() async { + await given + .setupTableView(state: state) + + await when + .populateData(state: state, numberOfRows: 100) .selectCellAt(50, state: state) .removeItemsFromSection(state: state) + + then + .selectedId(state: state, id: 50) } } extension Given { - @discardableResult @MainActor - func createTableView(state: ConcurrentReloadSelectTest.State) -> Given { - state.tableView = UITableView() - return self - } - - @discardableResult @MainActor + + @discardableResult + @MainActor func setupTableView(state: ConcurrentReloadSelectTest.State) -> Given { state.tableView.delegate = state.dataSource state.tableView.dataSource = state.dataSource @@ -77,40 +92,63 @@ extension Given { } extension When { + @discardableResult - func setMockCells(state: ConcurrentReloadSelectTest.State) async -> When { - let section = Section(items: [MockCellViewModel].init(repeating: .init(), count: 100)) + @MainActor + func populateData(state: ConcurrentReloadSelectTest.State, numberOfRows: Int) -> Self { + let items = Array(0.. Then { - await state.tableView.selectRow(at: .init(row: index, section: 0), animated: true, scrollPosition: .none) + @MainActor + func selectCellAt(_ index: Int, state: ConcurrentReloadSelectTest.State) -> Self { + state.dataSource.tableView(state.tableView, didSelectRowAt: .init(row: index, section: 0)) return self } - + @discardableResult - func emptyTableView(state: ConcurrentReloadSelectTest.State) async -> Then { + @MainActor + func emptyTableView(state: ConcurrentReloadSelectTest.State) async -> Self { state.dataSource.sections = [] - state.dataSource.reloadData(state.tableView, animated: true) + state.dataSource.reloadData(state.tableView, animated: false) + return self + } + + @discardableResult + @MainActor + func removeItemsFromSection(state: ConcurrentReloadSelectTest.State) async -> Self { + state.dataSource.sections = [Section(items: [Section(items: [MockCellViewModel(id: 0)])])] + state.dataSource.reloadData(state.tableView, animated: false) + return self + } +} + +extension Then { + @discardableResult + func nothingSelected(state: ConcurrentReloadSelectTest.State) async -> Self { + XCTAssertNil(state.selectedViewModel, "nothing should be selected but element with id \(state.selectedViewModel!.id) was selected") return self } @discardableResult - func removeItemsFromSection(state: ConcurrentReloadSelectTest.State) async -> Then { - state.dataSource.sections = [Section(items: [Section(items: [MockCellViewModel()])])] - state.dataSource.reloadData(state.tableView, animated: true) + func selectedId(state: ConcurrentReloadSelectTest.State, id: Int) -> Self { + do { + let selectedId = try XCTUnwrap(state.selectedViewModel?.id, "there should have been a selection") + XCTAssertEqual(selectedId, id, "mismatch on id selection: selected \(selectedId) but should have been \(id)") + } catch { + XCTFail("unwrap of selected cell failed") + } return self } } class MockCell: UITableViewCell, AutoRegisterCell { - public func configure(viewModel: MockCellViewModel) { } - + public func configure(viewModel _: MockCellViewModel) {} + static var descriptor: CellDescriptor { return CellDescriptor().configure { viewModel, cell, _ in cell.configure(viewModel: viewModel) @@ -118,4 +156,10 @@ class MockCell: UITableViewCell, AutoRegisterCell { } } -class MockCellViewModel {} +class MockCellViewModel { + let id: Int + + init(id: Int) { + self.id = id + } +} diff --git a/Package.swift b/Package.swift index d6de793..1836ca3 100644 --- a/Package.swift +++ b/Package.swift @@ -17,7 +17,8 @@ let package = Package( .package(url: "https://github.com/tonyarnold/Differ.git", from: "1.4.3") ], targets: [ - .target(name: "DataSource", dependencies: ["Differ"], path: "DataSource/") + .target(name: "DataSource", dependencies: ["Differ"], path: "DataSource/"), + .testTarget(name: "DataSourceTests", dependencies: ["DataSource"], path: "DataSourceTests/") ], swiftLanguageVersions: [.v5] ) From 4967f0eb1343cfedd4ab1f40c05afc2ea52f7d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Mu=CC=88hleder?= Date: Mon, 24 Jul 2023 08:51:31 +0200 Subject: [PATCH 30/31] Don't select/deselect if row not found --- DataSource/DataSource+UITableViewDelegate.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DataSource/DataSource+UITableViewDelegate.swift b/DataSource/DataSource+UITableViewDelegate.swift index 809e9fc..8529c6d 100644 --- a/DataSource/DataSource+UITableViewDelegate.swift +++ b/DataSource/DataSource+UITableViewDelegate.swift @@ -56,7 +56,7 @@ extension DataSource: UITableViewDelegate { if let closure = cellDescriptor?.willSelectClosure ?? willSelect { guard let visibleRow = visibleRow(at: indexPath) else { - return indexPath + return nil } return closure(visibleRow, indexPath) @@ -71,7 +71,7 @@ extension DataSource: UITableViewDelegate { if let closure = cellDescriptor?.willDeselectClosure ?? willDeselect { guard let visibleRow = visibleRow(at: indexPath) else { - return indexPath + return nil } return closure(visibleRow, indexPath) From 921f9b92afb587b5a99361f631d41838ad261501 Mon Sep 17 00:00:00 2001 From: Mathias Poimer <67694170+mpoimer@users.noreply.github.com> Date: Wed, 8 May 2024 16:57:23 +0200 Subject: [PATCH 31/31] Fixed crash when scrolling tableView with VoiceOver enabled (#42) --- DataSource/DataSource+UITableViewDelegate.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DataSource/DataSource+UITableViewDelegate.swift b/DataSource/DataSource+UITableViewDelegate.swift index 8529c6d..811a815 100644 --- a/DataSource/DataSource+UITableViewDelegate.swift +++ b/DataSource/DataSource+UITableViewDelegate.swift @@ -415,6 +415,8 @@ extension DataSource: UITableViewDelegate { } public func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { + guard !indexPath.isEmpty else { return [] } + let cellDescriptor = self.cellDescriptor(at: indexPath) if let closure = cellDescriptor?.editActionsClosure ?? editActions {