From c2d0fdecd2b6d4a8dcc9ed688878d03bc07474bb Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 6 Jul 2018 15:00:38 +0600 Subject: [PATCH 01/54] Global protocols/classes renaming --- CollectionViewTools.xcodeproj/project.pbxproj | 24 ++-- .../CellItems/ImageCellItem.swift | 2 +- .../MainViewController.swift | 4 +- Sources/CollectionViewManager.swift | 28 ++--- ...=> GeneralCollectionViewSectionItem.swift} | 12 +- ...ionViewCellItemDataSourcePrefetching.swift | 4 +- .../CollectionViewCellItemProtocol.swift | 105 ++++++++++++------ ...t => CollectionViewReusableViewItem.swift} | 4 +- ....swift => CollectionViewSectionItem.swift} | 10 +- 9 files changed, 117 insertions(+), 76 deletions(-) rename Sources/Items/{CollectionViewSectionItem.swift => GeneralCollectionViewSectionItem.swift} (73%) rename Sources/Protocols/ReuseViewItem/{CollectionViewReusableViewItemProtocol.swift => CollectionViewReusableViewItem.swift} (86%) rename Sources/Protocols/SectionItem/{CollectionViewSectionItemProtocol.swift => CollectionViewSectionItem.swift} (74%) diff --git a/CollectionViewTools.xcodeproj/project.pbxproj b/CollectionViewTools.xcodeproj/project.pbxproj index 38fee7a..f2b6d75 100644 --- a/CollectionViewTools.xcodeproj/project.pbxproj +++ b/CollectionViewTools.xcodeproj/project.pbxproj @@ -18,11 +18,11 @@ 0A37636D1F62545A00A80613 /* CollectionViewManager+UICollectionViewDelegateFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763591F62545A00A80613 /* CollectionViewManager+UICollectionViewDelegateFlowLayout.swift */; }; 0A37636E1F62545A00A80613 /* CollectionViewManager+UIScrollViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A37635A1F62545A00A80613 /* CollectionViewManager+UIScrollViewDelegate.swift */; }; 0A37636F1F62545A00A80613 /* UICollectionView+Registration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A37635B1F62545A00A80613 /* UICollectionView+Registration.swift */; }; - 0A3763711F62545A00A80613 /* CollectionViewSectionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A37635E1F62545A00A80613 /* CollectionViewSectionItem.swift */; }; + 0A3763711F62545A00A80613 /* GeneralCollectionViewSectionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A37635E1F62545A00A80613 /* GeneralCollectionViewSectionItem.swift */; }; 0A3763721F62545A00A80613 /* CollectionViewCellItemDataSourcePrefetching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763611F62545A00A80613 /* CollectionViewCellItemDataSourcePrefetching.swift */; }; 0A3763731F62545A00A80613 /* CollectionViewCellItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763621F62545A00A80613 /* CollectionViewCellItemProtocol.swift */; }; - 0A3763741F62545A00A80613 /* CollectionViewReusableViewItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763641F62545A00A80613 /* CollectionViewReusableViewItemProtocol.swift */; }; - 0A3763751F62545A00A80613 /* CollectionViewSectionItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763661F62545A00A80613 /* CollectionViewSectionItemProtocol.swift */; }; + 0A3763741F62545A00A80613 /* CollectionViewReusableViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763641F62545A00A80613 /* CollectionViewReusableViewItem.swift */; }; + 0A3763751F62545A00A80613 /* CollectionViewSectionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763661F62545A00A80613 /* CollectionViewSectionItem.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -50,11 +50,11 @@ 0A37635A1F62545A00A80613 /* CollectionViewManager+UIScrollViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CollectionViewManager+UIScrollViewDelegate.swift"; sourceTree = ""; }; 0A37635B1F62545A00A80613 /* UICollectionView+Registration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Registration.swift"; sourceTree = ""; }; 0A37635C1F62545A00A80613 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 0A37635E1F62545A00A80613 /* CollectionViewSectionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewSectionItem.swift; sourceTree = ""; }; + 0A37635E1F62545A00A80613 /* GeneralCollectionViewSectionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralCollectionViewSectionItem.swift; sourceTree = ""; }; 0A3763611F62545A00A80613 /* CollectionViewCellItemDataSourcePrefetching.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCellItemDataSourcePrefetching.swift; sourceTree = ""; }; 0A3763621F62545A00A80613 /* CollectionViewCellItemProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCellItemProtocol.swift; sourceTree = ""; }; - 0A3763641F62545A00A80613 /* CollectionViewReusableViewItemProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewReusableViewItemProtocol.swift; sourceTree = ""; }; - 0A3763661F62545A00A80613 /* CollectionViewSectionItemProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewSectionItemProtocol.swift; sourceTree = ""; }; + 0A3763641F62545A00A80613 /* CollectionViewReusableViewItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewReusableViewItem.swift; sourceTree = ""; }; + 0A3763661F62545A00A80613 /* CollectionViewSectionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewSectionItem.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -141,7 +141,7 @@ 0A37635D1F62545A00A80613 /* Items */ = { isa = PBXGroup; children = ( - 0A37635E1F62545A00A80613 /* CollectionViewSectionItem.swift */, + 0A37635E1F62545A00A80613 /* GeneralCollectionViewSectionItem.swift */, ); path = Items; sourceTree = ""; @@ -168,7 +168,7 @@ 0A3763631F62545A00A80613 /* ReuseViewItem */ = { isa = PBXGroup; children = ( - 0A3763641F62545A00A80613 /* CollectionViewReusableViewItemProtocol.swift */, + 0A3763641F62545A00A80613 /* CollectionViewReusableViewItem.swift */, ); path = ReuseViewItem; sourceTree = ""; @@ -176,7 +176,7 @@ 0A3763651F62545A00A80613 /* SectionItem */ = { isa = PBXGroup; children = ( - 0A3763661F62545A00A80613 /* CollectionViewSectionItemProtocol.swift */, + 0A3763661F62545A00A80613 /* CollectionViewSectionItem.swift */, ); path = SectionItem; sourceTree = ""; @@ -296,7 +296,7 @@ buildActionMask = 2147483647; files = ( 0A37636B1F62545A00A80613 /* CollectionViewManager+UICollectionViewDataSourcePrefetching.swift in Sources */, - 0A3763741F62545A00A80613 /* CollectionViewReusableViewItemProtocol.swift in Sources */, + 0A3763741F62545A00A80613 /* CollectionViewReusableViewItem.swift in Sources */, 0A37636F1F62545A00A80613 /* UICollectionView+Registration.swift in Sources */, 0A37636A1F62545A00A80613 /* CollectionViewManager+UICollectionViewDataSource.swift in Sources */, 0A37636D1F62545A00A80613 /* CollectionViewManager+UICollectionViewDelegateFlowLayout.swift in Sources */, @@ -304,10 +304,10 @@ 0A37636C1F62545A00A80613 /* CollectionViewManager+UICollectionViewDelegate.swift in Sources */, 0A3763721F62545A00A80613 /* CollectionViewCellItemDataSourcePrefetching.swift in Sources */, 0A3763731F62545A00A80613 /* CollectionViewCellItemProtocol.swift in Sources */, - 0A3763711F62545A00A80613 /* CollectionViewSectionItem.swift in Sources */, + 0A3763711F62545A00A80613 /* GeneralCollectionViewSectionItem.swift in Sources */, 0A3763671F62545A00A80613 /* ClosureWrapper.swift in Sources */, 0A37636E1F62545A00A80613 /* CollectionViewManager+UIScrollViewDelegate.swift in Sources */, - 0A3763751F62545A00A80613 /* CollectionViewSectionItemProtocol.swift in Sources */, + 0A3763751F62545A00A80613 /* CollectionViewSectionItem.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift index 885e708..0219db5 100644 --- a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift @@ -8,7 +8,7 @@ import CollectionViewTools import Foundation -final class ImageCellItem: CollectionViewCellItemProtocol { +final class ImageCellItem: CollectionViewCellItem { private let image: UIImage private let selectionHandler: (UIImage) -> Void diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index 70cf5bf..bd55686 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -26,8 +26,8 @@ class MainViewController: UIViewController { return CollectionViewManager(collectionView: self.collectionView) }() - var imagesSectionItem: CollectionViewSectionItemProtocol { - let sectionItem = CollectionViewSectionItem() + var imagesSectionItem: CollectionViewSectionItem { + let sectionItem = GeneralCollectionViewSectionItem() sectionItem.cellItems = images.map { ImageCellItem(image: $0) { [weak self] image in let detailViewController = DetailViewController() detailViewController.image = image diff --git a/Sources/CollectionViewManager.swift b/Sources/CollectionViewManager.swift index 1079b0b..278a446 100644 --- a/Sources/CollectionViewManager.swift +++ b/Sources/CollectionViewManager.swift @@ -8,8 +8,8 @@ import UIKit.UICollectionView open class CollectionViewManager: NSObject { - public typealias SectionItem = CollectionViewSectionItemProtocol - public typealias CellItem = CollectionViewCellItemProtocol + public typealias SectionItem = CollectionViewSectionItem + public typealias CellItem = CollectionViewCellItem public typealias Completion = (Bool) -> Void /// `UICollectionView` object for managing @@ -42,14 +42,14 @@ open class CollectionViewManager: NSObject { _ sourceIndexPath: IndexPath, _ destinationIndexPath: IndexPath) -> Void)? - internal var _sectionItems = [CollectionViewSectionItemProtocol]() { + internal var _sectionItems = [CollectionViewSectionItem]() { didSet { _sectionItems.forEach { register($0) } } } /// Array of `CollectionViewSectionItemProtocol` objects, which respond for configuration of specified section in collection view. - public var sectionItems: [CollectionViewSectionItemProtocol] { + public var sectionItems: [CollectionViewSectionItem] { get { return _sectionItems } @@ -72,7 +72,7 @@ open class CollectionViewManager: NSObject { /// Accesses the section item at the specified position. /// /// - Parameter index: The index of the section item to access. - public subscript(index: Int) -> CollectionViewSectionItemProtocol? { + public subscript(index: Int) -> CollectionViewSectionItem? { guard index < _sectionItems.count else { return nil } return _sectionItems[index] } @@ -80,7 +80,7 @@ open class CollectionViewManager: NSObject { /// Accesses the cell item in the specified section and at the specified position. /// /// - Parameter indexPath: The index path of the cell item to access. - public subscript(indexPath: IndexPath) -> CollectionViewCellItemProtocol? { + public subscript(indexPath: IndexPath) -> CollectionViewCellItem? { return cellItem(for: indexPath) } @@ -90,8 +90,8 @@ open class CollectionViewManager: NSObject { /// - cellItems: Cell items to reload /// - sectionItem: Section item that contains cell items to reload /// - completion: A closure that either specifies any additional actions which should be performed after reloading. - open func reloadCellItems(_ cellItems: [CollectionViewCellItemProtocol], - inSectionItem sectionItem: CollectionViewSectionItemProtocol, + open func reloadCellItems(_ cellItems: [CollectionViewCellItem], + inSectionItem sectionItem: CollectionViewSectionItem, completion: Completion? = nil) { let section = sectionItems.index(where: {$0 === sectionItem})! var indexPaths = [IndexPath]() @@ -156,7 +156,7 @@ open class CollectionViewManager: NSObject { // MARK: - Private - fileprivate func register(_ sectionItem: CollectionViewSectionItemProtocol) { + fileprivate func register(_ sectionItem: CollectionViewSectionItem) { sectionItem.cellItems.forEach { register($0) } sectionItem.reusableViewItems.forEach { $0.register(for: collectionView) } } @@ -238,7 +238,7 @@ extension CollectionViewManager { open func remove(_ cellItems: [CellItem], from sectionItem: SectionItem, completion: Completion? = nil) { let sectionIndex = _sectionItems.index { $0 === sectionItem }! - let indexPaths: [IndexPath] = cellItems.flatMap { cellItem in + let indexPaths: [IndexPath] = cellItems.compactMap { cellItem in guard let row = sectionItem.cellItems.index(where: {$0 === cellItem}) else { fatalError("Unable to remove cell item which is not contained in this section item.") } @@ -260,7 +260,7 @@ extension CollectionViewManager { open func remove(cellItemsAt indexes: [Int], from sectionItem: SectionItem, completion: Completion? = nil) { let sectionIndex = _sectionItems.index { $0 === sectionItem }! - let indexPaths: [IndexPath] = indexes.flatMap { IndexPath(item: $0, section: sectionIndex) } + let indexPaths: [IndexPath] = indexes.compactMap { IndexPath(item: $0, section: sectionIndex) } collectionView.performBatchUpdates({ self.collectionView.deleteItems(at: indexPaths) @@ -279,7 +279,7 @@ extension CollectionViewManager { /// - sectionItems: An array of `CollectionViewSectionItemProtocol` objects to insert /// - indexes: An array of locations that specifies the sections to insert in the collection view. If a section already exists at the specified index location, it is moved down one index location. /// - completion: A closure that either specifies any additional actions which should be performed after insertion. - open func insert(_ sectionItems: [CollectionViewSectionItemProtocol], at indexes: [Int], completion: Completion? = nil) { + open func insert(_ sectionItems: [CollectionViewSectionItem], at indexes: [Int], completion: Completion? = nil) { sectionItems.forEach { register($0) } zip(sectionItems, indexes).forEach { self._sectionItems.insert($0, at: $1) } @@ -307,8 +307,8 @@ extension CollectionViewManager { /// - Parameters: /// - sectionItems: An array of `CollectionViewSectionItemProtocol` objects to remove /// - completion: A closure that either specifies any additional actions which should be performed after removing. - open func remove(_ sectionItems: [CollectionViewSectionItemProtocol], completion: Completion? = nil) { - let indexes = sectionItems.flatMap { sectionItem in self._sectionItems.index { $0 === sectionItem } } + open func remove(_ sectionItems: [CollectionViewSectionItem], completion: Completion? = nil) { + let indexes = sectionItems.compactMap { sectionItem in self._sectionItems.index { $0 === sectionItem } } indexes.forEach { self._sectionItems.remove(at: $0) } collectionView.deleteSections(IndexSet(indexes)) } diff --git a/Sources/Items/CollectionViewSectionItem.swift b/Sources/Items/GeneralCollectionViewSectionItem.swift similarity index 73% rename from Sources/Items/CollectionViewSectionItem.swift rename to Sources/Items/GeneralCollectionViewSectionItem.swift index e832916..a4c58e7 100644 --- a/Sources/Items/CollectionViewSectionItem.swift +++ b/Sources/Items/GeneralCollectionViewSectionItem.swift @@ -1,22 +1,22 @@ // -// CollectionViewSectionItem.swift +// GeneralCollectionViewSectionItem.swift // // Copyright © 2017 Rosberry. All rights reserved. // import UIKit.UICollectionView -open class CollectionViewSectionItem: CollectionViewSectionItemProtocol { +open class GeneralCollectionViewSectionItem: CollectionViewSectionItem { - open var cellItems: [CollectionViewCellItemProtocol] - open var reusableViewItems: [CollectionViewReusableViewItemProtocol] + open var cellItems: [CollectionViewCellItem] + open var reusableViewItems: [CollectionViewReusableViewItem] public var minimumLineSpacing: CGFloat = 0 public var minimumInteritemSpacing: CGFloat = 0 public var insets: UIEdgeInsets = .zero - public init(cellItems: [CollectionViewCellItemProtocol] = [], - reusableViewItems: [CollectionViewReusableViewItemProtocol] = []) { + public init(cellItems: [CollectionViewCellItem] = [], + reusableViewItems: [CollectionViewReusableViewItem] = []) { self.cellItems = cellItems self.reusableViewItems = reusableViewItems } diff --git a/Sources/Protocols/CellItem/CollectionViewCellItemDataSourcePrefetching.swift b/Sources/Protocols/CellItem/CollectionViewCellItemDataSourcePrefetching.swift index 0be51a2..19b9b84 100644 --- a/Sources/Protocols/CellItem/CollectionViewCellItemDataSourcePrefetching.swift +++ b/Sources/Protocols/CellItem/CollectionViewCellItemDataSourcePrefetching.swift @@ -6,13 +6,13 @@ import UIKit.UICollectionView -public protocol CollectionViewCellItemDataSourcePrefetching { +public protocol CollectionViewCellItemDataSource { func prefetchData(for collectionView: UICollectionView, at indexPath: IndexPath) func cancelPrefetchingData(for collectionView: UICollectionView, at indexPath: IndexPath) } -public extension CollectionViewCellItemDataSourcePrefetching { +public extension CollectionViewCellItemDataSource { func prefetchData(for collectionView: UICollectionView, at indexPath: IndexPath) {} func cancelPrefetchingData(for collectionView: UICollectionView, at indexPath: IndexPath) {} diff --git a/Sources/Protocols/CellItem/CollectionViewCellItemProtocol.swift b/Sources/Protocols/CellItem/CollectionViewCellItemProtocol.swift index d156ab4..5c57181 100644 --- a/Sources/Protocols/CellItem/CollectionViewCellItemProtocol.swift +++ b/Sources/Protocols/CellItem/CollectionViewCellItemProtocol.swift @@ -8,28 +8,29 @@ import UIKit.UICollectionView // MARK: - CollectionViewCellItemProtocol -public protocol CollectionViewCellItemProtocol: AnyObject, -CollectionViewReuseCellItemProtocol, -CollectionViewSizeCellItemProtocol, -CollectionViewConfigureCellItemProtocol, -CollectionViewGeneralCellItemProtocol, -CollectionViewCellItemDataSourcePrefetching {} +public protocol CollectionViewCellItem: AnyObject, + CollectionViewReuseCellItem, + CollectionViewSizeCellItem, + CollectionViewConfigureCellItem, + CollectionViewGeneralCellItem, + CollectionViewCellItemDataSource { +} // MARK: - CollectionViewReuseCellItemProtocol -public protocol CollectionViewReuseCellItemProtocol { +public protocol CollectionViewReuseCellItem { var reuseType: ReuseType { get } } // MARK: - CollectionViewSizeCellItemProtocol -public protocol CollectionViewSizeCellItemProtocol { +public protocol CollectionViewSizeCellItem { func size(for collectionView: UICollectionView, with layout: UICollectionViewLayout, at indexPath: IndexPath) -> CGSize } // MARK: - CollectionViewConfigureCellItemProtocol -public protocol CollectionViewConfigureCellItemProtocol { +public protocol CollectionViewConfigureCellItem { func cell(for collectionView: UICollectionView, at indexPath: IndexPath) -> UICollectionViewCell } @@ -38,7 +39,7 @@ public protocol CollectionViewConfigureCellItemProtocol { public typealias ActionHandler = ((UICollectionView, IndexPath) -> Void) public typealias ActionResolver = ((UICollectionView, IndexPath) -> Bool) -public protocol CollectionViewGeneralCellItemProtocol { +public protocol CollectionViewGeneralCellItem { var itemShouldHighlightHandler: ActionResolver? { get set } var itemDidHighlightHandler: ActionHandler? { get set } @@ -50,7 +51,7 @@ public protocol CollectionViewGeneralCellItemProtocol { var itemDidEndDisplayingCellHandler: ActionHandler? { get set } var itemDidEndDisplayingViewHandler: ActionHandler? { get set } var itemCanMoveHandler: ActionResolver? { get set } - + func shouldHighlight(for collectionView: UICollectionView, at indexPath: IndexPath) -> Bool func didHighlight(for collectionView: UICollectionView, @@ -97,58 +98,98 @@ private enum AssociatedKeys { static var canMoveHandler = "rsb_canMoveHandler" } -public extension CollectionViewGeneralCellItemProtocol { +public extension CollectionViewGeneralCellItem { // MARK: - Handlers var itemShouldHighlightHandler: ActionResolver? { - get { return ClosureWrapper.handler(for: self, key: &AssociatedKeys.shouldHighlightHandler) } - set { ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.shouldHighlightHandler) } + get { + return ClosureWrapper.handler(for: self, key: &AssociatedKeys.shouldHighlightHandler) + } + set { + ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.shouldHighlightHandler) + } } var itemDidHighlightHandler: ActionHandler? { - get { return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didHighlightHandler) } - set { ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didHighlightHandler) } + get { + return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didHighlightHandler) + } + set { + ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didHighlightHandler) + } } var itemDidUnhighlightHandler: ActionHandler? { - get { return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didUnhighlightHandler) } - set { ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didUnhighlightHandler) } + get { + return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didUnhighlightHandler) + } + set { + ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didUnhighlightHandler) + } } var itemDidSelectHandler: ActionHandler? { - get { return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didDeselectHandler) } - set { ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didDeselectHandler) } + get { + return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didDeselectHandler) + } + set { + ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didDeselectHandler) + } } var itemDidDeselectHandler: ActionHandler? { - get { return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didSelectHandler) } - set { ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didSelectHandler) } + get { + return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didSelectHandler) + } + set { + ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didSelectHandler) + } } var itemWillDisplayCellHandler: ActionHandler? { - get { return ClosureWrapper.handler(for: self, key: &AssociatedKeys.willDisplayCellHandler) } - set { ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.willDisplayCellHandler) } + get { + return ClosureWrapper.handler(for: self, key: &AssociatedKeys.willDisplayCellHandler) + } + set { + ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.willDisplayCellHandler) + } } var itemWillDisplayViewHandler: ActionHandler? { - get { return ClosureWrapper.handler(for: self, key: &AssociatedKeys.willDisplayViewHandler) } - set { ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.willDisplayViewHandler) } + get { + return ClosureWrapper.handler(for: self, key: &AssociatedKeys.willDisplayViewHandler) + } + set { + ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.willDisplayViewHandler) + } } var itemDidEndDisplayingCellHandler: ActionHandler? { - get { return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didEndDisplayingCellHandler) } - set { ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didEndDisplayingCellHandler) } + get { + return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didEndDisplayingCellHandler) + } + set { + ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didEndDisplayingCellHandler) + } } var itemDidEndDisplayingViewHandler: ActionHandler? { - get { return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didEndDisplayingViewHandler) } - set { ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didEndDisplayingViewHandler) } + get { + return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didEndDisplayingViewHandler) + } + set { + ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didEndDisplayingViewHandler) + } } var itemCanMoveHandler: ActionResolver? { - get { return ClosureWrapper.handler(for: self, key: &AssociatedKeys.canMoveHandler) } - set { ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.canMoveHandler) } + get { + return ClosureWrapper.handler(for: self, key: &AssociatedKeys.canMoveHandler) + } + set { + ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.canMoveHandler) + } } func size(for collectionView: UICollectionView, diff --git a/Sources/Protocols/ReuseViewItem/CollectionViewReusableViewItemProtocol.swift b/Sources/Protocols/ReuseViewItem/CollectionViewReusableViewItem.swift similarity index 86% rename from Sources/Protocols/ReuseViewItem/CollectionViewReusableViewItemProtocol.swift rename to Sources/Protocols/ReuseViewItem/CollectionViewReusableViewItem.swift index c247130..44b2a3e 100644 --- a/Sources/Protocols/ReuseViewItem/CollectionViewReusableViewItemProtocol.swift +++ b/Sources/Protocols/ReuseViewItem/CollectionViewReusableViewItem.swift @@ -1,5 +1,5 @@ // -// CollectionViewReusableViewItemProtocol.swift +// CollectionViewReusableViewItem.swift // // Copyright © 2017 Rosberry. All rights reserved. // @@ -17,7 +17,7 @@ public enum ReusableViewType { } } -public protocol CollectionViewReusableViewItemProtocol { +public protocol CollectionViewReusableViewItem { var type: ReusableViewType { get set } diff --git a/Sources/Protocols/SectionItem/CollectionViewSectionItemProtocol.swift b/Sources/Protocols/SectionItem/CollectionViewSectionItem.swift similarity index 74% rename from Sources/Protocols/SectionItem/CollectionViewSectionItemProtocol.swift rename to Sources/Protocols/SectionItem/CollectionViewSectionItem.swift index a4acb5b..cf56bc9 100644 --- a/Sources/Protocols/SectionItem/CollectionViewSectionItemProtocol.swift +++ b/Sources/Protocols/SectionItem/CollectionViewSectionItem.swift @@ -1,22 +1,22 @@ // -// CollectionViewSectionItemProtocol.swift +// CollectionViewSectionItem.swift // // Copyright © 2017 Rosberry. All rights reserved. // import UIKit.UICollectionView -public protocol CollectionViewSectionItemProtocol: AnyObject { +public protocol CollectionViewSectionItem: AnyObject { - var cellItems: [CollectionViewCellItemProtocol] { get set } - var reusableViewItems: [CollectionViewReusableViewItemProtocol] { get set } + var cellItems: [CollectionViewCellItem] { get set } + var reusableViewItems: [CollectionViewReusableViewItem] { get set } func inset(for collectionView: UICollectionView, with layout: UICollectionViewLayout) -> UIEdgeInsets func minimumLineSpacing(for collectionView: UICollectionView, with layout: UICollectionViewLayout) -> CGFloat func minimumInteritemSpacing(for collectionView: UICollectionView, with layout: UICollectionViewLayout) -> CGFloat } -public extension CollectionViewSectionItemProtocol { +public extension CollectionViewSectionItem { func inset(for collectionView: UICollectionView, with layout: UICollectionViewLayout) -> UIEdgeInsets { return .zero From e1d397191fd5a80cad5b1482692f56b48bdfea20 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 6 Jul 2018 15:01:19 +0600 Subject: [PATCH 02/54] Rename other files --- CollectionViewTools.xcodeproj/project.pbxproj | 8 ++++---- ...ellItemProtocol.swift => CollectionViewCellItem.swift} | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) rename Sources/Protocols/CellItem/{CollectionViewCellItemProtocol.swift => CollectionViewCellItem.swift} (99%) diff --git a/CollectionViewTools.xcodeproj/project.pbxproj b/CollectionViewTools.xcodeproj/project.pbxproj index f2b6d75..206c407 100644 --- a/CollectionViewTools.xcodeproj/project.pbxproj +++ b/CollectionViewTools.xcodeproj/project.pbxproj @@ -20,7 +20,7 @@ 0A37636F1F62545A00A80613 /* UICollectionView+Registration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A37635B1F62545A00A80613 /* UICollectionView+Registration.swift */; }; 0A3763711F62545A00A80613 /* GeneralCollectionViewSectionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A37635E1F62545A00A80613 /* GeneralCollectionViewSectionItem.swift */; }; 0A3763721F62545A00A80613 /* CollectionViewCellItemDataSourcePrefetching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763611F62545A00A80613 /* CollectionViewCellItemDataSourcePrefetching.swift */; }; - 0A3763731F62545A00A80613 /* CollectionViewCellItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763621F62545A00A80613 /* CollectionViewCellItemProtocol.swift */; }; + 0A3763731F62545A00A80613 /* CollectionViewCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763621F62545A00A80613 /* CollectionViewCellItem.swift */; }; 0A3763741F62545A00A80613 /* CollectionViewReusableViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763641F62545A00A80613 /* CollectionViewReusableViewItem.swift */; }; 0A3763751F62545A00A80613 /* CollectionViewSectionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763661F62545A00A80613 /* CollectionViewSectionItem.swift */; }; /* End PBXBuildFile section */ @@ -52,7 +52,7 @@ 0A37635C1F62545A00A80613 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0A37635E1F62545A00A80613 /* GeneralCollectionViewSectionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralCollectionViewSectionItem.swift; sourceTree = ""; }; 0A3763611F62545A00A80613 /* CollectionViewCellItemDataSourcePrefetching.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCellItemDataSourcePrefetching.swift; sourceTree = ""; }; - 0A3763621F62545A00A80613 /* CollectionViewCellItemProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCellItemProtocol.swift; sourceTree = ""; }; + 0A3763621F62545A00A80613 /* CollectionViewCellItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCellItem.swift; sourceTree = ""; }; 0A3763641F62545A00A80613 /* CollectionViewReusableViewItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewReusableViewItem.swift; sourceTree = ""; }; 0A3763661F62545A00A80613 /* CollectionViewSectionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewSectionItem.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -160,7 +160,7 @@ isa = PBXGroup; children = ( 0A3763611F62545A00A80613 /* CollectionViewCellItemDataSourcePrefetching.swift */, - 0A3763621F62545A00A80613 /* CollectionViewCellItemProtocol.swift */, + 0A3763621F62545A00A80613 /* CollectionViewCellItem.swift */, ); path = CellItem; sourceTree = ""; @@ -303,7 +303,7 @@ 0A3763681F62545A00A80613 /* CollectionViewManager.swift in Sources */, 0A37636C1F62545A00A80613 /* CollectionViewManager+UICollectionViewDelegate.swift in Sources */, 0A3763721F62545A00A80613 /* CollectionViewCellItemDataSourcePrefetching.swift in Sources */, - 0A3763731F62545A00A80613 /* CollectionViewCellItemProtocol.swift in Sources */, + 0A3763731F62545A00A80613 /* CollectionViewCellItem.swift in Sources */, 0A3763711F62545A00A80613 /* GeneralCollectionViewSectionItem.swift in Sources */, 0A3763671F62545A00A80613 /* ClosureWrapper.swift in Sources */, 0A37636E1F62545A00A80613 /* CollectionViewManager+UIScrollViewDelegate.swift in Sources */, diff --git a/Sources/Protocols/CellItem/CollectionViewCellItemProtocol.swift b/Sources/Protocols/CellItem/CollectionViewCellItem.swift similarity index 99% rename from Sources/Protocols/CellItem/CollectionViewCellItemProtocol.swift rename to Sources/Protocols/CellItem/CollectionViewCellItem.swift index 5c57181..640757d 100644 --- a/Sources/Protocols/CellItem/CollectionViewCellItemProtocol.swift +++ b/Sources/Protocols/CellItem/CollectionViewCellItem.swift @@ -1,5 +1,5 @@ // -// CollectionViewCellItemProtocol.swift +// CollectionViewCellItem.swift // // Copyright © 2017 Rosberry. All rights reserved. // From a19ed1472c754e50f40e8c166005269d58ca43d2 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 6 Jul 2018 15:20:47 +0600 Subject: [PATCH 03/54] Update project settings --- CollectionViewTools.xcodeproj/project.pbxproj | 6 +++++- .../xcshareddata/xcschemes/CollectionViewTools.xcscheme | 4 +--- .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++++++++ .../CollectionViewToolsExample.xcodeproj/project.pbxproj | 6 +++++- 4 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 CollectionViewTools.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/CollectionViewTools.xcodeproj/project.pbxproj b/CollectionViewTools.xcodeproj/project.pbxproj index 206c407..2177c32 100644 --- a/CollectionViewTools.xcodeproj/project.pbxproj +++ b/CollectionViewTools.xcodeproj/project.pbxproj @@ -238,7 +238,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0830; - LastUpgradeCheck = 0910; + LastUpgradeCheck = 0940; ORGANIZATIONNAME = Rosberry; TargetAttributes = { 0A3763241F6251A600A80613 = { @@ -344,6 +344,7 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -351,6 +352,7 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -404,6 +406,7 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -411,6 +414,7 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; diff --git a/CollectionViewTools.xcodeproj/xcshareddata/xcschemes/CollectionViewTools.xcscheme b/CollectionViewTools.xcodeproj/xcshareddata/xcschemes/CollectionViewTools.xcscheme index b2fd0e0..861f9f1 100644 --- a/CollectionViewTools.xcodeproj/xcshareddata/xcschemes/CollectionViewTools.xcscheme +++ b/CollectionViewTools.xcodeproj/xcshareddata/xcschemes/CollectionViewTools.xcscheme @@ -1,6 +1,6 @@ + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj b/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj index d8e37aa..a971dee 100644 --- a/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj +++ b/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj @@ -125,7 +125,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0830; - LastUpgradeCheck = 0910; + LastUpgradeCheck = 0940; ORGANIZATIONNAME = Rosberry; TargetAttributes = { 0A37637E1F62556300A80613 = { @@ -207,6 +207,7 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -214,6 +215,7 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -263,6 +265,7 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -270,6 +273,7 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; From 8436798fd9d8a503a99499085171b0e435cb7f11 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 6 Jul 2018 15:21:09 +0600 Subject: [PATCH 04/54] Update framework embedding for example project --- .../project.pbxproj | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj b/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj index a971dee..3a36927 100644 --- a/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj +++ b/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj @@ -14,9 +14,24 @@ 0ACA6FC91F626AF900782C1E /* ImageCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACA6FC81F626AF900782C1E /* ImageCellItem.swift */; }; 0ACA6FCC1F626D2E00782C1E /* ImageCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACA6FCB1F626D2E00782C1E /* ImageCollectionViewCell.swift */; }; 0ACA6FCE1F6275D200782C1E /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACA6FCD1F6275D200782C1E /* DetailViewController.swift */; }; - FE6E86B21FB961B20027A8FE /* CollectionViewTools.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE6E86B31FB961B20027A8FE /* CollectionViewTools.framework */; }; + FED796BA20EF6B3B0044B82F /* CollectionViewTools.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE6E86B31FB961B20027A8FE /* CollectionViewTools.framework */; }; + FED796BB20EF6B3B0044B82F /* CollectionViewTools.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FE6E86B31FB961B20027A8FE /* CollectionViewTools.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ +/* Begin PBXCopyFilesBuildPhase section */ + FED796BC20EF6B3B0044B82F /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + FED796BB20EF6B3B0044B82F /* CollectionViewTools.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 0A37637F1F62556300A80613 /* CollectionViewToolsExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CollectionViewToolsExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0A3763821F62556300A80613 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -35,7 +50,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - FE6E86B21FB961B20027A8FE /* CollectionViewTools.framework in Frameworks */, + FED796BA20EF6B3B0044B82F /* CollectionViewTools.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -108,6 +123,7 @@ 0A37637B1F62556300A80613 /* Sources */, 0A37637C1F62556300A80613 /* Frameworks */, 0A37637D1F62556300A80613 /* Resources */, + FED796BC20EF6B3B0044B82F /* Embed Frameworks */, ); buildRules = ( ); From 1e398108b398e70470180df0de011a4478685da2 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 6 Jul 2018 15:24:06 +0600 Subject: [PATCH 05/54] Rename and simplify reuse type cases --- .../CellItems/ImageCellItem.swift | 3 +- .../UICollectionView+Registration.swift | 33 ++++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift index 0219db5..c616379 100644 --- a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift @@ -12,8 +12,7 @@ final class ImageCellItem: CollectionViewCellItem { private let image: UIImage private let selectionHandler: (UIImage) -> Void - - var reuseType = ReuseType(cellClass: ImageCollectionViewCell.self) + private(set) var reuseType: ReuseType = .class(ImageCollectionViewCell.self) init(image: UIImage, selectionHandler: @escaping (UIImage) -> Void) { self.image = image diff --git a/Sources/Extensions/UICollectionView+Registration.swift b/Sources/Extensions/UICollectionView+Registration.swift index 004bd1c..ce28193 100644 --- a/Sources/Extensions/UICollectionView+Registration.swift +++ b/Sources/Extensions/UICollectionView+Registration.swift @@ -7,30 +7,32 @@ import UIKit.UICollectionView public enum ReuseType { - case byStoryboardIdentifier(String) - case byNib(UINib, identifier: String) - case byClass(UICollectionViewCell.Type, identifier: String) + case storyboardIdentifier(String) + case nib(UINib, identifier: String) + case `class`(UICollectionViewCell.Type) public var identifier: String { switch self { - case let .byStoryboardIdentifier(identifier): return identifier - case let .byNib(_, identifier: identifier): return identifier - case let .byClass(_, identifier: identifier): return identifier + case let .storyboardIdentifier(identifier): + return identifier + case let .nib(_, identifier): + return identifier + case let .class(`class`): + return NSStringFromClass(`class`) } } - - public init(cellClass: UICollectionViewCell.Type) { - self = .byClass(cellClass, identifier: NSStringFromClass(cellClass)) - } } public extension UICollectionView { func register(by type: ReuseType) { switch type { - case let .byNib(nib, identifier: identifier): register(nib, forCellWithReuseIdentifier: identifier) - case let .byClass(cellClass, identifier: identifier): register(cellClass, forCellWithReuseIdentifier: identifier) - default: break + case let .nib(nib, identifier): + register(nib, forCellWithReuseIdentifier: identifier) + case let .class(`class`): + register(`class`, forCellWithReuseIdentifier: type.identifier) + default: + break } } @@ -40,8 +42,7 @@ public extension UICollectionView { } func dequeueReusableSupplementaryView(with type: ReusableViewType, at indexPath: IndexPath) -> T { - return dequeueReusableSupplementaryView(ofKind: type.kind, - withReuseIdentifier: "\(T.self)", - for: indexPath) as! T // swiftlint:disable:this force_cast + // swiftlint:disable:next force_cast + return dequeueReusableSupplementaryView(ofKind: type.kind, withReuseIdentifier: "\(T.self)", for: indexPath) as! T } } From 2745d4dc20254cb01089383397814d1425090a44 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 6 Jul 2018 15:27:15 +0600 Subject: [PATCH 06/54] Fix comments --- .../Cells/ImageCollectionViewCell.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Example/CollectionViewToolsExample/Cells/ImageCollectionViewCell.swift b/Example/CollectionViewToolsExample/Cells/ImageCollectionViewCell.swift index db0d297..d39dfbe 100644 --- a/Example/CollectionViewToolsExample/Cells/ImageCollectionViewCell.swift +++ b/Example/CollectionViewToolsExample/Cells/ImageCollectionViewCell.swift @@ -1,8 +1,6 @@ // // ImageCollectionViewCell.swift -// CollectionViewToolsExample // -// Created by Dmitry Frishbuter on 08/09/2017. // Copyright © 2017 Rosberry. All rights reserved. // From 83762c8b817866d93f8d04af9a6561c146ab239c Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 6 Jul 2018 15:37:39 +0600 Subject: [PATCH 07/54] Refactor handlers/functions --- .../CellItem/CollectionViewCellItem.swift | 83 +++++++------------ 1 file changed, 32 insertions(+), 51 deletions(-) diff --git a/Sources/Protocols/CellItem/CollectionViewCellItem.swift b/Sources/Protocols/CellItem/CollectionViewCellItem.swift index 640757d..fb12228 100644 --- a/Sources/Protocols/CellItem/CollectionViewCellItem.swift +++ b/Sources/Protocols/CellItem/CollectionViewCellItem.swift @@ -44,38 +44,31 @@ public protocol CollectionViewGeneralCellItem { var itemShouldHighlightHandler: ActionResolver? { get set } var itemDidHighlightHandler: ActionHandler? { get set } var itemDidUnhighlightHandler: ActionHandler? { get set } + var itemDidSelectHandler: ActionHandler? { get set } var itemDidDeselectHandler: ActionHandler? { get set } + var itemShouldSelectHandler: ActionResolver? { get set } + var itemShouldDeselectHandler: ActionResolver? { get set } + var itemWillDisplayCellHandler: ActionHandler? { get set } var itemWillDisplayViewHandler: ActionHandler? { get set } var itemDidEndDisplayingCellHandler: ActionHandler? { get set } var itemDidEndDisplayingViewHandler: ActionHandler? { get set } + var itemCanMoveHandler: ActionResolver? { get set } - func shouldHighlight(for collectionView: UICollectionView, - at indexPath: IndexPath) -> Bool - func didHighlight(for collectionView: UICollectionView, - at indexPath: IndexPath) - func didUnhighlight(for collectionView: UICollectionView, - at indexPath: IndexPath) - func shouldSelect(for collectionView: UICollectionView, - at indexPath: IndexPath) -> Bool - func shouldDeselect(for collectionView: UICollectionView, - at indexPath: IndexPath) -> Bool - func didSelect(for collectionView: UICollectionView, - at indexPath: IndexPath) - func didDeselect(for collectionView: UICollectionView, - at indexPath: IndexPath) - func willDisplay(cell: UICollectionViewCell, - for collectionView: UICollectionView, - at indexPath: IndexPath) - func willDisplay(view: UICollectionReusableView, - for elementKind: String, - for collectionView: UICollectionView, - at indexPath: IndexPath) - func didEndDisplaying(cell: UICollectionViewCell, - for collectionView: UICollectionView, - at indexPath: IndexPath) + func shouldHighlight(for collectionView: UICollectionView, at indexPath: IndexPath) -> Bool + func didHighlight(for collectionView: UICollectionView, at indexPath: IndexPath) + func didUnhighlight(for collectionView: UICollectionView, at indexPath: IndexPath) + + func shouldSelect(for collectionView: UICollectionView, at indexPath: IndexPath) -> Bool + func shouldDeselect(for collectionView: UICollectionView, at indexPath: IndexPath) -> Bool + func didSelect(for collectionView: UICollectionView, at indexPath: IndexPath) + func didDeselect(for collectionView: UICollectionView, at indexPath: IndexPath) + + func willDisplay(cell: UICollectionViewCell, for collectionView: UICollectionView, at indexPath: IndexPath) + func willDisplay(view: UICollectionReusableView, for elementKind: String, for collectionView: UICollectionView, at indexPath: IndexPath) + func didEndDisplaying(cell: UICollectionViewCell, for collectionView: UICollectionView, at indexPath: IndexPath) func didEndDisplaying(view: UICollectionReusableView, for elementKind: String, for collectionView: UICollectionView, @@ -192,50 +185,41 @@ public extension CollectionViewGeneralCellItem { } } - func size(for collectionView: UICollectionView, - with layout: UICollectionViewLayout, - at indexPath: IndexPath) -> CGSize { + // MARK: - Functions + + func size(for collectionView: UICollectionView, with layout: UICollectionViewLayout, at indexPath: IndexPath) -> CGSize { return CGSize(width: 50, height: 50) } - func shouldHighlight(for collectionView: UICollectionView, - at indexPath: IndexPath) -> Bool { + func shouldHighlight(for collectionView: UICollectionView, at indexPath: IndexPath) -> Bool { return itemShouldHighlightHandler?(collectionView, indexPath) ?? true } - func didHighlight(for collectionView: UICollectionView, - at indexPath: IndexPath) { + func didHighlight(for collectionView: UICollectionView, at indexPath: IndexPath) { itemDidHighlightHandler?(collectionView, indexPath) } - func didUnhighlight(for collectionView: UICollectionView, - at indexPath: IndexPath) { + func didUnhighlight(for collectionView: UICollectionView, at indexPath: IndexPath) { itemDidUnhighlightHandler?(collectionView, indexPath) } - func shouldSelect(for collectionView: UICollectionView, - at indexPath: IndexPath) -> Bool { - return true + func shouldSelect(for collectionView: UICollectionView, at indexPath: IndexPath) -> Bool { + return itemShouldSelectHandler?(collectionView, indexPath) ?? true } - func shouldDeselect(for collectionView: UICollectionView, - at indexPath: IndexPath) -> Bool { - return true + func shouldDeselect(for collectionView: UICollectionView, at indexPath: IndexPath) -> Bool { + return itemShouldDeselectHandler?(collectionView, indexPath) ?? true } - func didSelect(for collectionView: UICollectionView, - at indexPath: IndexPath) { + func didSelect(for collectionView: UICollectionView, at indexPath: IndexPath) { itemDidSelectHandler?(collectionView, indexPath) } - func didDeselect(for collectionView: UICollectionView, - at indexPath: IndexPath) { + func didDeselect(for collectionView: UICollectionView, at indexPath: IndexPath) { itemDidDeselectHandler?(collectionView, indexPath) } - func willDisplay(cell: UICollectionViewCell, - for collectionView: UICollectionView, - at indexPath: IndexPath) { + func willDisplay(cell: UICollectionViewCell, for collectionView: UICollectionView, at indexPath: IndexPath) { itemWillDisplayCellHandler?(collectionView, indexPath) } @@ -246,9 +230,7 @@ public extension CollectionViewGeneralCellItem { itemWillDisplayViewHandler?(collectionView, indexPath) } - func didEndDisplaying(cell: UICollectionViewCell, - for collectionView: UICollectionView, - at indexPath: IndexPath) { + func didEndDisplaying(cell: UICollectionViewCell, for collectionView: UICollectionView, at indexPath: IndexPath) { itemDidEndDisplayingCellHandler?(collectionView, indexPath) } @@ -259,8 +241,7 @@ public extension CollectionViewGeneralCellItem { itemDidEndDisplayingViewHandler?(collectionView, indexPath) } - func canMove(for collectionView: UICollectionView, - at indexPath: IndexPath) -> Bool { + func canMove(for collectionView: UICollectionView, at indexPath: IndexPath) -> Bool { return itemCanMoveHandler?(collectionView, indexPath) ?? false } } From 00c153c4479476e24c65e829d860faddcae06c75 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 6 Jul 2018 16:36:49 +0600 Subject: [PATCH 08/54] Start adding generic to collection view cell item --- CollectionViewTools.xcodeproj/project.pbxproj | 4 +++ .../CellItems/ImageCellItem.swift | 13 ++++--- .../MainViewController.swift | 13 +++---- Sources/CollectionViewManager.swift | 6 ++-- ...ewManager+UICollectionViewDataSource.swift | 4 ++- .../GeneralCollectionViewSectionItem.swift | 4 +-- .../CellItem/AnyCollectionViewCellItem.swift | 36 +++++++++++++++++++ .../CellItem/CollectionViewCellItem.swift | 29 +++++++++++---- .../CollectionViewSectionItem.swift | 2 +- 9 files changed, 84 insertions(+), 27 deletions(-) create mode 100644 Sources/Protocols/CellItem/AnyCollectionViewCellItem.swift diff --git a/CollectionViewTools.xcodeproj/project.pbxproj b/CollectionViewTools.xcodeproj/project.pbxproj index 2177c32..d91c921 100644 --- a/CollectionViewTools.xcodeproj/project.pbxproj +++ b/CollectionViewTools.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ 0A3763731F62545A00A80613 /* CollectionViewCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763621F62545A00A80613 /* CollectionViewCellItem.swift */; }; 0A3763741F62545A00A80613 /* CollectionViewReusableViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763641F62545A00A80613 /* CollectionViewReusableViewItem.swift */; }; 0A3763751F62545A00A80613 /* CollectionViewSectionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763661F62545A00A80613 /* CollectionViewSectionItem.swift */; }; + 629A67012CD6843FB2869A01 /* AnyCollectionViewCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A6094DC950478A5E96F92 /* AnyCollectionViewCellItem.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -55,6 +56,7 @@ 0A3763621F62545A00A80613 /* CollectionViewCellItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCellItem.swift; sourceTree = ""; }; 0A3763641F62545A00A80613 /* CollectionViewReusableViewItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewReusableViewItem.swift; sourceTree = ""; }; 0A3763661F62545A00A80613 /* CollectionViewSectionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewSectionItem.swift; sourceTree = ""; }; + 629A6094DC950478A5E96F92 /* AnyCollectionViewCellItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyCollectionViewCellItem.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -161,6 +163,7 @@ children = ( 0A3763611F62545A00A80613 /* CollectionViewCellItemDataSourcePrefetching.swift */, 0A3763621F62545A00A80613 /* CollectionViewCellItem.swift */, + 629A6094DC950478A5E96F92 /* AnyCollectionViewCellItem.swift */, ); path = CellItem; sourceTree = ""; @@ -308,6 +311,7 @@ 0A3763671F62545A00A80613 /* ClosureWrapper.swift in Sources */, 0A37636E1F62545A00A80613 /* CollectionViewManager+UIScrollViewDelegate.swift in Sources */, 0A3763751F62545A00A80613 /* CollectionViewSectionItem.swift in Sources */, + 629A67012CD6843FB2869A01 /* AnyCollectionViewCellItem.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift index c616379..924b46c 100644 --- a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift @@ -10,9 +10,14 @@ import Foundation final class ImageCellItem: CollectionViewCellItem { + typealias Cell = ImageCollectionViewCell + private(set) var reuseType: ReuseType = .class(Cell.self) private let image: UIImage private let selectionHandler: (UIImage) -> Void - private(set) var reuseType: ReuseType = .class(ImageCollectionViewCell.self) + + func configure(cell: ImageCollectionViewCell, at indexPath: IndexPath) { + cell.imageView.image = image + } init(image: UIImage, selectionHandler: @escaping (UIImage) -> Void) { self.image = image @@ -23,12 +28,6 @@ final class ImageCellItem: CollectionViewCellItem { return collectionView.bounds.size } - func cell(for collectionView: UICollectionView, at indexPath: IndexPath) -> UICollectionViewCell { - let cell: ImageCollectionViewCell = collectionView.dequeueReusableCell(for: indexPath) - cell.imageView.image = image - return cell - } - func size(for collectionView: UICollectionView, with layout: UICollectionViewLayout, at indexPath: IndexPath) -> CGSize { return size(for: collectionView, at: indexPath) } diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index bd55686..7ee4f46 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -28,15 +28,16 @@ class MainViewController: UIViewController { var imagesSectionItem: CollectionViewSectionItem { let sectionItem = GeneralCollectionViewSectionItem() - sectionItem.cellItems = images.map { ImageCellItem(image: $0) { [weak self] image in - let detailViewController = DetailViewController() - detailViewController.image = image - self?.navigationController?.pushViewController(detailViewController, animated: true) - } + sectionItem.cellItems = images.map { + AnyCollectionViewCellItem(ImageCellItem(image: $0) { [weak self] image in + let detailViewController = DetailViewController() + detailViewController.image = image + self?.navigationController?.pushViewController(detailViewController, animated: true) + }) } return sectionItem } - + override func viewDidLoad() { super.viewDidLoad() diff --git a/Sources/CollectionViewManager.swift b/Sources/CollectionViewManager.swift index 278a446..46f8fd3 100644 --- a/Sources/CollectionViewManager.swift +++ b/Sources/CollectionViewManager.swift @@ -9,7 +9,7 @@ import UIKit.UICollectionView open class CollectionViewManager: NSObject { public typealias SectionItem = CollectionViewSectionItem - public typealias CellItem = CollectionViewCellItem + public typealias CellItem = AnyCollectionViewCellItem public typealias Completion = (Bool) -> Void /// `UICollectionView` object for managing @@ -80,7 +80,7 @@ open class CollectionViewManager: NSObject { /// Accesses the cell item in the specified section and at the specified position. /// /// - Parameter indexPath: The index path of the cell item to access. - public subscript(indexPath: IndexPath) -> CollectionViewCellItem? { + public subscript(indexPath: IndexPath) -> CellItem? { return cellItem(for: indexPath) } @@ -90,7 +90,7 @@ open class CollectionViewManager: NSObject { /// - cellItems: Cell items to reload /// - sectionItem: Section item that contains cell items to reload /// - completion: A closure that either specifies any additional actions which should be performed after reloading. - open func reloadCellItems(_ cellItems: [CollectionViewCellItem], + open func reloadCellItems(_ cellItems: [CellItem], inSectionItem sectionItem: CollectionViewSectionItem, completion: Completion? = nil) { let section = sectionItems.index(where: {$0 === sectionItem})! diff --git a/Sources/Extensions/CollectionViewManager+UICollectionViewDataSource.swift b/Sources/Extensions/CollectionViewManager+UICollectionViewDataSource.swift index 603fb67..d6504ec 100644 --- a/Sources/Extensions/CollectionViewManager+UICollectionViewDataSource.swift +++ b/Sources/Extensions/CollectionViewManager+UICollectionViewDataSource.swift @@ -16,7 +16,9 @@ extension CollectionViewManager: UICollectionViewDataSource { open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cellItem = self.cellItem(for: indexPath)! - return cellItem.cell(for: collectionView, at: indexPath) + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellItem.reuseType.identifier, for: indexPath) + cellItem.configure(cell: cell, at: indexPath) + return cell } open func numberOfSections(in collectionView: UICollectionView) -> Int { diff --git a/Sources/Items/GeneralCollectionViewSectionItem.swift b/Sources/Items/GeneralCollectionViewSectionItem.swift index a4c58e7..7c97255 100644 --- a/Sources/Items/GeneralCollectionViewSectionItem.swift +++ b/Sources/Items/GeneralCollectionViewSectionItem.swift @@ -8,14 +8,14 @@ import UIKit.UICollectionView open class GeneralCollectionViewSectionItem: CollectionViewSectionItem { - open var cellItems: [CollectionViewCellItem] + open var cellItems: [CollectionViewManager.CellItem] open var reusableViewItems: [CollectionViewReusableViewItem] public var minimumLineSpacing: CGFloat = 0 public var minimumInteritemSpacing: CGFloat = 0 public var insets: UIEdgeInsets = .zero - public init(cellItems: [CollectionViewCellItem] = [], + public init(cellItems: [CollectionViewManager.CellItem] = [], reusableViewItems: [CollectionViewReusableViewItem] = []) { self.cellItems = cellItems self.reusableViewItems = reusableViewItems diff --git a/Sources/Protocols/CellItem/AnyCollectionViewCellItem.swift b/Sources/Protocols/CellItem/AnyCollectionViewCellItem.swift new file mode 100644 index 0000000..15419a2 --- /dev/null +++ b/Sources/Protocols/CellItem/AnyCollectionViewCellItem.swift @@ -0,0 +1,36 @@ +// +// Copyright (c) 2018 Rosberry. All rights reserved. +// + +import UIKit.UICollectionView + +open class AnyCollectionViewCellItem: CollectionViewCellItem where T: UICollectionViewCell { + + public typealias Cell = T + + private let _configure: (T, IndexPath) -> Void + private let _reuseType: () -> ReuseType + + public var reuseType: ReuseType { + return _reuseType() + } + + public init(_ cellItem: U) where U.Cell == T { + _configure = { [weak cellItem] (cell, indexPath) in + guard let cellItem = cellItem else { + return + } + cellItem.configure(cell: cell, at: indexPath) + } + _reuseType = { [weak cellItem] in + guard let cellItem = cellItem else { + fatalError("It is impossible to create cell item without reuse type") + } + return cellItem.reuseType + } + } + + public func configure(cell: Cell, at indexPath: IndexPath) { + _configure(cell, indexPath) + } +} diff --git a/Sources/Protocols/CellItem/CollectionViewCellItem.swift b/Sources/Protocols/CellItem/CollectionViewCellItem.swift index fb12228..ffb19f7 100644 --- a/Sources/Protocols/CellItem/CollectionViewCellItem.swift +++ b/Sources/Protocols/CellItem/CollectionViewCellItem.swift @@ -11,9 +11,10 @@ import UIKit.UICollectionView public protocol CollectionViewCellItem: AnyObject, CollectionViewReuseCellItem, CollectionViewSizeCellItem, - CollectionViewConfigureCellItem, CollectionViewGeneralCellItem, CollectionViewCellItemDataSource { + associatedtype Cell + func configure(cell: Cell, at indexPath: IndexPath) } // MARK: - CollectionViewReuseCellItemProtocol @@ -28,12 +29,6 @@ public protocol CollectionViewSizeCellItem { func size(for collectionView: UICollectionView, with layout: UICollectionViewLayout, at indexPath: IndexPath) -> CGSize } -// MARK: - CollectionViewConfigureCellItemProtocol - -public protocol CollectionViewConfigureCellItem { - func cell(for collectionView: UICollectionView, at indexPath: IndexPath) -> UICollectionViewCell -} - // MARK: - CollectionViewGeneralCellItemProtocol public typealias ActionHandler = ((UICollectionView, IndexPath) -> Void) @@ -84,6 +79,8 @@ private enum AssociatedKeys { static var didUnhighlightHandler = "rsb_didUnhighlightHandler" static var didSelectHandler = "rsb_didSelectHandler" static var didDeselectHandler = "rsb_didDeselectHandler" + static var shouldSelectHandler = "rsb_shouldSelectHandler" + static var shouldDeselectHandler = "rsb_shouldDeselectHandler" static var willDisplayCellHandler = "rsb_willDisplayCellHandler" static var willDisplayViewHandler = "rsb_willDisplayViewHandler" static var didEndDisplayingCellHandler = "rsb_didEndDisplayingCellHandler" @@ -140,6 +137,24 @@ public extension CollectionViewGeneralCellItem { } } + var itemShouldSelectHandler: ActionResolver? { + get { + return ClosureWrapper.handler(for: self, key: &AssociatedKeys.shouldSelectHandler) + } + set { + ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.shouldSelectHandler) + } + } + + var itemShouldDeselectHandler: ActionResolver? { + get { + return ClosureWrapper.handler(for: self, key: &AssociatedKeys.shouldDeselectHandler) + } + set { + ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.shouldDeselectHandler) + } + } + var itemWillDisplayCellHandler: ActionHandler? { get { return ClosureWrapper.handler(for: self, key: &AssociatedKeys.willDisplayCellHandler) diff --git a/Sources/Protocols/SectionItem/CollectionViewSectionItem.swift b/Sources/Protocols/SectionItem/CollectionViewSectionItem.swift index cf56bc9..53be0df 100644 --- a/Sources/Protocols/SectionItem/CollectionViewSectionItem.swift +++ b/Sources/Protocols/SectionItem/CollectionViewSectionItem.swift @@ -8,7 +8,7 @@ import UIKit.UICollectionView public protocol CollectionViewSectionItem: AnyObject { - var cellItems: [CollectionViewCellItem] { get set } + var cellItems: [CollectionViewManager.CellItem] { get set } var reusableViewItems: [CollectionViewReusableViewItem] { get set } func inset(for collectionView: UICollectionView, with layout: UICollectionViewLayout) -> UIEdgeInsets From c27b7932822215425b7f61b235c62198a93c00c0 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Thu, 26 Jul 2018 11:09:12 +0600 Subject: [PATCH 09/54] Turn off generics --- .../CellItems/ImageCellItem.swift | 4 +- .../MainViewController.swift | 4 +- Sources/CollectionViewManager.swift | 2 +- .../CellItem/AnyCollectionViewCellItem.swift | 60 +++++++++---------- .../CellItem/CollectionViewCellItem.swift | 3 +- 5 files changed, 36 insertions(+), 37 deletions(-) diff --git a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift index 924b46c..cfc695a 100644 --- a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift @@ -15,8 +15,8 @@ final class ImageCellItem: CollectionViewCellItem { private let image: UIImage private let selectionHandler: (UIImage) -> Void - func configure(cell: ImageCollectionViewCell, at indexPath: IndexPath) { - cell.imageView.image = image + func configure(cell: UICollectionViewCell, at indexPath: IndexPath) { +// cell.imageView.image = image } init(image: UIImage, selectionHandler: @escaping (UIImage) -> Void) { diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index 7ee4f46..305a7d4 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -29,11 +29,11 @@ class MainViewController: UIViewController { var imagesSectionItem: CollectionViewSectionItem { let sectionItem = GeneralCollectionViewSectionItem() sectionItem.cellItems = images.map { - AnyCollectionViewCellItem(ImageCellItem(image: $0) { [weak self] image in + ImageCellItem(image: $0) { [weak self] image in let detailViewController = DetailViewController() detailViewController.image = image self?.navigationController?.pushViewController(detailViewController, animated: true) - }) + } } return sectionItem } diff --git a/Sources/CollectionViewManager.swift b/Sources/CollectionViewManager.swift index 46f8fd3..d9dfa68 100644 --- a/Sources/CollectionViewManager.swift +++ b/Sources/CollectionViewManager.swift @@ -9,7 +9,7 @@ import UIKit.UICollectionView open class CollectionViewManager: NSObject { public typealias SectionItem = CollectionViewSectionItem - public typealias CellItem = AnyCollectionViewCellItem + public typealias CellItem = CollectionViewCellItem public typealias Completion = (Bool) -> Void /// `UICollectionView` object for managing diff --git a/Sources/Protocols/CellItem/AnyCollectionViewCellItem.swift b/Sources/Protocols/CellItem/AnyCollectionViewCellItem.swift index 15419a2..c3bf078 100644 --- a/Sources/Protocols/CellItem/AnyCollectionViewCellItem.swift +++ b/Sources/Protocols/CellItem/AnyCollectionViewCellItem.swift @@ -4,33 +4,33 @@ import UIKit.UICollectionView -open class AnyCollectionViewCellItem: CollectionViewCellItem where T: UICollectionViewCell { - - public typealias Cell = T - - private let _configure: (T, IndexPath) -> Void - private let _reuseType: () -> ReuseType - - public var reuseType: ReuseType { - return _reuseType() - } - - public init(_ cellItem: U) where U.Cell == T { - _configure = { [weak cellItem] (cell, indexPath) in - guard let cellItem = cellItem else { - return - } - cellItem.configure(cell: cell, at: indexPath) - } - _reuseType = { [weak cellItem] in - guard let cellItem = cellItem else { - fatalError("It is impossible to create cell item without reuse type") - } - return cellItem.reuseType - } - } - - public func configure(cell: Cell, at indexPath: IndexPath) { - _configure(cell, indexPath) - } -} +//open class AnyCollectionViewCellItem: CollectionViewCellItem { +// +// public typealias Cell = T +// +// private let _configure: (T, IndexPath) -> Void +// private let _reuseType: () -> ReuseType +// +// public var reuseType: ReuseType { +// return _reuseType() +// } +// +// public init(_ cellItem: U) where U.Cell == T { +// _configure = { [weak cellItem] (cell, indexPath) in +// guard let cellItem = cellItem else { +// return +// } +// cellItem.configure(cell: cell, at: indexPath) +// } +// _reuseType = { [weak cellItem] in +// guard let cellItem = cellItem else { +// fatalError("It is impossible to create cell item without reuse type") +// } +// return cellItem.reuseType +// } +// } +// +// public func configure(cell: Cell, at indexPath: IndexPath) { +// _configure(cell, indexPath) +// } +//} diff --git a/Sources/Protocols/CellItem/CollectionViewCellItem.swift b/Sources/Protocols/CellItem/CollectionViewCellItem.swift index ffb19f7..48bacfe 100644 --- a/Sources/Protocols/CellItem/CollectionViewCellItem.swift +++ b/Sources/Protocols/CellItem/CollectionViewCellItem.swift @@ -13,8 +13,7 @@ public protocol CollectionViewCellItem: AnyObject, CollectionViewSizeCellItem, CollectionViewGeneralCellItem, CollectionViewCellItemDataSource { - associatedtype Cell - func configure(cell: Cell, at indexPath: IndexPath) + func configure(cell: UICollectionViewCell, at indexPath: IndexPath) } // MARK: - CollectionViewReuseCellItemProtocol From e00aa0b6167fa88beb6f5ce2cba074a0748e46b3 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Thu, 26 Jul 2018 11:19:25 +0600 Subject: [PATCH 10/54] Turn back image setting --- .../CollectionViewToolsExample/CellItems/ImageCellItem.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift index cfc695a..6570ae8 100644 --- a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift @@ -16,7 +16,10 @@ final class ImageCellItem: CollectionViewCellItem { private let selectionHandler: (UIImage) -> Void func configure(cell: UICollectionViewCell, at indexPath: IndexPath) { -// cell.imageView.image = image + guard let cell = cell as? Cell else { + return + } + cell.imageView.image = image } init(image: UIImage, selectionHandler: @escaping (UIImage) -> Void) { From c28f24e1b6774feab6a524794ff5692082fa2be1 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 27 Jul 2018 15:33:44 +0600 Subject: [PATCH 11/54] Initial commin of update collection view methods --- README.md | 2 +- Sources/CollectionViewManager.swift | 381 +++++++++++++----- ...r+UICollectionViewDelegateFlowLayout.swift | 8 +- .../UICollectionView+Registration.swift | 5 - .../CellItem/CollectionViewCellItem.swift | 2 +- .../CollectionViewReusableViewItem.swift | 6 +- 6 files changed, 289 insertions(+), 115 deletions(-) diff --git a/README.md b/README.md index 4ff1cb5..787954e 100755 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ manager.sectionItems = [sectionItem] #### Cell item implementation ```swift -class ExampleCollectionViewCellItem: CollectionViewCellItemProtocol { +class ExampleCollectionViewCellItem: CollectionViewCellItem { private let title: String diff --git a/Sources/CollectionViewManager.swift b/Sources/CollectionViewManager.swift index d9dfa68..88602f3 100644 --- a/Sources/CollectionViewManager.swift +++ b/Sources/CollectionViewManager.swift @@ -26,7 +26,7 @@ open class CollectionViewManager: NSObject { self.collectionView.prefetchDataSource = self } else { - fatalError("Prefetching allowed only on iOS versions greater than or equal to 10.0") + print("[ERROR] Prefetching allowed only on iOS versions >= 10.0") } } } @@ -44,16 +44,19 @@ open class CollectionViewManager: NSObject { internal var _sectionItems = [CollectionViewSectionItem]() { didSet { - _sectionItems.forEach { register($0) } + _sectionItems.forEach { sectionItem in + register(sectionItem) + } } } - /// Array of `CollectionViewSectionItemProtocol` objects, which respond for configuration of specified section in collection view. + /// Array of `CollectionViewSectionItem` objects, which respond for configuration of specified section in collection view. public var sectionItems: [CollectionViewSectionItem] { get { return _sectionItems } set { + //TODO: Fix every time reload _sectionItems = newValue collectionView.reloadData() } @@ -73,7 +76,9 @@ open class CollectionViewManager: NSObject { /// /// - Parameter index: The index of the section item to access. public subscript(index: Int) -> CollectionViewSectionItem? { - guard index < _sectionItems.count else { return nil } + guard (0..<_sectionItems.count).contains(index) else { + return nil + } return _sectionItems[index] } @@ -84,46 +89,79 @@ open class CollectionViewManager: NSObject { return cellItem(for: indexPath) } - /// Reloads cells, associated with passed cell items inside specified section, associated with passed section item + /// Reloads cells, associated with passed cell items. If section item is nil this method search through all section items until found + /// all cell items which should be reload. If you have concrete section item it might be more efficient to set it. /// /// - Parameters: /// - cellItems: Cell items to reload /// - sectionItem: Section item that contains cell items to reload /// - completion: A closure that either specifies any additional actions which should be performed after reloading. open func reloadCellItems(_ cellItems: [CellItem], - inSectionItem sectionItem: CollectionViewSectionItem, + inSectionItem sectionItem: CollectionViewSectionItem?, completion: Completion? = nil) { - let section = sectionItems.index(where: {$0 === sectionItem})! - var indexPaths = [IndexPath]() - - for cellItem in cellItems { - guard let item = sectionItem.cellItems.index(where: {$0 === cellItem}) else { - fatalError("Unable to reload cell items that are not contained in section item.") + var indexPaths: [IndexPath] = [] + if let sectionItem = sectionItem { + guard let section = sectionItems.index(where: { element in + element === sectionItem + }) else { + print("[ERROR] There are no sectionItem \(sectionItem) in sectionItems array") + return } - indexPaths.append(IndexPath(item: item, section: section)) + + for cellItem in cellItems { + guard let item = sectionItem.cellItems.index(where: { element in + element === cellItem + }) else { + print("[ERROR] Unable to reload cell items that are not contained in section item") + return + } + indexPaths.append(IndexPath(item: item, section: section)) + } + } + else { + indexPaths = calculateIndexPaths(of: cellItems) } - collectionView.performBatchUpdates({ - self.collectionView.reloadItems(at: indexPaths) + perform(updates: { collectionView in + collectionView?.reloadItems(at: indexPaths) }, completion: completion) } /// Scrolls through the collection view until a cell, associated with passed cell item is at a particular location on the screen. /// Invoking this method does not cause the delegate to receive a scrollViewDidScroll(_:) message, as is normal for programmatically invoked user interface operations. + /// If section item is nil this method tries to calculate index path by searching through all section items and cell items. /// /// - Parameters: - /// - cellItem: `CollectionViewCellItemProtocol` object, that responds for configuration of cell at the specified index path. - /// - sectionItem: `CollectionViewSectionItemProtocol` object, that contains passed cell item + /// - cellItem: `CollectionViewCellItem` object, that responds for configuration of cell at the specified index path. + /// - sectionItem: `CollectionViewSectionItem` object, that contains passed cell item /// - scrollPosition: A constant that identifies a relative position in the collection view (top, middle, bottom) for item when scrolling concludes. See UICollectionViewScrollPosition for descriptions of valid constants. /// - animated: true if you want to animate the change in position; false if it should be immediate. open func scroll(to cellItem: CellItem, - in sectionItem: SectionItem, + in sectionItem: SectionItem?, at scrollPosition: UICollectionViewScrollPosition, animated: Bool = true) { - let optionalSectionIndex = _sectionItems.index { $0 === sectionItem } - let optionalCellIndex = sectionItem.cellItems.index { $0 === cellItem } - guard let sectionIndex = optionalSectionIndex, - let cellIndex = optionalCellIndex else { return } + var section: Int? + if let index = _sectionItems.index(where: { element in + return element === sectionItem + }) { + section = index + } + else { + for (index, sectionItem) in _sectionItems.enumerated() where sectionItem.cellItems.contains(where: { element in + return cellItem === element + }) { + section = index + } + } + + guard let sectionIndex = section, + let cellIndex = _sectionItems[sectionIndex].cellItems.index(where: { element in + return element === cellItem + }) else { + print("[ERROR] Can't scroll to cell item \(cellItem) because manager isn't contains it") + return + } + let indexPath = IndexPath(item: cellIndex, section: sectionIndex) collectionView.scrollToItem(at: indexPath, at: scrollPosition, animated: animated) } @@ -154,35 +192,56 @@ open class CollectionViewManager: NSObject { return nil } - // MARK: - Private + /// Returns the index of the section item if possible. + /// + /// - Parameter sectionItem: The section item which index must be found + open func sectionIndex(for sectionItem: SectionItem) -> Int? { + return _sectionItems.index { element in + return element === sectionItem + } + } + + // MARK: - Registration - fileprivate func register(_ sectionItem: CollectionViewSectionItem) { - sectionItem.cellItems.forEach { register($0) } - sectionItem.reusableViewItems.forEach { $0.register(for: collectionView) } + /// Use this function to force cells and reusable views registration process if you override add/replace/reload methods + /// + /// - Parameter sectionItem: The section item with cell items which need to be registered + open func register(_ sectionItem: CollectionViewSectionItem) { + sectionItem.cellItems.forEach { cellItem in + register(cellItem) + } + sectionItem.reusableViewItems.forEach { reusableViewItem in + reusableViewItem.register(for: collectionView) + } } - fileprivate func register(_ cellItem: CellItem) { + /// Use this function to force cells registration process if you override add/replace/reload methods + /// + /// - Parameter cellItem: The cell item which need to be registered + open func register(_ cellItem: CellItem) { collectionView.register(by: cellItem.reuseType) } -} - -// MARK: - Batch updates for cell items -extension CollectionViewManager { - + + // MARK: - Updates for cell items + /// Replaces all cell items which are contained in specified section item with new cell items, and then replaces cells at the corresponding index paths of collection view. /// /// - Parameters: /// - cellItems: An array of cell items to set, which respond for cell configuration at specified index path - /// - sectionItem: Section item within which cell items should be setted + /// - sectionItem: Section item within which cell items should be set /// - completion: A closure that either specifies any additional actions which should be performed after setting. - open func set(_ cellItems: [CellItem], to sectionItem: SectionItem, completion: Completion? = nil) { - cellItems.forEach { cellItem in - register(cellItem) + open func replaceAllCellItems(in sectionItem: SectionItem, with cellItems: [CellItem], completion: Completion? = nil) { + guard let section = sectionIndex(for: sectionItem) else { + return } - sectionItem.cellItems = cellItems - collectionView.performBatchUpdates({ - self.collectionView.reloadData() + perform(updates: { collectionView in + cellItems.forEach { cellItem in + register(cellItem) + } + sectionItem.cellItems = cellItems + + collectionView?.reloadSections([section]) }, completion: completion) } @@ -194,21 +253,44 @@ extension CollectionViewManager { /// - indexes: An array of locations, that contains indexes of positions where specified cell items should be inserted /// - completion: A closure that either specifies any additional actions which should be performed after insertion. open func insert(_ cellItems: [CellItem], to sectionItem: SectionItem, at indexes: [Int], completion: Completion? = nil) { - zip(cellItems, indexes).forEach { cellItem, index in - precondition(index <= sectionItem.cellItems.count, - "Unable to insert item at index that is larger than count of cell items in this section.") - register(cellItem) - sectionItem.cellItems.insert(cellItem, at: index) + guard let section = sectionIndex(for: sectionItem) else { + return } - let sectionIndex = _sectionItems.index { $0 === sectionItem }! - let indexPaths = indexes.map { IndexPath(item: $0, section: sectionIndex) } - - collectionView.performBatchUpdates({ - self.collectionView.insertItems(at: indexPaths) + perform(updates: { collectionView in + zip(cellItems, indexes).forEach { cellItem, index in + register(cellItem) + sectionItem.cellItems.insert(cellItem, at: index) + } + + let indexPaths: [IndexPath] = indexes.map { index in + return .init(row: index, section: section) + } + + collectionView?.insertItems(at: indexPaths) }, completion: completion) } + /// Inserts cell items to the specified section item, and then inserts cells at the end of the section. + /// + /// - Parameters: + /// - cellItems: An array of cell items to insert, which respond for cell configuration at specified index path + /// - sectionItem: Section item within which cell items should be inserted + /// - completion: A closure that either specifies any additional actions which should be performed after insertion. + open func append(_ cellItems: [CellItem], to sectionItem: SectionItem, completion: Completion? = nil) { + insert(cellItems, to: sectionItem, at: Array(sectionItem.cellItems.count...cellItems.count), completion: completion) + } + + /// Inserts cell items to the specified section item, and then inserts cells at the beginning of the section. + /// + /// - Parameters: + /// - cellItems: An array of cell items to insert, which respond for cell configuration at specified index path + /// - sectionItem: Section item within which cell items should be inserted + /// - completion: A closure that either specifies any additional actions which should be performed after insertion. + open func prepend(_ cellItems: [CellItem], to sectionItem: SectionItem, completion: Completion? = nil) { + insert(cellItems, to: sectionItem, at: Array(0...cellItems.count), completion: completion) + } + /// Replaces cell items inside the specified section item, and then replaces corresponding cells within section. /// /// - Parameters: @@ -217,100 +299,167 @@ extension CollectionViewManager { /// - sectionItem: Section item within which cell items should be replaced /// - completion: A closure that either specifies any additional actions which should be performed after replacing. open func replace(cellItemsAt indexes: [Int], with cellItems: [CellItem], in sectionItem: SectionItem, completion: Completion? = nil) { - precondition(indexes.count == cellItems.count, "Count of indexes and count of replacement cell items should be equal.") - cellItems.forEach { register($0) } - zip(cellItems, indexes).forEach { sectionItem.cellItems[$1] = $0 } - - let sectionIndex = _sectionItems.index { $0 === sectionItem }! - let indexPaths = indexes.map { IndexPath(item: $0, section: sectionIndex) } + guard let section = sectionIndex(for: sectionItem), + indexes.count > 0 else { + return + } - collectionView.performBatchUpdates({ - self.collectionView.reloadItems(at: indexPaths) + perform(updates: { collectionView in + cellItems.forEach { cellItem in + register(cellItem) + } + + if indexes.count == cellItems.count { + zip(cellItems, indexes).forEach { cellItem, index in + sectionItem.cellItems[index] = cellItem + } + let indexPaths: [IndexPath] = indexes.map { index in + return .init(row: index, section: section) + } + collectionView?.reloadItems(at: indexPaths) + } + else { + var removeIndexPaths: [IndexPath] = [] + let firstIndex = indexes[0] + indexes.sorted().reversed().forEach { index in + sectionItem.cellItems.remove(at: index) + removeIndexPaths.append(.init(row: index, section: section)) + } + + let insertIndexPaths: [IndexPath] = Array(firstIndex.. 0 else { + return + } + perform(updates: { collectionView in + if indexes.count == sectionItems.count { + sectionItems.forEach { sectionItem in + register(sectionItem) + } + zip(sectionItems, indexes).forEach { sectionItem, index in + _sectionItems[index] = sectionItem + } + collectionView?.reloadSections(IndexSet(indexes)) + } + else { + let firstIndex = indexes[0] + indexes.sorted().reversed().forEach { index in + _sectionItems.remove(at: index) + } + + collectionView?.deleteSections(.init(indexes)) + collectionView?.insertSections(.init(firstIndex.. Void, completion: Completion?) { + collectionView.performBatchUpdates({ [weak collectionView] in + updates(collectionView) + }, completion: completion) + } + + private func calculateIndexPaths(of cellItems: [CellItem]) -> [IndexPath] { + var indexPaths: [IndexPath] = [] + outer: for (sectionIndex, sectionItem) in sectionItems.enumerated() { + for (cellIndex, cellItem) in sectionItem.cellItems.enumerated() where cellItems.contains(where: { element in + return cellItem === element + }) { + indexPaths.append(.init(row: cellIndex, section: sectionIndex)) + if indexPaths.count == cellItems.count { + break outer + } + } + } + return indexPaths + } } diff --git a/Sources/Extensions/CollectionViewManager+UICollectionViewDelegateFlowLayout.swift b/Sources/Extensions/CollectionViewManager+UICollectionViewDelegateFlowLayout.swift index 6c2b84c..acec44a 100644 --- a/Sources/Extensions/CollectionViewManager+UICollectionViewDelegateFlowLayout.swift +++ b/Sources/Extensions/CollectionViewManager+UICollectionViewDelegateFlowLayout.swift @@ -42,7 +42,9 @@ extension CollectionViewManager: UICollectionViewDelegateFlowLayout { layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { let optionalItem = _sectionItems[section].reusableViewItems.filter { $0.type == .header }.first - guard let item = optionalItem else { return .zero } + guard let item = optionalItem else { + return .zero + } return item.size(for: collectionView, with: collectionViewLayout) } @@ -50,7 +52,9 @@ extension CollectionViewManager: UICollectionViewDelegateFlowLayout { layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize { let optionalItem = _sectionItems[section].reusableViewItems.filter { $0.type == .footer }.first - guard let item = optionalItem else { return .zero } + guard let item = optionalItem else { + return .zero + } return item.size(for: collectionView, with: collectionViewLayout) } } diff --git a/Sources/Extensions/UICollectionView+Registration.swift b/Sources/Extensions/UICollectionView+Registration.swift index ce28193..78182cd 100644 --- a/Sources/Extensions/UICollectionView+Registration.swift +++ b/Sources/Extensions/UICollectionView+Registration.swift @@ -36,11 +36,6 @@ public extension UICollectionView { } } - func dequeueReusableCell(for indexPath: IndexPath) -> T { - // swiftlint:disable:next force_cast - return dequeueReusableCell(withReuseIdentifier: NSStringFromClass(T.self), for: indexPath) as! T - } - func dequeueReusableSupplementaryView(with type: ReusableViewType, at indexPath: IndexPath) -> T { // swiftlint:disable:next force_cast return dequeueReusableSupplementaryView(ofKind: type.kind, withReuseIdentifier: "\(T.self)", for: indexPath) as! T diff --git a/Sources/Protocols/CellItem/CollectionViewCellItem.swift b/Sources/Protocols/CellItem/CollectionViewCellItem.swift index 48bacfe..5ea568a 100644 --- a/Sources/Protocols/CellItem/CollectionViewCellItem.swift +++ b/Sources/Protocols/CellItem/CollectionViewCellItem.swift @@ -6,7 +6,7 @@ import UIKit.UICollectionView -// MARK: - CollectionViewCellItemProtocol +// MARK: - CollectionViewCellItem public protocol CollectionViewCellItem: AnyObject, CollectionViewReuseCellItem, diff --git a/Sources/Protocols/ReuseViewItem/CollectionViewReusableViewItem.swift b/Sources/Protocols/ReuseViewItem/CollectionViewReusableViewItem.swift index 44b2a3e..03393e0 100644 --- a/Sources/Protocols/ReuseViewItem/CollectionViewReusableViewItem.swift +++ b/Sources/Protocols/ReuseViewItem/CollectionViewReusableViewItem.swift @@ -11,8 +11,10 @@ public enum ReusableViewType { public var kind: String { switch self { - case .header: return UICollectionElementKindSectionHeader - case .footer: return UICollectionElementKindSectionFooter + case .header: + return UICollectionElementKindSectionHeader + case .footer: return + UICollectionElementKindSectionFooter } } } From 2fae396eb07b4dad15c694164635c420cb4428f4 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 3 Aug 2018 14:26:16 +0600 Subject: [PATCH 12/54] Update docs --- Sources/CollectionViewManager.swift | 42 +++++++++++++++++++---------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/Sources/CollectionViewManager.swift b/Sources/CollectionViewManager.swift index 88602f3..b6422f7 100644 --- a/Sources/CollectionViewManager.swift +++ b/Sources/CollectionViewManager.swift @@ -15,10 +15,13 @@ open class CollectionViewManager: NSObject { /// `UICollectionView` object for managing public unowned let collectionView: UICollectionView - /// The methods declared by the UIScrollViewDelegate protocol allow the adopting delegate to respond to messages from the UIScrollView class and thus respond to, and in some affect, operations such as scrolling, zooming, deceleration of scrolled content, and scrolling animations. + /// The methods declared by the UIScrollViewDelegate protocol allow the adopting delegate to respond to messages + /// from the UIScrollView class and thus respond to, and in some affect, operations such as scrolling, zooming, + /// deceleration of scrolled content, and scrolling animations. public weak var scrollDelegate: UIScrollViewDelegate? - /// The property that determines whether should be used data source prefetching. Prefetching allowed only on iOS versions greater than or equal to 10.0 + /// The property that determines whether should be used data source prefetching. + /// Prefetching is available only on iOS versions greater than or equal to 10.0 public var isPrefetchingEnabled = false { didSet { if isPrefetchingEnabled { @@ -26,7 +29,7 @@ open class CollectionViewManager: NSObject { self.collectionView.prefetchDataSource = self } else { - print("[ERROR] Prefetching allowed only on iOS versions >= 10.0") + print("[WARNING] Prefetching is available only on iOS versions >= 10.0") } } } @@ -128,13 +131,15 @@ open class CollectionViewManager: NSObject { } /// Scrolls through the collection view until a cell, associated with passed cell item is at a particular location on the screen. - /// Invoking this method does not cause the delegate to receive a scrollViewDidScroll(_:) message, as is normal for programmatically invoked user interface operations. + /// Invoking this method does not cause the delegate to receive a scrollViewDidScroll(_:) message, as is normal for programmatically + /// invoked user interface operations. /// If section item is nil this method tries to calculate index path by searching through all section items and cell items. /// /// - Parameters: /// - cellItem: `CollectionViewCellItem` object, that responds for configuration of cell at the specified index path. /// - sectionItem: `CollectionViewSectionItem` object, that contains passed cell item - /// - scrollPosition: A constant that identifies a relative position in the collection view (top, middle, bottom) for item when scrolling concludes. See UICollectionViewScrollPosition for descriptions of valid constants. + /// - scrollPosition: A constant that identifies a relative position in the collection view (top, middle, bottom) for item when + /// scrolling concludes. See UICollectionViewScrollPosition for descriptions of valid constants. /// - animated: true if you want to animate the change in position; false if it should be immediate. open func scroll(to cellItem: CellItem, in sectionItem: SectionItem?, @@ -171,7 +176,8 @@ open class CollectionViewManager: NSObject { /// Returns the cell item at the specified index path. /// /// - Parameter indexPath: The index path locating the item in the collection view. - /// - Returns: A cell item associated with cell of the collection, or nil if the cell item wasn't added to manager or indexPath is out of range. + /// - Returns: A cell item associated with cell of the collection, or nil if the cell item + /// wasn't added to manager or indexPath is out of range. open func cellItem(for indexPath: IndexPath) -> CellItem? { if let cellItems = self.sectionItem(for: indexPath)?.cellItems { if indexPath.row < cellItems.count { @@ -184,7 +190,8 @@ open class CollectionViewManager: NSObject { /// Returns the section item at the specified index path. /// /// - Parameter indexPath: The index path locating the section in the collection view. - /// - Returns: A section item associated with section of the collection, or nil if the section item wasn't added to manager or indexPath.section is out of range. + /// - Returns: A section item associated with section of the collection, or nil if the section item + /// wasn't added to manager or indexPath.section is out of range. open func sectionItem(for indexPath: IndexPath) -> SectionItem? { if indexPath.section < _sectionItems.count { return _sectionItems[indexPath.section] @@ -224,7 +231,8 @@ open class CollectionViewManager: NSObject { // MARK: - Updates for cell items - /// Replaces all cell items which are contained in specified section item with new cell items, and then replaces cells at the corresponding index paths of collection view. + /// Replaces all cell items which are contained in specified section item with new cell items, and then replaces cells + /// at the corresponding index paths of collection view. /// /// - Parameters: /// - cellItems: An array of cell items to set, which respond for cell configuration at specified index path @@ -245,7 +253,8 @@ open class CollectionViewManager: NSObject { }, completion: completion) } - /// Inserts cell items to the specified section item, and then inserts cells at the locations identified by array of corresponding index paths. + /// Inserts cell items to the specified section item, and then inserts cells + /// at the locations identified by array of corresponding index paths. /// /// - Parameters: /// - cellItems: An array of cell items to insert, which respond for cell configuration at specified index path @@ -352,7 +361,8 @@ open class CollectionViewManager: NSObject { }, completion: completion) } - /// Removes cell items, that are preserved at specified indexes inside section item, and then removes cells at the corresponding locations. + /// Removes cell items, that are preserved at specified indexes inside section item, + /// and then removes cells at the corresponding locations. /// /// - Parameters: /// - cellItems: Cell items to remove @@ -382,7 +392,8 @@ open class CollectionViewManager: NSObject { /// /// - Parameters: /// - sectionItems: An array of `CollectionViewSectionItem` objects to insert - /// - indexes: An array of locations that specifies the sections to insert in the collection view. If a section already exists at the specified index location, it is moved down one index location. + /// - indexes: An array of locations that specifies the sections to insert in the collection view. + /// If a section already exists at the specified index location, it is moved down one index location. /// - completion: A closure that either specifies any additional actions which should be performed after insertion. open func insert(_ sectionItems: [CollectionViewSectionItem], at indexes: [Int], completion: Completion? = nil) { perform(updates: { collectionView in @@ -401,7 +412,8 @@ open class CollectionViewManager: NSObject { /// /// - Parameters: /// - sectionItems: An array of `CollectionViewSectionItem` objects to insert - /// - indexes: An array of locations that specifies the sections to insert in the collection view. If a section already exists at the specified index location, it is moved down one index location. + /// - indexes: An array of locations that specifies the sections to insert in the collection view. + /// If a section already exists at the specified index location, it is moved down one index location. /// - completion: A closure that either specifies any additional actions which should be performed after insertion. open func append(_ sectionItems: [CollectionViewSectionItem], completion: Completion? = nil) { insert(sectionItems, at: Array(self.sectionItems.count.. Date: Fri, 10 Aug 2018 12:47:27 +0600 Subject: [PATCH 13/54] Improve example --- .../CellItems/ImageCellItem.swift | 4 +- .../DetailViewController.swift | 4 +- .../MainViewController.swift | 46 +++++++++---------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift index 6570ae8..1da1ada 100644 --- a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift @@ -32,7 +32,9 @@ final class ImageCellItem: CollectionViewCellItem { } func size(for collectionView: UICollectionView, with layout: UICollectionViewLayout, at indexPath: IndexPath) -> CGSize { - return size(for: collectionView, at: indexPath) + let ratio = image.size.width / image.size.height + let width = collectionView.bounds.width / 2 - 16 + return .init(width: width, height: width / ratio) } func didSelect(for collectionView: UICollectionView, at indexPath: IndexPath) { diff --git a/Example/CollectionViewToolsExample/DetailViewController.swift b/Example/CollectionViewToolsExample/DetailViewController.swift index af5ed74..6423ecd 100644 --- a/Example/CollectionViewToolsExample/DetailViewController.swift +++ b/Example/CollectionViewToolsExample/DetailViewController.swift @@ -10,10 +10,10 @@ class DetailViewController: UIViewController { var image: UIImage? private lazy var imageView = UIImageView() - + override func viewDidLoad() { super.viewDidLoad() - + view.backgroundColor = .white view.clipsToBounds = true diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index 305a7d4..2c4fa9a 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -9,16 +9,12 @@ import CollectionViewTools class MainViewController: UIViewController { - let images = [#imageLiteral(resourceName: "nightlife-1"), #imageLiteral(resourceName: "nightlife-2"), #imageLiteral(resourceName: "nightlife-3"), #imageLiteral(resourceName: "nightlife-4"), #imageLiteral(resourceName: "nightlife-5")] - lazy var collectionView: UICollectionView = { let flowLayout = UICollectionViewFlowLayout() - flowLayout.scrollDirection = .horizontal let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) collectionView.scrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: -6, right: 0) collectionView.clipsToBounds = false - collectionView.backgroundColor = .white - collectionView.isPagingEnabled = true + collectionView.backgroundColor = .clear return collectionView }() @@ -26,35 +22,37 @@ class MainViewController: UIViewController { return CollectionViewManager(collectionView: self.collectionView) }() - var imagesSectionItem: CollectionViewSectionItem { - let sectionItem = GeneralCollectionViewSectionItem() - sectionItem.cellItems = images.map { - ImageCellItem(image: $0) { [weak self] image in - let detailViewController = DetailViewController() - detailViewController.image = image - self?.navigationController?.pushViewController(detailViewController, animated: true) - } - } - return sectionItem - } - override func viewDidLoad() { super.viewDidLoad() + navigationItem.title = "Library" + + var images: [UIImage] = [] + for _ in 0..<10 { + images.append(contentsOf: [#imageLiteral(resourceName: "nightlife-1"), #imageLiteral(resourceName: "nightlife-2"), #imageLiteral(resourceName: "nightlife-3"), #imageLiteral(resourceName: "nightlife-4"), #imageLiteral(resourceName: "nightlife-5")]) + } + edgesForExtendedLayout = [] view.addSubview(collectionView) - manager.sectionItems = [imagesSectionItem] + manager.sectionItems = [makeImagesSectionItem(images: images)] } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - - collectionView.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: 200) + collectionView.frame = view.bounds } - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - collectionView.flashScrollIndicators() + func makeImagesSectionItem(images: [UIImage]) -> CollectionViewSectionItem { + let sectionItem = GeneralCollectionViewSectionItem() + sectionItem.cellItems = images.map { image in + ImageCellItem(image: image) { [weak self] image in + let detailViewController = DetailViewController() + detailViewController.image = image + self?.navigationController?.pushViewController(detailViewController, animated: true) + } + } + sectionItem.insets = .init(top: 0, left: 12, bottom: 0, right: 12) + sectionItem.minimumLineSpacing = 8 + return sectionItem } } - From 27f2cdcc23397c180e2d9fae192e69d0d01d0b0a Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 10 Aug 2018 15:16:44 +0600 Subject: [PATCH 14/54] Implement text cell item for actions --- .../project.pbxproj | 8 ++++++ .../CellItems/TextCellItem.swift | 28 +++++++++++++++++++ .../Cells/TextCollectionViewCell.swift | 28 +++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 Example/CollectionViewToolsExample/CellItems/TextCellItem.swift create mode 100644 Example/CollectionViewToolsExample/Cells/TextCollectionViewCell.swift diff --git a/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj b/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj index 3a36927..05e89f6 100644 --- a/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj +++ b/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ 0ACA6FC91F626AF900782C1E /* ImageCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACA6FC81F626AF900782C1E /* ImageCellItem.swift */; }; 0ACA6FCC1F626D2E00782C1E /* ImageCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACA6FCB1F626D2E00782C1E /* ImageCollectionViewCell.swift */; }; 0ACA6FCE1F6275D200782C1E /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACA6FCD1F6275D200782C1E /* DetailViewController.swift */; }; + 629A6E35B86008DC359DB40B /* TextCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A6D49B5E8DB9CAACD07CE /* TextCellItem.swift */; }; + 629A6F552D5D656B36F4F52C /* TextCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A60E846B05B88D62C7880 /* TextCollectionViewCell.swift */; }; FED796BA20EF6B3B0044B82F /* CollectionViewTools.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE6E86B31FB961B20027A8FE /* CollectionViewTools.framework */; }; FED796BB20EF6B3B0044B82F /* CollectionViewTools.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FE6E86B31FB961B20027A8FE /* CollectionViewTools.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ @@ -42,6 +44,8 @@ 0ACA6FC81F626AF900782C1E /* ImageCellItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCellItem.swift; sourceTree = ""; }; 0ACA6FCB1F626D2E00782C1E /* ImageCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCollectionViewCell.swift; sourceTree = ""; }; 0ACA6FCD1F6275D200782C1E /* DetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; + 629A60E846B05B88D62C7880 /* TextCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextCollectionViewCell.swift; sourceTree = ""; }; + 629A6D49B5E8DB9CAACD07CE /* TextCellItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextCellItem.swift; sourceTree = ""; }; FE6E86B31FB961B20027A8FE /* CollectionViewTools.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CollectionViewTools.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -101,6 +105,7 @@ isa = PBXGroup; children = ( 0ACA6FC81F626AF900782C1E /* ImageCellItem.swift */, + 629A6D49B5E8DB9CAACD07CE /* TextCellItem.swift */, ); path = CellItems; sourceTree = ""; @@ -109,6 +114,7 @@ isa = PBXGroup; children = ( 0ACA6FCB1F626D2E00782C1E /* ImageCollectionViewCell.swift */, + 629A60E846B05B88D62C7880 /* TextCollectionViewCell.swift */, ); path = Cells; sourceTree = ""; @@ -192,6 +198,8 @@ 0ACA6FCE1F6275D200782C1E /* DetailViewController.swift in Sources */, 0ACA6FCC1F626D2E00782C1E /* ImageCollectionViewCell.swift in Sources */, 0A3763831F62556300A80613 /* AppDelegate.swift in Sources */, + 629A6E35B86008DC359DB40B /* TextCellItem.swift in Sources */, + 629A6F552D5D656B36F4F52C /* TextCollectionViewCell.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift b/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift new file mode 100644 index 0000000..c6a6900 --- /dev/null +++ b/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift @@ -0,0 +1,28 @@ +// +// Copyright (c) 2018 Rosberry. All rights reserved. +// + +import CollectionViewTools + +final class TextCellItem: CollectionViewCellItem { + + private let text: String + + init(text: String) { + self.text = text + } + + private typealias Cell = TextCollectionViewCell + private(set) var reuseType: ReuseType = .class(Cell.self) + + private func configure(_ cell: UICollectionViewCell) { + guard let cell = cell as? Cell else { + return + } + cell.titleLabel.text = text + } + + func size(for collectionView: UICollectionView, with layout: UICollectionViewLayout, at indexPath: IndexPath) -> CGSize { + return .init(width: collectionView.bounds.width - 2 * 16, height: collectionView.bounds.height) + } +} diff --git a/Example/CollectionViewToolsExample/Cells/TextCollectionViewCell.swift b/Example/CollectionViewToolsExample/Cells/TextCollectionViewCell.swift new file mode 100644 index 0000000..2d473c8 --- /dev/null +++ b/Example/CollectionViewToolsExample/Cells/TextCollectionViewCell.swift @@ -0,0 +1,28 @@ +// +// Copyright (c) 2018 Rosberry. All rights reserved. +// + +import UIKit + +final class TextCollectionViewCell: UICollectionViewCell { + + // MARK: Subviews + + lazy var titleLabel: UILabel = .init() + + // MARK: Life cycle + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override init(frame: CGRect) { + super.init(frame: frame) + contentView.addSubview(titleLabel) + } + + override func layoutSubviews() { + super.layoutSubviews() + titleLabel.frame = contentView.bounds + } +} From 0ba0fdfe1e0eddc9339772217577704436823a4c Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 10 Aug 2018 15:56:30 +0600 Subject: [PATCH 15/54] Implement reset action --- .../CellItems/TextCellItem.swift | 6 +- .../Cells/TextCollectionViewCell.swift | 8 +- .../MainViewController.swift | 88 +++++++++++++++---- 3 files changed, 82 insertions(+), 20 deletions(-) diff --git a/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift b/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift index c6a6900..62822ed 100644 --- a/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift @@ -15,7 +15,7 @@ final class TextCellItem: CollectionViewCellItem { private typealias Cell = TextCollectionViewCell private(set) var reuseType: ReuseType = .class(Cell.self) - private func configure(_ cell: UICollectionViewCell) { + func configure(cell: UICollectionViewCell, at indexPath: IndexPath) { guard let cell = cell as? Cell else { return } @@ -23,6 +23,8 @@ final class TextCellItem: CollectionViewCellItem { } func size(for collectionView: UICollectionView, with layout: UICollectionViewLayout, at indexPath: IndexPath) -> CGSize { - return .init(width: collectionView.bounds.width - 2 * 16, height: collectionView.bounds.height) + let numberOfActionsInRow: CGFloat = 3 + let width = collectionView.bounds.width / numberOfActionsInRow + return .init(width: width - (numberOfActionsInRow + 1) * 8, height: collectionView.bounds.height / 1.4) } } diff --git a/Example/CollectionViewToolsExample/Cells/TextCollectionViewCell.swift b/Example/CollectionViewToolsExample/Cells/TextCollectionViewCell.swift index 2d473c8..5bbfc22 100644 --- a/Example/CollectionViewToolsExample/Cells/TextCollectionViewCell.swift +++ b/Example/CollectionViewToolsExample/Cells/TextCollectionViewCell.swift @@ -8,7 +8,11 @@ final class TextCollectionViewCell: UICollectionViewCell { // MARK: Subviews - lazy var titleLabel: UILabel = .init() + lazy var titleLabel: UILabel = { + let label = UILabel() + label.textAlignment = .center + return label + }() // MARK: Life cycle @@ -18,6 +22,8 @@ final class TextCollectionViewCell: UICollectionViewCell { override init(frame: CGRect) { super.init(frame: frame) + contentView.backgroundColor = .white + contentView.layer.cornerRadius = 4 contentView.addSubview(titleLabel) } diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index 2c4fa9a..b21bad0 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -9,39 +9,74 @@ import CollectionViewTools class MainViewController: UIViewController { - lazy var collectionView: UICollectionView = { - let flowLayout = UICollectionViewFlowLayout() - let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) - collectionView.scrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: -6, right: 0) - collectionView.clipsToBounds = false + var images: [UIImage] { + var images: [UIImage] = [] + for _ in 0..<10 { + images.append(contentsOf: [#imageLiteral(resourceName: "nightlife-1"), #imageLiteral(resourceName: "nightlife-2"), #imageLiteral(resourceName: "nightlife-3"), #imageLiteral(resourceName: "nightlife-4"), #imageLiteral(resourceName: "nightlife-5")]) + } + return images + } + + lazy var mainCollectionViewManager: CollectionViewManager = .init(collectionView: mainCollectionView) + lazy var actionsCollectionViewManager: CollectionViewManager = .init(collectionView: actionsCollectionView) + + // MARK: Subviews + + private lazy var actionsCollectionView: UICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + let view = UICollectionView(frame: .zero, collectionViewLayout: layout) + view.backgroundColor = .lightGray + return view + }() + + lazy var mainCollectionView: UICollectionView = { + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) collectionView.backgroundColor = .clear return collectionView }() - lazy var manager: CollectionViewManager = { - return CollectionViewManager(collectionView: self.collectionView) - }() + // MARK: Lifecycle override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "Library" - - var images: [UIImage] = [] - for _ in 0..<10 { - images.append(contentsOf: [#imageLiteral(resourceName: "nightlife-1"), #imageLiteral(resourceName: "nightlife-2"), #imageLiteral(resourceName: "nightlife-3"), #imageLiteral(resourceName: "nightlife-4"), #imageLiteral(resourceName: "nightlife-5")]) - } - edgesForExtendedLayout = [] - view.addSubview(collectionView) - manager.sectionItems = [makeImagesSectionItem(images: images)] + view.addSubview(mainCollectionView) + view.addSubview(actionsCollectionView) + + resetMainCollection() + actionsCollectionViewManager.sectionItems = [makeActionsSectionItem()] } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - collectionView.frame = view.bounds + + var bottomInset: CGFloat = 0 + if #available(iOS 11.0, *) { + bottomInset = view.safeAreaInsets.bottom + } + let actionsCollectionHeight: CGFloat = 100 + mainCollectionView.frame = .init(x: 0, + y: 0, + width: view.bounds.width, + height: view.bounds.height - actionsCollectionHeight - bottomInset) + actionsCollectionView.frame = .init(x: 0, + y: view.bounds.height - actionsCollectionHeight - bottomInset, + width: view.bounds.width, + height: actionsCollectionHeight) } + // MARK: - Private + + private func resetMainCollection() { + mainCollectionViewManager.sectionItems = [makeImagesSectionItem(images: images)] + mainCollectionView.contentOffset = .zero + } + + // MARK: - Factory methods + func makeImagesSectionItem(images: [UIImage]) -> CollectionViewSectionItem { let sectionItem = GeneralCollectionViewSectionItem() sectionItem.cellItems = images.map { image in @@ -55,4 +90,23 @@ class MainViewController: UIViewController { sectionItem.minimumLineSpacing = 8 return sectionItem } + + // MARK: Actions + + func makeActionsSectionItem() -> CollectionViewSectionItem { + let sectionItem = GeneralCollectionViewSectionItem() + sectionItem.cellItems = [ + makeResetActionCellItem() + ] + sectionItem.insets = .init(top: 0, left: 8, bottom: 0, right: 8) + return sectionItem + } + + func makeResetActionCellItem() -> CollectionViewCellItem { + var cellItem = TextCellItem(text: "Reset") + cellItem.itemDidSelectHandler = { [weak self] _, _ in + self?.resetMainCollection() + } + return cellItem + } } From 384dcec5dcc961d81705246f59727727ee604552 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 10 Aug 2018 16:36:01 +0600 Subject: [PATCH 16/54] Implement prepend action --- .../CellItems/TextCellItem.swift | 8 +-- .../Cells/TextCollectionViewCell.swift | 4 ++ .../MainViewController.swift | 51 +++++++++++++++---- Sources/CollectionViewManager.swift | 4 +- 4 files changed, 53 insertions(+), 14 deletions(-) diff --git a/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift b/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift index 62822ed..9ac9461 100644 --- a/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift @@ -22,9 +22,11 @@ final class TextCellItem: CollectionViewCellItem { cell.titleLabel.text = text } + private static let sizeCell: Cell = .init() func size(for collectionView: UICollectionView, with layout: UICollectionViewLayout, at indexPath: IndexPath) -> CGSize { - let numberOfActionsInRow: CGFloat = 3 - let width = collectionView.bounds.width / numberOfActionsInRow - return .init(width: width - (numberOfActionsInRow + 1) * 8, height: collectionView.bounds.height / 1.4) + let cell: Cell = type(of: self).sizeCell + configure(cell: cell, at: indexPath) + let cellSize = cell.sizeThatFits(.init(width: collectionView.bounds.size.width, height: .greatestFiniteMagnitude)) + return .init(width: cellSize.width + 2 * 12, height: collectionView.bounds.height / 1.4) } } diff --git a/Example/CollectionViewToolsExample/Cells/TextCollectionViewCell.swift b/Example/CollectionViewToolsExample/Cells/TextCollectionViewCell.swift index 5bbfc22..4428ec4 100644 --- a/Example/CollectionViewToolsExample/Cells/TextCollectionViewCell.swift +++ b/Example/CollectionViewToolsExample/Cells/TextCollectionViewCell.swift @@ -31,4 +31,8 @@ final class TextCollectionViewCell: UICollectionViewCell { super.layoutSubviews() titleLabel.frame = contentView.bounds } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + return titleLabel.sizeThatFits(.init(width: .greatestFiniteMagnitude, height: size.height)) + } } diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index b21bad0..eb6d905 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -9,10 +9,13 @@ import CollectionViewTools class MainViewController: UIViewController { + var initialImages: [UIImage] { + return [#imageLiteral(resourceName: "nightlife-1"), #imageLiteral(resourceName: "nightlife-2"), #imageLiteral(resourceName: "nightlife-3"), #imageLiteral(resourceName: "nightlife-4"), #imageLiteral(resourceName: "nightlife-5")] + } var images: [UIImage] { var images: [UIImage] = [] for _ in 0..<10 { - images.append(contentsOf: [#imageLiteral(resourceName: "nightlife-1"), #imageLiteral(resourceName: "nightlife-2"), #imageLiteral(resourceName: "nightlife-3"), #imageLiteral(resourceName: "nightlife-4"), #imageLiteral(resourceName: "nightlife-5")]) + images.append(contentsOf: initialImages) } return images } @@ -80,33 +83,63 @@ class MainViewController: UIViewController { func makeImagesSectionItem(images: [UIImage]) -> CollectionViewSectionItem { let sectionItem = GeneralCollectionViewSectionItem() sectionItem.cellItems = images.map { image in - ImageCellItem(image: image) { [weak self] image in - let detailViewController = DetailViewController() - detailViewController.image = image - self?.navigationController?.pushViewController(detailViewController, animated: true) - } + return makeImageCellItem(image: image) } sectionItem.insets = .init(top: 0, left: 12, bottom: 0, right: 12) sectionItem.minimumLineSpacing = 8 return sectionItem } + private func makeImageCellItem(image: UIImage) -> ImageCellItem { + return ImageCellItem(image: image) { [weak self] image in + let detailViewController = DetailViewController() + detailViewController.image = image + self?.navigationController?.pushViewController(detailViewController, animated: true) + } + } + // MARK: Actions func makeActionsSectionItem() -> CollectionViewSectionItem { let sectionItem = GeneralCollectionViewSectionItem() sectionItem.cellItems = [ - makeResetActionCellItem() + makeResetActionCellItem(), + makePrependCellItemsActionCellItem() ] sectionItem.insets = .init(top: 0, left: 8, bottom: 0, right: 8) + sectionItem.minimumInteritemSpacing = 8 + sectionItem.minimumLineSpacing = 8 return sectionItem } func makeResetActionCellItem() -> CollectionViewCellItem { - var cellItem = TextCellItem(text: "Reset") - cellItem.itemDidSelectHandler = { [weak self] _, _ in + return makeActionCellItem(title: "Reset") { [weak self] in self?.resetMainCollection() } + } + + func makePrependCellItemsActionCellItem() -> CollectionViewCellItem { + return makeActionCellItem(title: "Prepend cell items") { [weak self] in + guard let `self` = self else { + return + } + guard let sectionItem = self.mainCollectionViewManager.sectionItems.first else { + return + } + let cellItems = self.initialImages.map { image in + return self.makeImageCellItem(image: image) + } + self.mainCollectionViewManager.prepend(cellItems, to: sectionItem) + } + } + + // MARK: Common + + func makeActionCellItem(title: String, action: @escaping (() -> Void)) -> CollectionViewCellItem { + var cellItem = TextCellItem(text: title) + cellItem.itemDidSelectHandler = { _, _ in + action() + } return cellItem } } diff --git a/Sources/CollectionViewManager.swift b/Sources/CollectionViewManager.swift index b6422f7..c24f70d 100644 --- a/Sources/CollectionViewManager.swift +++ b/Sources/CollectionViewManager.swift @@ -287,7 +287,7 @@ open class CollectionViewManager: NSObject { /// - sectionItem: Section item within which cell items should be inserted /// - completion: A closure that either specifies any additional actions which should be performed after insertion. open func append(_ cellItems: [CellItem], to sectionItem: SectionItem, completion: Completion? = nil) { - insert(cellItems, to: sectionItem, at: Array(sectionItem.cellItems.count...cellItems.count), completion: completion) + insert(cellItems, to: sectionItem, at: Array(sectionItem.cellItems.count.. Date: Fri, 10 Aug 2018 16:47:05 +0600 Subject: [PATCH 17/54] Implement append action --- .../MainViewController.swift | 22 +++++++++++++++++-- Sources/CollectionViewManager.swift | 5 ++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index eb6d905..a7f0c2c 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -14,7 +14,7 @@ class MainViewController: UIViewController { } var images: [UIImage] { var images: [UIImage] = [] - for _ in 0..<10 { + for _ in 0..<3 { images.append(contentsOf: initialImages) } return images @@ -104,7 +104,8 @@ class MainViewController: UIViewController { let sectionItem = GeneralCollectionViewSectionItem() sectionItem.cellItems = [ makeResetActionCellItem(), - makePrependCellItemsActionCellItem() + makePrependCellItemsActionCellItem(), + makeAppendCellItemsActionCellItem() ] sectionItem.insets = .init(top: 0, left: 8, bottom: 0, right: 8) sectionItem.minimumInteritemSpacing = 8 @@ -129,10 +130,27 @@ class MainViewController: UIViewController { let cellItems = self.initialImages.map { image in return self.makeImageCellItem(image: image) } + self.mainCollectionView.scrollToItem(at: .init(row: 0, section: 0), at: .top, animated: false) self.mainCollectionViewManager.prepend(cellItems, to: sectionItem) } } + func makeAppendCellItemsActionCellItem() -> CollectionViewCellItem { + return makeActionCellItem(title: "Append cell items") { [weak self] in + guard let `self` = self else { + return + } + guard let sectionItem = self.mainCollectionViewManager.sectionItems.first else { + return + } + let cellItems = self.initialImages.map { image in + return self.makeImageCellItem(image: image) + } + self.mainCollectionView.scrollToItem(at: .init(row: sectionItem.cellItems.count - 1, section: 0), at: .bottom, animated: false) + self.mainCollectionViewManager.append(cellItems, to: sectionItem) + } + } + // MARK: Common func makeActionCellItem(title: String, action: @escaping (() -> Void)) -> CollectionViewCellItem { diff --git a/Sources/CollectionViewManager.swift b/Sources/CollectionViewManager.swift index c24f70d..2a7b1a6 100644 --- a/Sources/CollectionViewManager.swift +++ b/Sources/CollectionViewManager.swift @@ -287,7 +287,10 @@ open class CollectionViewManager: NSObject { /// - sectionItem: Section item within which cell items should be inserted /// - completion: A closure that either specifies any additional actions which should be performed after insertion. open func append(_ cellItems: [CellItem], to sectionItem: SectionItem, completion: Completion? = nil) { - insert(cellItems, to: sectionItem, at: Array(sectionItem.cellItems.count.. Date: Fri, 10 Aug 2018 16:55:12 +0600 Subject: [PATCH 18/54] Implement insert in the middle action --- .../MainViewController.swift | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index a7f0c2c..25a61cc 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -14,7 +14,7 @@ class MainViewController: UIViewController { } var images: [UIImage] { var images: [UIImage] = [] - for _ in 0..<3 { + for _ in 0..<1 { images.append(contentsOf: initialImages) } return images @@ -105,7 +105,8 @@ class MainViewController: UIViewController { sectionItem.cellItems = [ makeResetActionCellItem(), makePrependCellItemsActionCellItem(), - makeAppendCellItemsActionCellItem() + makeAppendCellItemsActionCellItem(), + makeInsertInTheMiddleCellItemsActionCellItem() ] sectionItem.insets = .init(top: 0, left: 8, bottom: 0, right: 8) sectionItem.minimumInteritemSpacing = 8 @@ -151,6 +152,24 @@ class MainViewController: UIViewController { } } + func makeInsertInTheMiddleCellItemsActionCellItem() -> CollectionViewCellItem { + return makeActionCellItem(title: "Insert cell items in the middle") { [weak self] in + guard let `self` = self else { + return + } + guard let sectionItem = self.mainCollectionViewManager.sectionItems.first else { + return + } + let cellItems = self.initialImages.map { image in + return self.makeImageCellItem(image: image) + } + let initialIndex = sectionItem.cellItems.count / 2 - 1 + let indexPath = IndexPath (row: initialIndex, section: 0) + self.mainCollectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: false) + self.mainCollectionViewManager.insert(cellItems, to: sectionItem, at: Array(initialIndex.. Void)) -> CollectionViewCellItem { From 7a85e07ca18a703008cca78db05ad0b4784703f2 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 17 Aug 2018 15:24:45 +0600 Subject: [PATCH 19/54] Implement insert sections methods --- .../MainViewController.swift | 98 ++++++++++++++++--- Sources/CollectionViewManager.swift | 2 +- 2 files changed, 86 insertions(+), 14 deletions(-) diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index 25a61cc..b9a2562 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -60,7 +60,7 @@ class MainViewController: UIViewController { if #available(iOS 11.0, *) { bottomInset = view.safeAreaInsets.bottom } - let actionsCollectionHeight: CGFloat = 100 + let actionsCollectionHeight: CGFloat = 70 mainCollectionView.frame = .init(x: 0, y: 0, width: view.bounds.width, @@ -85,7 +85,7 @@ class MainViewController: UIViewController { sectionItem.cellItems = images.map { image in return makeImageCellItem(image: image) } - sectionItem.insets = .init(top: 0, left: 12, bottom: 0, right: 12) + sectionItem.insets = .init(top: 0, left: 12, bottom: 12, right: 12) sectionItem.minimumLineSpacing = 8 return sectionItem } @@ -106,7 +106,10 @@ class MainViewController: UIViewController { makeResetActionCellItem(), makePrependCellItemsActionCellItem(), makeAppendCellItemsActionCellItem(), - makeInsertInTheMiddleCellItemsActionCellItem() + makeInsertCellItemsInTheMiddleActionCellItem(), + makePrependSectionItemActionCellItem(), + makeAppendSectionItemActionCellItem(), + makeInsertSectionItemInTheMiddleActionCellItem() ] sectionItem.insets = .init(top: 0, left: 8, bottom: 0, right: 8) sectionItem.minimumInteritemSpacing = 8 @@ -121,7 +124,7 @@ class MainViewController: UIViewController { } func makePrependCellItemsActionCellItem() -> CollectionViewCellItem { - return makeActionCellItem(title: "Prepend cell items") { [weak self] in + return makeActionCellItem(title: "Prepend cells") { [weak self] in guard let `self` = self else { return } @@ -131,13 +134,15 @@ class MainViewController: UIViewController { let cellItems = self.initialImages.map { image in return self.makeImageCellItem(image: image) } - self.mainCollectionView.scrollToItem(at: .init(row: 0, section: 0), at: .top, animated: false) - self.mainCollectionViewManager.prepend(cellItems, to: sectionItem) + self.mainCollectionView.scrollToItem(at: .init(row: 0, section: 0), at: .top, animated: true) + self.mainCollectionViewManager.prepend(cellItems, to: sectionItem) { [weak self] _ in + self?.mainCollectionView.scrollToItem(at: .init(row: 0, section: 0), at: .top, animated: true) + } } } func makeAppendCellItemsActionCellItem() -> CollectionViewCellItem { - return makeActionCellItem(title: "Append cell items") { [weak self] in + return makeActionCellItem(title: "Append cells") { [weak self] in guard let `self` = self else { return } @@ -147,13 +152,16 @@ class MainViewController: UIViewController { let cellItems = self.initialImages.map { image in return self.makeImageCellItem(image: image) } - self.mainCollectionView.scrollToItem(at: .init(row: sectionItem.cellItems.count - 1, section: 0), at: .bottom, animated: false) - self.mainCollectionViewManager.append(cellItems, to: sectionItem) + self.mainCollectionView.scrollToItem(at: .init(row: sectionItem.cellItems.count - 1, section: 0), at: .bottom, animated: true) + self.mainCollectionViewManager.append(cellItems, to: sectionItem) { [weak self] _ in + let indexPath = IndexPath(row: sectionItem.cellItems.count - 1, section: 0) + self?.mainCollectionView.scrollToItem(at: indexPath, at: .top, animated: true) + } } } - func makeInsertInTheMiddleCellItemsActionCellItem() -> CollectionViewCellItem { - return makeActionCellItem(title: "Insert cell items in the middle") { [weak self] in + func makeInsertCellItemsInTheMiddleActionCellItem() -> CollectionViewCellItem { + return makeActionCellItem(title: "Insert cells in the middle") { [weak self] in guard let `self` = self else { return } @@ -164,12 +172,76 @@ class MainViewController: UIViewController { return self.makeImageCellItem(image: image) } let initialIndex = sectionItem.cellItems.count / 2 - 1 - let indexPath = IndexPath (row: initialIndex, section: 0) - self.mainCollectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: false) + let indexPath = IndexPath(row: initialIndex, section: 0) + self.mainCollectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: true) self.mainCollectionViewManager.insert(cellItems, to: sectionItem, at: Array(initialIndex.. CollectionViewCellItem { + return makeActionCellItem(title: "Append section") { [weak self] in + guard let `self` = self else { + return + } + + let sectionItems = self.mainCollectionViewManager.sectionItems + guard let sectionItem = sectionItems.last else { + return + } + + let indexPath = IndexPath(row: sectionItem.cellItems.count - 1, section: sectionItems.count - 1) + self.mainCollectionView.scrollToItem(at: indexPath, at: .bottom, animated: true) + + var additionalSectionItems: [CollectionViewSectionItem] = [] + for _ in 0..<1 { + additionalSectionItems.append(self.makeImagesSectionItem(images: self.initialImages)) + } + self.mainCollectionViewManager.append(additionalSectionItems) { [weak self] _ in + let indexPath = IndexPath(row: 0, section: sectionItems.count + additionalSectionItems.count - 1) + self?.mainCollectionView.scrollToItem(at: indexPath, at: .top, animated: true) + } + } + } + + func makePrependSectionItemActionCellItem() -> CollectionViewCellItem { + return makeActionCellItem(title: "Prepend section") { [weak self] in + guard let `self` = self else { + return + } + + let indexPath = IndexPath(row: 0, section: 0) + self.mainCollectionView.scrollToItem(at: indexPath, at: .top, animated: true) + + var additionalSectionItems: [CollectionViewSectionItem] = [] + for _ in 0..<1 { + additionalSectionItems.append(self.makeImagesSectionItem(images: self.initialImages)) + } + self.mainCollectionViewManager.prepend(additionalSectionItems) { [weak self] _ in + let indexPath = IndexPath(row: 0, section: 0) + self?.mainCollectionView.scrollToItem(at: indexPath, at: .top, animated: true) + } + } + } + + func makeInsertSectionItemInTheMiddleActionCellItem() -> CollectionViewCellItem { + return makeActionCellItem(title: "Insert section in the middle") { [weak self] in + guard let `self` = self else { + return + } + + let section: Int = self.mainCollectionViewManager.sectionItems.count / 2 + let indexPath = IndexPath(row: 0, section: section) + self.mainCollectionView.scrollToItem(at: indexPath, at: .bottom, animated: true) + + var additionalSectionItems: [CollectionViewSectionItem] = [] + for _ in 0..<1 { + additionalSectionItems.append(self.makeImagesSectionItem(images: self.initialImages)) + } + let indexes = Array(section..
Void)) -> CollectionViewCellItem { diff --git a/Sources/CollectionViewManager.swift b/Sources/CollectionViewManager.swift index 2a7b1a6..991a6ac 100644 --- a/Sources/CollectionViewManager.swift +++ b/Sources/CollectionViewManager.swift @@ -419,7 +419,7 @@ open class CollectionViewManager: NSObject { /// If a section already exists at the specified index location, it is moved down one index location. /// - completion: A closure that either specifies any additional actions which should be performed after insertion. open func append(_ sectionItems: [CollectionViewSectionItem], completion: Completion? = nil) { - insert(sectionItems, at: Array(self.sectionItems.count.. Date: Fri, 17 Aug 2018 16:01:39 +0600 Subject: [PATCH 20/54] Add extension to get random int --- .../project.pbxproj | 4 ++++ .../Int+Random.swift | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 Example/CollectionViewToolsExample/Int+Random.swift diff --git a/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj b/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj index 05e89f6..48958e7 100644 --- a/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj +++ b/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 0ACA6FC91F626AF900782C1E /* ImageCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACA6FC81F626AF900782C1E /* ImageCellItem.swift */; }; 0ACA6FCC1F626D2E00782C1E /* ImageCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACA6FCB1F626D2E00782C1E /* ImageCollectionViewCell.swift */; }; 0ACA6FCE1F6275D200782C1E /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACA6FCD1F6275D200782C1E /* DetailViewController.swift */; }; + 629A6181628F24FC4839B1A7 /* Int+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A68EBC5E5A768097300D7 /* Int+Random.swift */; }; 629A6E35B86008DC359DB40B /* TextCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A6D49B5E8DB9CAACD07CE /* TextCellItem.swift */; }; 629A6F552D5D656B36F4F52C /* TextCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A60E846B05B88D62C7880 /* TextCollectionViewCell.swift */; }; FED796BA20EF6B3B0044B82F /* CollectionViewTools.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE6E86B31FB961B20027A8FE /* CollectionViewTools.framework */; }; @@ -45,6 +46,7 @@ 0ACA6FCB1F626D2E00782C1E /* ImageCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCollectionViewCell.swift; sourceTree = ""; }; 0ACA6FCD1F6275D200782C1E /* DetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; 629A60E846B05B88D62C7880 /* TextCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextCollectionViewCell.swift; sourceTree = ""; }; + 629A68EBC5E5A768097300D7 /* Int+Random.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Int+Random.swift"; sourceTree = ""; }; 629A6D49B5E8DB9CAACD07CE /* TextCellItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextCellItem.swift; sourceTree = ""; }; FE6E86B31FB961B20027A8FE /* CollectionViewTools.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CollectionViewTools.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -89,6 +91,7 @@ 0A3763891F62556300A80613 /* Assets.xcassets */, 0A37638B1F62556400A80613 /* LaunchScreen.storyboard */, 0A37638E1F62556400A80613 /* Info.plist */, + 629A68EBC5E5A768097300D7 /* Int+Random.swift */, ); path = CollectionViewToolsExample; sourceTree = ""; @@ -200,6 +203,7 @@ 0A3763831F62556300A80613 /* AppDelegate.swift in Sources */, 629A6E35B86008DC359DB40B /* TextCellItem.swift in Sources */, 629A6F552D5D656B36F4F52C /* TextCollectionViewCell.swift in Sources */, + 629A6181628F24FC4839B1A7 /* Int+Random.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/CollectionViewToolsExample/Int+Random.swift b/Example/CollectionViewToolsExample/Int+Random.swift new file mode 100644 index 0000000..184a88f --- /dev/null +++ b/Example/CollectionViewToolsExample/Int+Random.swift @@ -0,0 +1,20 @@ +// +// Copyright (c) 2018 Rosberry. All rights reserved. +// + +import Foundation + +extension Int { + static func random(range: Range) -> Int { + var offset = 0 + + if range.lowerBound < 0 { + offset = abs(range.upperBound) + } + + let minValue = UInt32(range.lowerBound + offset) + let maxValue = UInt32(range.upperBound + offset) + + return Int(minValue + arc4random_uniform(maxValue - minValue)) - offset + } +} From d5b8e31bf8ff644ea2fbe238554be01b38537a58 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 17 Aug 2018 16:01:56 +0600 Subject: [PATCH 21/54] Add remove random cell item action --- .../MainViewController.swift | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index b9a2562..6356585 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -104,12 +104,16 @@ class MainViewController: UIViewController { let sectionItem = GeneralCollectionViewSectionItem() sectionItem.cellItems = [ makeResetActionCellItem(), + // Insert cells makePrependCellItemsActionCellItem(), makeAppendCellItemsActionCellItem(), makeInsertCellItemsInTheMiddleActionCellItem(), + // Insert sections makePrependSectionItemActionCellItem(), makeAppendSectionItemActionCellItem(), - makeInsertSectionItemInTheMiddleActionCellItem() + makeInsertSectionItemInTheMiddleActionCellItem(), + // Remove cells + makeRemoveRandomCellActionCellItem() ] sectionItem.insets = .init(top: 0, left: 8, bottom: 0, right: 8) sectionItem.minimumInteritemSpacing = 8 @@ -123,6 +127,8 @@ class MainViewController: UIViewController { } } + // MARK: Insert cells + func makePrependCellItemsActionCellItem() -> CollectionViewCellItem { return makeActionCellItem(title: "Prepend cells") { [weak self] in guard let `self` = self else { @@ -178,6 +184,8 @@ class MainViewController: UIViewController { } } + // MARK: Insert sections + func makeAppendSectionItemActionCellItem() -> CollectionViewCellItem { return makeActionCellItem(title: "Append section") { [weak self] in guard let `self` = self else { @@ -242,6 +250,43 @@ class MainViewController: UIViewController { } } + // MARK: Remove cells + + func makeRemoveRandomCellActionCellItem() -> CollectionViewCellItem { + return makeActionCellItem(title: "Remove random cell") { [weak self] in + guard let `self` = self else { + return + } + + let sectionItems = self.mainCollectionViewManager.sectionItems + let nonEmptySectionsIndexes: [Int] = sectionItems.enumerated().compactMap { tuple in + if tuple.element.cellItems.count > 0 { + return tuple.offset + } + return nil + } + + let sectionsCount = nonEmptySectionsIndexes.count + guard sectionsCount > 0 else { + return + } + + let sectionIndex = nonEmptySectionsIndexes[Int.random(range: 0.. 0 else { + return + } + + let cellIndex = Int.random(range: 0.. Void)) -> CollectionViewCellItem { From 2ab211499eab8f4fb4afc34c09ce608821fc64ff Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 17 Aug 2018 16:44:00 +0600 Subject: [PATCH 22/54] Update minimal iOS version --- CollectionViewTools.xcodeproj/project.pbxproj | 4 ++-- Example/CollectionViewToolsExample.xcodeproj/project.pbxproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CollectionViewTools.xcodeproj/project.pbxproj b/CollectionViewTools.xcodeproj/project.pbxproj index d91c921..0d30298 100644 --- a/CollectionViewTools.xcodeproj/project.pbxproj +++ b/CollectionViewTools.xcodeproj/project.pbxproj @@ -462,7 +462,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.rosberry.CollectionViewTools; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -483,7 +483,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.rosberry.CollectionViewTools; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj b/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj index 48958e7..d575390 100644 --- a/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj +++ b/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj @@ -336,7 +336,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEVELOPMENT_TEAM = GPVA8JVMU3; INFOPLIST_FILE = CollectionViewToolsExample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.rosberry.CollectionViewToolsExample; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -351,7 +351,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEVELOPMENT_TEAM = GPVA8JVMU3; INFOPLIST_FILE = CollectionViewToolsExample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.rosberry.CollectionViewToolsExample; PRODUCT_NAME = "$(TARGET_NAME)"; From 9bd84d31a9fd5239bc92411c774cbf4566fbcede Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 17 Aug 2018 16:45:28 +0600 Subject: [PATCH 23/54] Add removing concrete cell item --- .../CellItems/ImageCellItem.swift | 2 ++ .../Cells/ImageCollectionViewCell.swift | 24 +++++++++++++++++++ .../MainViewController.swift | 14 +++++++++-- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift index 1da1ada..ea6191e 100644 --- a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift @@ -14,12 +14,14 @@ final class ImageCellItem: CollectionViewCellItem { private(set) var reuseType: ReuseType = .class(Cell.self) private let image: UIImage private let selectionHandler: (UIImage) -> Void + var removeActionHandler: (() -> Void)? func configure(cell: UICollectionViewCell, at indexPath: IndexPath) { guard let cell = cell as? Cell else { return } cell.imageView.image = image + cell.removeActionHandler = removeActionHandler } init(image: UIImage, selectionHandler: @escaping (UIImage) -> Void) { diff --git a/Example/CollectionViewToolsExample/Cells/ImageCollectionViewCell.swift b/Example/CollectionViewToolsExample/Cells/ImageCollectionViewCell.swift index d39dfbe..d13971c 100644 --- a/Example/CollectionViewToolsExample/Cells/ImageCollectionViewCell.swift +++ b/Example/CollectionViewToolsExample/Cells/ImageCollectionViewCell.swift @@ -8,15 +8,29 @@ import UIKit final class ImageCollectionViewCell: UICollectionViewCell { + var removeActionHandler: (() -> Void)? + + // MARK: Subviews + let imageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFit return imageView }() + private lazy var removeButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("X", for: .normal) + button.titleLabel?.font = .systemFont(ofSize: 14, weight: .bold) + button.backgroundColor = .white + button.addTarget(self, action: #selector(removeButtonPressed), for: .touchUpInside) + return button + }() + override init(frame: CGRect) { super.init(frame: frame) contentView.addSubview(imageView) + contentView.addSubview(removeButton) } required init?(coder aDecoder: NSCoder) { @@ -25,6 +39,16 @@ final class ImageCollectionViewCell: UICollectionViewCell { override func layoutSubviews() { super.layoutSubviews() + imageView.frame = contentView.bounds + + let side = contentView.bounds.width / 8 + removeButton.frame = .init(x: contentView.bounds.width - side, y: 0, width: side, height: side) + } + + // MARK: Actions + + @objc private func removeButtonPressed() { + removeActionHandler?() } } diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index 6356585..9bfd218 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -78,6 +78,12 @@ class MainViewController: UIViewController { mainCollectionView.contentOffset = .zero } + private func remove(_ cellItem: CollectionViewCellItem?) { + if let cellItem = cellItem { + mainCollectionViewManager.remove([cellItem]) + } + } + // MARK: - Factory methods func makeImagesSectionItem(images: [UIImage]) -> CollectionViewSectionItem { @@ -91,14 +97,18 @@ class MainViewController: UIViewController { } private func makeImageCellItem(image: UIImage) -> ImageCellItem { - return ImageCellItem(image: image) { [weak self] image in + let cellItem = ImageCellItem(image: image) { [weak self] image in let detailViewController = DetailViewController() detailViewController.image = image self?.navigationController?.pushViewController(detailViewController, animated: true) } + cellItem.removeActionHandler = { [weak self, weak cellItem] in + self?.remove(cellItem) + } + return cellItem } - // MARK: Actions + // MARK: Actions cell items func makeActionsSectionItem() -> CollectionViewSectionItem { let sectionItem = GeneralCollectionViewSectionItem() From bbb1a7add7a2b5f8e4f1222fa6bf3521bd09ab39 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 24 Aug 2018 11:55:57 +0600 Subject: [PATCH 24/54] Add delete random section action --- .../MainViewController.swift | 61 +++++++++++++++---- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index 9bfd218..d1936c4 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -123,7 +123,9 @@ class MainViewController: UIViewController { makeAppendSectionItemActionCellItem(), makeInsertSectionItemInTheMiddleActionCellItem(), // Remove cells - makeRemoveRandomCellActionCellItem() + makeRemoveRandomCellActionCellItem(), + // Remove sections + makeRemoveRandomSectionActionCellItem() ] sectionItem.insets = .init(top: 0, left: 8, bottom: 0, right: 8) sectionItem.minimumInteritemSpacing = 8 @@ -203,13 +205,11 @@ class MainViewController: UIViewController { } let sectionItems = self.mainCollectionViewManager.sectionItems - guard let sectionItem = sectionItems.last else { - return + if let sectionItem = sectionItems.last { + let indexPath = IndexPath(row: sectionItem.cellItems.count - 1, section: sectionItems.count - 1) + self.mainCollectionView.scrollToItem(at: indexPath, at: .bottom, animated: true) } - let indexPath = IndexPath(row: sectionItem.cellItems.count - 1, section: sectionItems.count - 1) - self.mainCollectionView.scrollToItem(at: indexPath, at: .bottom, animated: true) - var additionalSectionItems: [CollectionViewSectionItem] = [] for _ in 0..<1 { additionalSectionItems.append(self.makeImagesSectionItem(images: self.initialImages)) @@ -227,8 +227,11 @@ class MainViewController: UIViewController { return } - let indexPath = IndexPath(row: 0, section: 0) - self.mainCollectionView.scrollToItem(at: indexPath, at: .top, animated: true) + let sectionsCount = self.mainCollectionViewManager.sectionItems.count + if sectionsCount > 0 { + let indexPath = IndexPath(row: 0, section: 0) + self.mainCollectionView.scrollToItem(at: indexPath, at: .top, animated: true) + } var additionalSectionItems: [CollectionViewSectionItem] = [] for _ in 0..<1 { @@ -247,9 +250,12 @@ class MainViewController: UIViewController { return } - let section: Int = self.mainCollectionViewManager.sectionItems.count / 2 - let indexPath = IndexPath(row: 0, section: section) - self.mainCollectionView.scrollToItem(at: indexPath, at: .bottom, animated: true) + let sectionsCount = self.mainCollectionViewManager.sectionItems.count + let section: Int = sectionsCount / 2 + if sectionsCount > 0 { + let indexPath = IndexPath(row: 0, section: section) + self.mainCollectionView.scrollToItem(at: indexPath, at: .bottom, animated: true) + } var additionalSectionItems: [CollectionViewSectionItem] = [] for _ in 0..<1 { @@ -267,7 +273,7 @@ class MainViewController: UIViewController { guard let `self` = self else { return } - + let sectionItems = self.mainCollectionViewManager.sectionItems let nonEmptySectionsIndexes: [Int] = sectionItems.enumerated().compactMap { tuple in if tuple.element.cellItems.count > 0 { @@ -297,6 +303,37 @@ class MainViewController: UIViewController { } } + // MARK: Remove sections + + func makeRemoveRandomSectionActionCellItem() -> CollectionViewCellItem { + return makeActionCellItem(title: "Remove random section") { [weak self] in + guard let `self` = self else { + return + } + + let sectionItems = self.mainCollectionViewManager.sectionItems + let nonEmptySectionsIndexes: [Int] = sectionItems.enumerated().compactMap { tuple in + if tuple.element.cellItems.count > 0 { + return tuple.offset + } + return nil + } + + let sectionsCount = nonEmptySectionsIndexes.count + guard sectionsCount > 0 else { + return + } + + let sectionIndex = nonEmptySectionsIndexes[Int.random(range: 0.. Void)) -> CollectionViewCellItem { From 766c81b8cd38b09abb1c0c56198fac8fdc7b7a5d Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 24 Aug 2018 15:23:30 +0600 Subject: [PATCH 25/54] Add extension for collection --- .../project.pbxproj | 28 +++++++++++++----- .../Extensions/Collection+Shuffle.swift | 29 +++++++++++++++++++ .../{ => Extensions}/Int+Random.swift | 0 3 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 Example/CollectionViewToolsExample/Extensions/Collection+Shuffle.swift rename Example/CollectionViewToolsExample/{ => Extensions}/Int+Random.swift (100%) diff --git a/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj b/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj index d575390..f90f803 100644 --- a/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj +++ b/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj @@ -8,14 +8,15 @@ /* Begin PBXBuildFile section */ 0A3763831F62556300A80613 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763821F62556300A80613 /* AppDelegate.swift */; }; - 0A3763851F62556300A80613 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763841F62556300A80613 /* MainViewController.swift */; }; 0A37638A1F62556300A80613 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0A3763891F62556300A80613 /* Assets.xcassets */; }; 0A37638D1F62556400A80613 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0A37638B1F62556400A80613 /* LaunchScreen.storyboard */; }; 0ACA6FC91F626AF900782C1E /* ImageCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACA6FC81F626AF900782C1E /* ImageCellItem.swift */; }; 0ACA6FCC1F626D2E00782C1E /* ImageCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACA6FCB1F626D2E00782C1E /* ImageCollectionViewCell.swift */; }; 0ACA6FCE1F6275D200782C1E /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACA6FCD1F6275D200782C1E /* DetailViewController.swift */; }; - 629A6181628F24FC4839B1A7 /* Int+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A68EBC5E5A768097300D7 /* Int+Random.swift */; }; + 629A66269E82069645F0C493 /* Int+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A6A87FA655E721B318C15 /* Int+Random.swift */; }; + 629A6B0205FC8F49878C89C1 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A6D6C61B4795949B30938 /* MainViewController.swift */; }; 629A6E35B86008DC359DB40B /* TextCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A6D49B5E8DB9CAACD07CE /* TextCellItem.swift */; }; + 629A6F192B106310AE2F4456 /* Collection+Shuffle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A6A35641443892980C187 /* Collection+Shuffle.swift */; }; 629A6F552D5D656B36F4F52C /* TextCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A60E846B05B88D62C7880 /* TextCollectionViewCell.swift */; }; FED796BA20EF6B3B0044B82F /* CollectionViewTools.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE6E86B31FB961B20027A8FE /* CollectionViewTools.framework */; }; FED796BB20EF6B3B0044B82F /* CollectionViewTools.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FE6E86B31FB961B20027A8FE /* CollectionViewTools.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -38,7 +39,6 @@ /* Begin PBXFileReference section */ 0A37637F1F62556300A80613 /* CollectionViewToolsExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CollectionViewToolsExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0A3763821F62556300A80613 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 0A3763841F62556300A80613 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; 0A3763891F62556300A80613 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 0A37638C1F62556400A80613 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 0A37638E1F62556400A80613 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -46,8 +46,10 @@ 0ACA6FCB1F626D2E00782C1E /* ImageCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCollectionViewCell.swift; sourceTree = ""; }; 0ACA6FCD1F6275D200782C1E /* DetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; 629A60E846B05B88D62C7880 /* TextCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextCollectionViewCell.swift; sourceTree = ""; }; - 629A68EBC5E5A768097300D7 /* Int+Random.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Int+Random.swift"; sourceTree = ""; }; + 629A6A35641443892980C187 /* Collection+Shuffle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Collection+Shuffle.swift"; sourceTree = ""; }; + 629A6A87FA655E721B318C15 /* Int+Random.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Int+Random.swift"; sourceTree = ""; }; 629A6D49B5E8DB9CAACD07CE /* TextCellItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextCellItem.swift; sourceTree = ""; }; + 629A6D6C61B4795949B30938 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; FE6E86B31FB961B20027A8FE /* CollectionViewTools.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CollectionViewTools.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -84,14 +86,14 @@ isa = PBXGroup; children = ( 0A3763821F62556300A80613 /* AppDelegate.swift */, - 0A3763841F62556300A80613 /* MainViewController.swift */, 0ACA6FCD1F6275D200782C1E /* DetailViewController.swift */, 0ACA6FC71F626AED00782C1E /* CellItems */, 0ACA6FCA1F626D1700782C1E /* Cells */, 0A3763891F62556300A80613 /* Assets.xcassets */, 0A37638B1F62556400A80613 /* LaunchScreen.storyboard */, 0A37638E1F62556400A80613 /* Info.plist */, - 629A68EBC5E5A768097300D7 /* Int+Random.swift */, + 629A69A736A251CE646CE017 /* Extensions */, + 629A6D6C61B4795949B30938 /* MainViewController.swift */, ); path = CollectionViewToolsExample; sourceTree = ""; @@ -122,6 +124,15 @@ path = Cells; sourceTree = ""; }; + 629A69A736A251CE646CE017 /* Extensions */ = { + isa = PBXGroup; + children = ( + 629A6A35641443892980C187 /* Collection+Shuffle.swift */, + 629A6A87FA655E721B318C15 /* Int+Random.swift */, + ); + path = Extensions; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -196,14 +207,15 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0A3763851F62556300A80613 /* MainViewController.swift in Sources */, 0ACA6FC91F626AF900782C1E /* ImageCellItem.swift in Sources */, 0ACA6FCE1F6275D200782C1E /* DetailViewController.swift in Sources */, 0ACA6FCC1F626D2E00782C1E /* ImageCollectionViewCell.swift in Sources */, 0A3763831F62556300A80613 /* AppDelegate.swift in Sources */, 629A6E35B86008DC359DB40B /* TextCellItem.swift in Sources */, 629A6F552D5D656B36F4F52C /* TextCollectionViewCell.swift in Sources */, - 629A6181628F24FC4839B1A7 /* Int+Random.swift in Sources */, + 629A6F192B106310AE2F4456 /* Collection+Shuffle.swift in Sources */, + 629A66269E82069645F0C493 /* Int+Random.swift in Sources */, + 629A6B0205FC8F49878C89C1 /* MainViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/CollectionViewToolsExample/Extensions/Collection+Shuffle.swift b/Example/CollectionViewToolsExample/Extensions/Collection+Shuffle.swift new file mode 100644 index 0000000..4084bd3 --- /dev/null +++ b/Example/CollectionViewToolsExample/Extensions/Collection+Shuffle.swift @@ -0,0 +1,29 @@ +// +// Copyright (c) 2018 Rosberry. All rights reserved. +// + +import Foundation + +extension MutableCollection { + /// Shuffles the contents of this collection. + mutating func shuffle() { + let c = count + guard c > 1 else { return } + + for (firstUnshuffled, unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) { + // Change `Int` in the next line to `IndexDistance` in < Swift 4.1 + let d: Int = numericCast(arc4random_uniform(numericCast(unshuffledCount))) + let i = index(firstUnshuffled, offsetBy: d) + swapAt(firstUnshuffled, i) + } + } +} + +extension Sequence { + /// Returns an array with the contents of this sequence, shuffled. + func shuffled() -> [Element] { + var result = Array(self) + result.shuffle() + return result + } +} diff --git a/Example/CollectionViewToolsExample/Int+Random.swift b/Example/CollectionViewToolsExample/Extensions/Int+Random.swift similarity index 100% rename from Example/CollectionViewToolsExample/Int+Random.swift rename to Example/CollectionViewToolsExample/Extensions/Int+Random.swift From 62045ccd7e2d8597536b28c98123335d2e53180a Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 24 Aug 2018 15:23:51 +0600 Subject: [PATCH 26/54] Implement replace cells --- .../MainViewController.swift | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index d1936c4..448db50 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -15,11 +15,15 @@ class MainViewController: UIViewController { var images: [UIImage] { var images: [UIImage] = [] for _ in 0..<1 { - images.append(contentsOf: initialImages) + images.append(contentsOf: shuffledImages) } return images } + var shuffledImages: [UIImage] { + return initialImages.shuffled() + } + lazy var mainCollectionViewManager: CollectionViewManager = .init(collectionView: mainCollectionView) lazy var actionsCollectionViewManager: CollectionViewManager = .init(collectionView: actionsCollectionView) @@ -125,7 +129,9 @@ class MainViewController: UIViewController { // Remove cells makeRemoveRandomCellActionCellItem(), // Remove sections - makeRemoveRandomSectionActionCellItem() + makeRemoveRandomSectionActionCellItem(), + // Replace cells + makeReplaceCellItemsActionCellItem() ] sectionItem.insets = .init(top: 0, left: 8, bottom: 0, right: 8) sectionItem.minimumInteritemSpacing = 8 @@ -149,7 +155,7 @@ class MainViewController: UIViewController { guard let sectionItem = self.mainCollectionViewManager.sectionItems.first else { return } - let cellItems = self.initialImages.map { image in + let cellItems = self.shuffledImages.map { image in return self.makeImageCellItem(image: image) } self.mainCollectionView.scrollToItem(at: .init(row: 0, section: 0), at: .top, animated: true) @@ -167,7 +173,7 @@ class MainViewController: UIViewController { guard let sectionItem = self.mainCollectionViewManager.sectionItems.first else { return } - let cellItems = self.initialImages.map { image in + let cellItems = self.shuffledImages.map { image in return self.makeImageCellItem(image: image) } self.mainCollectionView.scrollToItem(at: .init(row: sectionItem.cellItems.count - 1, section: 0), at: .bottom, animated: true) @@ -186,7 +192,7 @@ class MainViewController: UIViewController { guard let sectionItem = self.mainCollectionViewManager.sectionItems.first else { return } - let cellItems = self.initialImages.map { image in + let cellItems = self.shuffledImages.map { image in return self.makeImageCellItem(image: image) } let initialIndex = sectionItem.cellItems.count / 2 - 1 @@ -212,7 +218,7 @@ class MainViewController: UIViewController { var additionalSectionItems: [CollectionViewSectionItem] = [] for _ in 0..<1 { - additionalSectionItems.append(self.makeImagesSectionItem(images: self.initialImages)) + additionalSectionItems.append(self.makeImagesSectionItem(images: self.shuffledImages)) } self.mainCollectionViewManager.append(additionalSectionItems) { [weak self] _ in let indexPath = IndexPath(row: 0, section: sectionItems.count + additionalSectionItems.count - 1) @@ -235,7 +241,7 @@ class MainViewController: UIViewController { var additionalSectionItems: [CollectionViewSectionItem] = [] for _ in 0..<1 { - additionalSectionItems.append(self.makeImagesSectionItem(images: self.initialImages)) + additionalSectionItems.append(self.makeImagesSectionItem(images: self.shuffledImages)) } self.mainCollectionViewManager.prepend(additionalSectionItems) { [weak self] _ in let indexPath = IndexPath(row: 0, section: 0) @@ -259,7 +265,7 @@ class MainViewController: UIViewController { var additionalSectionItems: [CollectionViewSectionItem] = [] for _ in 0..<1 { - additionalSectionItems.append(self.makeImagesSectionItem(images: self.initialImages)) + additionalSectionItems.append(self.makeImagesSectionItem(images: self.shuffledImages)) } let indexes = Array(section..
CollectionViewCellItem { + return makeActionCellItem(title: "Replace cells") { [weak self] in + guard let `self` = self else { + return + } + guard let sectionItem = self.mainCollectionViewManager.sectionItems.first else { + return + } + var images = self.shuffledImages + images.append(contentsOf: self.shuffledImages) + let cellItems = images.map { image in + return self.makeImageCellItem(image: image) + } + self.mainCollectionView.scrollToItem(at: .init(row: 0, section: 0), at: .top, animated: true) + + let replaceIndexes = Array(0.. Void)) -> CollectionViewCellItem { From 500bddfc09fa137cfdb3fe1b20c60b537df0274b Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 24 Aug 2018 15:44:59 +0600 Subject: [PATCH 27/54] Implement replace sections action --- .../MainViewController.swift | 22 ++++++++++++++++++- Sources/CollectionViewManager.swift | 8 ++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index 448db50..a5511d9 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -131,7 +131,9 @@ class MainViewController: UIViewController { // Remove sections makeRemoveRandomSectionActionCellItem(), // Replace cells - makeReplaceCellItemsActionCellItem() + makeReplaceCellItemsActionCellItem(), + // Replace sections + makeReplaceSectionItemsActionCellItem() ] sectionItem.insets = .init(top: 0, left: 8, bottom: 0, right: 8) sectionItem.minimumInteritemSpacing = 8 @@ -364,6 +366,24 @@ class MainViewController: UIViewController { } } + // MARK: Replace sections + + func makeReplaceSectionItemsActionCellItem() -> CollectionViewCellItem { + return makeActionCellItem(title: "Replace sections") { [weak self] in + guard let `self` = self else { + return + } + + var sectionItems: [CollectionViewSectionItem] = [] + for _ in 0..<2 { + sectionItems.append(self.makeImagesSectionItem(images: self.shuffledImages)) + } + + let replaceIndexes = Array(0.. Void)) -> CollectionViewCellItem { diff --git a/Sources/CollectionViewManager.swift b/Sources/CollectionViewManager.swift index 991a6ac..1602ea6 100644 --- a/Sources/CollectionViewManager.swift +++ b/Sources/CollectionViewManager.swift @@ -444,10 +444,11 @@ open class CollectionViewManager: NSObject { return } perform(updates: { collectionView in + sectionItems.forEach { sectionItem in + register(sectionItem) + } + if indexes.count == sectionItems.count { - sectionItems.forEach { sectionItem in - register(sectionItem) - } zip(sectionItems, indexes).forEach { sectionItem, index in _sectionItems[index] = sectionItem } @@ -458,6 +459,7 @@ open class CollectionViewManager: NSObject { indexes.sorted().reversed().forEach { index in _sectionItems.remove(at: index) } + _sectionItems = sectionItems collectionView?.deleteSections(.init(indexes)) collectionView?.insertSections(.init(firstIndex.. Date: Wed, 29 Aug 2018 13:56:29 +0600 Subject: [PATCH 28/54] Add collection view property to cell items --- .../CellItems/ImageCellItem.swift | 1 - .../MainViewController.swift | 2 +- Sources/CollectionViewManager.swift | 1 + .../CellItem/CollectionViewCellItem.swift | 49 +++++++++++++++---- ...ionViewCellItemDataSourcePrefetching.swift | 4 +- 5 files changed, 43 insertions(+), 14 deletions(-) diff --git a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift index ea6191e..7ba1579 100644 --- a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift @@ -5,7 +5,6 @@ // import CollectionViewTools - import Foundation final class ImageCellItem: CollectionViewCellItem { diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index a5511d9..d60dfc3 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -387,7 +387,7 @@ class MainViewController: UIViewController { // MARK: Common func makeActionCellItem(title: String, action: @escaping (() -> Void)) -> CollectionViewCellItem { - var cellItem = TextCellItem(text: title) + let cellItem = TextCellItem(text: title) cellItem.itemDidSelectHandler = { _, _ in action() } diff --git a/Sources/CollectionViewManager.swift b/Sources/CollectionViewManager.swift index 1602ea6..dc27f40 100644 --- a/Sources/CollectionViewManager.swift +++ b/Sources/CollectionViewManager.swift @@ -227,6 +227,7 @@ open class CollectionViewManager: NSObject { /// - Parameter cellItem: The cell item which need to be registered open func register(_ cellItem: CellItem) { collectionView.register(by: cellItem.reuseType) + cellItem.collectionView = collectionView } // MARK: - Updates for cell items diff --git a/Sources/Protocols/CellItem/CollectionViewCellItem.swift b/Sources/Protocols/CellItem/CollectionViewCellItem.swift index 5ea568a..35d0387 100644 --- a/Sources/Protocols/CellItem/CollectionViewCellItem.swift +++ b/Sources/Protocols/CellItem/CollectionViewCellItem.swift @@ -5,35 +5,64 @@ // import UIKit.UICollectionView +import ObjectiveC.runtime // MARK: - CollectionViewCellItem -public protocol CollectionViewCellItem: AnyObject, +public protocol CollectionViewCellItem: CollectionViewConfigureCellItem, CollectionViewReuseCellItem, CollectionViewSizeCellItem, CollectionViewGeneralCellItem, - CollectionViewCellItemDataSource { - func configure(cell: UICollectionViewCell, at indexPath: IndexPath) + CollectionViewCellItemDataSource, + CollectionViewSiblingCellItem { + } -// MARK: - CollectionViewReuseCellItemProtocol +// MARK: - CollectionViewReuseCellItem -public protocol CollectionViewReuseCellItem { +public protocol CollectionViewReuseCellItem: AnyObject { var reuseType: ReuseType { get } } -// MARK: - CollectionViewSizeCellItemProtocol +// MARK: - CollectionViewSizeCellItem -public protocol CollectionViewSizeCellItem { +public protocol CollectionViewSizeCellItem: AnyObject { func size(for collectionView: UICollectionView, with layout: UICollectionViewLayout, at indexPath: IndexPath) -> CGSize } -// MARK: - CollectionViewGeneralCellItemProtocol +// MARK: - CollectionViewConfigureCellItem + +public protocol CollectionViewConfigureCellItem: AnyObject { + func configure(cell: UICollectionViewCell, at indexPath: IndexPath) +} + +// MARK: - CollectionViewConfigureCellItem + +public protocol CollectionViewSiblingCellItem: AnyObject { + var collectionView: UICollectionView { get set } +} + +extension CollectionViewSiblingCellItem { + public var collectionView: UICollectionView { + get { + if let collectionView = objc_getAssociatedObject(self, &AssociatedKeys.collectionView) as? UICollectionView { + return collectionView + } + fatalError("You should never get this error if you use collection view tools properly. " + + "The reason is that you create cell item and didn't set collection view") + } + set { + objc_setAssociatedObject(self, &AssociatedKeys.collectionView, newValue, .OBJC_ASSOCIATION_ASSIGN) + } + } +} + +// MARK: - CollectionViewGeneralCellItem public typealias ActionHandler = ((UICollectionView, IndexPath) -> Void) public typealias ActionResolver = ((UICollectionView, IndexPath) -> Bool) -public protocol CollectionViewGeneralCellItem { +public protocol CollectionViewGeneralCellItem: AnyObject { var itemShouldHighlightHandler: ActionResolver? { get set } var itemDidHighlightHandler: ActionHandler? { get set } @@ -85,6 +114,8 @@ private enum AssociatedKeys { static var didEndDisplayingCellHandler = "rsb_didEndDisplayingCellHandler" static var didEndDisplayingViewHandler = "rsb_didEndDisplayingViewHandler" static var canMoveHandler = "rsb_canMoveHandler" + + static var collectionView = "rsb_collectionView" } public extension CollectionViewGeneralCellItem { diff --git a/Sources/Protocols/CellItem/CollectionViewCellItemDataSourcePrefetching.swift b/Sources/Protocols/CellItem/CollectionViewCellItemDataSourcePrefetching.swift index 19b9b84..4d532d9 100644 --- a/Sources/Protocols/CellItem/CollectionViewCellItemDataSourcePrefetching.swift +++ b/Sources/Protocols/CellItem/CollectionViewCellItemDataSourcePrefetching.swift @@ -6,14 +6,12 @@ import UIKit.UICollectionView -public protocol CollectionViewCellItemDataSource { - +public protocol CollectionViewCellItemDataSource: AnyObject { func prefetchData(for collectionView: UICollectionView, at indexPath: IndexPath) func cancelPrefetchingData(for collectionView: UICollectionView, at indexPath: IndexPath) } public extension CollectionViewCellItemDataSource { - func prefetchData(for collectionView: UICollectionView, at indexPath: IndexPath) {} func cancelPrefetchingData(for collectionView: UICollectionView, at indexPath: IndexPath) {} } From 88aeb3e750eed59d355e82f8e54fe1fbe557fe2a Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 31 Aug 2018 14:03:16 +0600 Subject: [PATCH 29/54] Add index to section items and index path to cell items - Implement updating of all indexes and index paths for all kind of items operations - Add helper functions for updating indexes and index paths - Update all functions and handlers for cell items and section items by removing redundant collectionView/indexPath/layout/etc. --- .../CellItems/ImageCellItem.swift | 10 +- .../CellItems/TextCellItem.swift | 6 +- .../MainViewController.swift | 2 +- Sources/CollectionViewManager.swift | 250 +++++++----------- ...ewManager+UICollectionViewDataSource.swift | 14 +- ...ICollectionViewDataSourcePrefetching.swift | 4 +- ...ViewManager+UICollectionViewDelegate.swift | 49 ++-- ...r+UICollectionViewDelegateFlowLayout.swift | 11 +- .../GeneralCollectionViewSectionItem.swift | 15 +- .../CellItem/CollectionViewCellItem.swift | 156 ++++++----- ...ionViewCellItemDataSourcePrefetching.swift | 8 +- .../CollectionViewSectionItem.swift | 57 ++-- 12 files changed, 270 insertions(+), 312 deletions(-) diff --git a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift index 7ba1579..999ddc9 100644 --- a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift @@ -15,7 +15,7 @@ final class ImageCellItem: CollectionViewCellItem { private let selectionHandler: (UIImage) -> Void var removeActionHandler: (() -> Void)? - func configure(cell: UICollectionViewCell, at indexPath: IndexPath) { + func configure(_ cell: UICollectionViewCell) { guard let cell = cell as? Cell else { return } @@ -28,17 +28,13 @@ final class ImageCellItem: CollectionViewCellItem { self.selectionHandler = selectionHandler } - func size(for collectionView: UICollectionView, at indexPath: IndexPath) -> CGSize { - return collectionView.bounds.size - } - - func size(for collectionView: UICollectionView, with layout: UICollectionViewLayout, at indexPath: IndexPath) -> CGSize { + func size() -> CGSize { let ratio = image.size.width / image.size.height let width = collectionView.bounds.width / 2 - 16 return .init(width: width, height: width / ratio) } - func didSelect(for collectionView: UICollectionView, at indexPath: IndexPath) { + func didSelect() { selectionHandler(image) } } diff --git a/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift b/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift index 9ac9461..68fb153 100644 --- a/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift @@ -15,7 +15,7 @@ final class TextCellItem: CollectionViewCellItem { private typealias Cell = TextCollectionViewCell private(set) var reuseType: ReuseType = .class(Cell.self) - func configure(cell: UICollectionViewCell, at indexPath: IndexPath) { + func configure(_ cell: UICollectionViewCell) { guard let cell = cell as? Cell else { return } @@ -23,9 +23,9 @@ final class TextCellItem: CollectionViewCellItem { } private static let sizeCell: Cell = .init() - func size(for collectionView: UICollectionView, with layout: UICollectionViewLayout, at indexPath: IndexPath) -> CGSize { + func size() -> CGSize { let cell: Cell = type(of: self).sizeCell - configure(cell: cell, at: indexPath) + configure(cell) let cellSize = cell.sizeThatFits(.init(width: collectionView.bounds.size.width, height: .greatestFiniteMagnitude)) return .init(width: cellSize.width + 2 * 12, height: collectionView.bounds.height / 1.4) } diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index d60dfc3..629c2e4 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -388,7 +388,7 @@ class MainViewController: UIViewController { func makeActionCellItem(title: String, action: @escaping (() -> Void)) -> CollectionViewCellItem { let cellItem = TextCellItem(text: title) - cellItem.itemDidSelectHandler = { _, _ in + cellItem.itemDidSelectHandler = { action() } return cellItem diff --git a/Sources/CollectionViewManager.swift b/Sources/CollectionViewManager.swift index dc27f40..c01c7ba 100644 --- a/Sources/CollectionViewManager.swift +++ b/Sources/CollectionViewManager.swift @@ -20,22 +20,8 @@ open class CollectionViewManager: NSObject { /// deceleration of scrolled content, and scrolling animations. public weak var scrollDelegate: UIScrollViewDelegate? - /// The property that determines whether should be used data source prefetching. - /// Prefetching is available only on iOS versions greater than or equal to 10.0 - public var isPrefetchingEnabled = false { - didSet { - if isPrefetchingEnabled { - if #available(iOS 10.0, *) { - self.collectionView.prefetchDataSource = self - } - else { - print("[WARNING] Prefetching is available only on iOS versions >= 10.0") - } - } - } - } - /// Set this handler to update your data after moving cells in collection view. + /// Do not forget to update cell item's index path or section's index. /// /// - Parameters: /// - collectionView: collection view where move action finished @@ -45,21 +31,23 @@ open class CollectionViewManager: NSObject { _ sourceIndexPath: IndexPath, _ destinationIndexPath: IndexPath) -> Void)? + /// Use this property instead of `sectionItems` internally to avoid every time reload during update operations. internal var _sectionItems = [CollectionViewSectionItem]() { didSet { - _sectionItems.forEach { sectionItem in + _sectionItems.enumerated().forEach { (index, sectionItem) in + sectionItem.index = index register(sectionItem) } } } /// Array of `CollectionViewSectionItem` objects, which respond for configuration of specified section in collection view. + /// Setting this property leads collection view to reload data. If you don't need this behaviour use update methods instead. public var sectionItems: [CollectionViewSectionItem] { get { return _sectionItems } set { - //TODO: Fix every time reload _sectionItems = newValue collectionView.reloadData() } @@ -92,39 +80,15 @@ open class CollectionViewManager: NSObject { return cellItem(for: indexPath) } - /// Reloads cells, associated with passed cell items. If section item is nil this method search through all section items until found - /// all cell items which should be reload. If you have concrete section item it might be more efficient to set it. + /// Reloads cells, associated with passed cell items. /// /// - Parameters: /// - cellItems: Cell items to reload - /// - sectionItem: Section item that contains cell items to reload /// - completion: A closure that either specifies any additional actions which should be performed after reloading. - open func reloadCellItems(_ cellItems: [CellItem], - inSectionItem sectionItem: CollectionViewSectionItem?, - completion: Completion? = nil) { - var indexPaths: [IndexPath] = [] - if let sectionItem = sectionItem { - guard let section = sectionItems.index(where: { element in - element === sectionItem - }) else { - print("[ERROR] There are no sectionItem \(sectionItem) in sectionItems array") - return - } - - for cellItem in cellItems { - guard let item = sectionItem.cellItems.index(where: { element in - element === cellItem - }) else { - print("[ERROR] Unable to reload cell items that are not contained in section item") - return - } - indexPaths.append(IndexPath(item: item, section: section)) - } - } - else { - indexPaths = calculateIndexPaths(of: cellItems) + open func reloadCellItems(_ cellItems: [CellItem], completion: Completion? = nil) { + let indexPaths: [IndexPath] = cellItems.map { cellItem in + return cellItem.indexPath } - perform(updates: { collectionView in collectionView?.reloadItems(at: indexPaths) }, completion: completion) @@ -133,42 +97,14 @@ open class CollectionViewManager: NSObject { /// Scrolls through the collection view until a cell, associated with passed cell item is at a particular location on the screen. /// Invoking this method does not cause the delegate to receive a scrollViewDidScroll(_:) message, as is normal for programmatically /// invoked user interface operations. - /// If section item is nil this method tries to calculate index path by searching through all section items and cell items. /// /// - Parameters: /// - cellItem: `CollectionViewCellItem` object, that responds for configuration of cell at the specified index path. - /// - sectionItem: `CollectionViewSectionItem` object, that contains passed cell item /// - scrollPosition: A constant that identifies a relative position in the collection view (top, middle, bottom) for item when /// scrolling concludes. See UICollectionViewScrollPosition for descriptions of valid constants. /// - animated: true if you want to animate the change in position; false if it should be immediate. - open func scroll(to cellItem: CellItem, - in sectionItem: SectionItem?, - at scrollPosition: UICollectionViewScrollPosition, - animated: Bool = true) { - var section: Int? - if let index = _sectionItems.index(where: { element in - return element === sectionItem - }) { - section = index - } - else { - for (index, sectionItem) in _sectionItems.enumerated() where sectionItem.cellItems.contains(where: { element in - return cellItem === element - }) { - section = index - } - } - - guard let sectionIndex = section, - let cellIndex = _sectionItems[sectionIndex].cellItems.index(where: { element in - return element === cellItem - }) else { - print("[ERROR] Can't scroll to cell item \(cellItem) because manager isn't contains it") - return - } - - let indexPath = IndexPath(item: cellIndex, section: sectionIndex) - collectionView.scrollToItem(at: indexPath, at: scrollPosition, animated: animated) + open func scroll(to cellItem: CellItem, at scrollPosition: UICollectionViewScrollPosition, animated: Bool = true) { + collectionView.scrollToItem(at: cellItem.indexPath, at: scrollPosition, animated: animated) } // MARK: - Helpers @@ -179,9 +115,9 @@ open class CollectionViewManager: NSObject { /// - Returns: A cell item associated with cell of the collection, or nil if the cell item /// wasn't added to manager or indexPath is out of range. open func cellItem(for indexPath: IndexPath) -> CellItem? { - if let cellItems = self.sectionItem(for: indexPath)?.cellItems { - if indexPath.row < cellItems.count { - return cellItems[indexPath.row] + if let cellItems = sectionItem(for: indexPath)?.cellItems { + return cellItems.first { cellItem in + return cellItem.indexPath == indexPath } } return nil @@ -193,28 +129,22 @@ open class CollectionViewManager: NSObject { /// - Returns: A section item associated with section of the collection, or nil if the section item /// wasn't added to manager or indexPath.section is out of range. open func sectionItem(for indexPath: IndexPath) -> SectionItem? { - if indexPath.section < _sectionItems.count { - return _sectionItems[indexPath.section] - } - return nil - } - - /// Returns the index of the section item if possible. - /// - /// - Parameter sectionItem: The section item which index must be found - open func sectionIndex(for sectionItem: SectionItem) -> Int? { - return _sectionItems.index { element in - return element === sectionItem + return _sectionItems.first { sectionItem in + return sectionItem.index == indexPath.section } } // MARK: - Registration /// Use this function to force cells and reusable views registration process if you override add/replace/reload methods + /// Also in this method section item got set collection view and perform setting index paths for cell items. + /// So pay attention to the order of operations for section items updates methods. Set correct index for section item first. /// /// - Parameter sectionItem: The section item with cell items which need to be registered open func register(_ sectionItem: CollectionViewSectionItem) { - sectionItem.cellItems.forEach { cellItem in + sectionItem.collectionView = collectionView + sectionItem.cellItems.enumerated().forEach { (index, cellItem) in + cellItem.indexPath = .init(row: index, section: sectionItem.index) register(cellItem) } sectionItem.reusableViewItems.forEach { reusableViewItem in @@ -226,8 +156,35 @@ open class CollectionViewManager: NSObject { /// /// - Parameter cellItem: The cell item which need to be registered open func register(_ cellItem: CellItem) { - collectionView.register(by: cellItem.reuseType) cellItem.collectionView = collectionView + collectionView.register(by: cellItem.reuseType) + } + + // MARK: - Index paths + + /// Use this function to force update all indexes and index paths + /// for section items and cell items during custom update operations. + open func recalculateIndexPaths() { + _sectionItems.enumerated().forEach { index, sectionItem in + sectionItem.index = index + recalculateIndexPaths(in: sectionItem) + } + } + + /// Use this function to force update all index paths for all cell items in specific section item during custom update operations. + /// + /// - Parameter sectionItem: The section item with cell items needed to recalculate index paths. + open func recalculateIndexPaths(in sectionItem: CollectionViewSectionItem) { + sectionItem.cellItems.enumerated().forEach { index, cellItem in + cellItem.indexPath = .init(row: index, section: sectionItem.index) + } + } + + /// Use this function to force update indexes for all section items during custom update operations. + open func recalculateSectionsIndexes() { + _sectionItems.enumerated().forEach { index, sectionItem in + sectionItem.index = index + } } // MARK: - Updates for cell items @@ -240,17 +197,10 @@ open class CollectionViewManager: NSObject { /// - sectionItem: Section item within which cell items should be set /// - completion: A closure that either specifies any additional actions which should be performed after setting. open func replaceAllCellItems(in sectionItem: SectionItem, with cellItems: [CellItem], completion: Completion? = nil) { - guard let section = sectionIndex(for: sectionItem) else { - return - } - perform(updates: { collectionView in - cellItems.forEach { cellItem in - register(cellItem) - } sectionItem.cellItems = cellItems - - collectionView?.reloadSections([section]) + register(sectionItem) + collectionView?.reloadSections([sectionItem.index]) }, completion: completion) } @@ -263,18 +213,15 @@ open class CollectionViewManager: NSObject { /// - indexes: An array of locations, that contains indexes of positions where specified cell items should be inserted /// - completion: A closure that either specifies any additional actions which should be performed after insertion. open func insert(_ cellItems: [CellItem], to sectionItem: SectionItem, at indexes: [Int], completion: Completion? = nil) { - guard let section = sectionIndex(for: sectionItem) else { - return - } - - perform(updates: { collectionView in + perform(updates: { [weak self] collectionView in zip(cellItems, indexes).forEach { cellItem, index in register(cellItem) sectionItem.cellItems.insert(cellItem, at: index) } + self?.recalculateIndexPaths(in: sectionItem) - let indexPaths: [IndexPath] = indexes.map { index in - return .init(row: index, section: section) + let indexPaths: [IndexPath] = cellItems.map { cellItem in + return cellItem.indexPath } collectionView?.insertItems(at: indexPaths) @@ -312,12 +259,13 @@ open class CollectionViewManager: NSObject { /// - sectionItem: Section item within which cell items should be replaced /// - completion: A closure that either specifies any additional actions which should be performed after replacing. open func replace(cellItemsAt indexes: [Int], with cellItems: [CellItem], in sectionItem: SectionItem, completion: Completion? = nil) { - guard let section = sectionIndex(for: sectionItem), - indexes.count > 0 else { + guard indexes.count > 0 else { return } - perform(updates: { collectionView in + let section = sectionItem.index + + perform(updates: { [weak self] collectionView in cellItems.forEach { cellItem in register(cellItem) } @@ -325,6 +273,7 @@ open class CollectionViewManager: NSObject { if indexes.count == cellItems.count { zip(cellItems, indexes).forEach { cellItem, index in sectionItem.cellItems[index] = cellItem + cellItem.indexPath = .init(row: index, section: sectionItem.index) } let indexPaths: [IndexPath] = indexes.map { index in return .init(row: index, section: section) @@ -339,11 +288,12 @@ open class CollectionViewManager: NSObject { removeIndexPaths.append(.init(row: index, section: section)) } - let insertIndexPaths: [IndexPath] = Array(firstIndex.. 0 else { return } - perform(updates: { collectionView in - sectionItems.forEach { sectionItem in - register(sectionItem) - } - + + perform(updates: { [weak self] collectionView in if indexes.count == sectionItems.count { zip(sectionItems, indexes).forEach { sectionItem, index in _sectionItems[index] = sectionItem + sectionItem.index = index + register(sectionItem) } collectionView?.reloadSections(IndexSet(indexes)) } @@ -460,10 +411,16 @@ open class CollectionViewManager: NSObject { indexes.sorted().reversed().forEach { index in _sectionItems.remove(at: index) } - _sectionItems = sectionItems + + _sectionItems.insert(contentsOf: sectionItems, at: firstIndex) + self?.recalculateSectionsIndexes() + + sectionItems.forEach { sectionItem in + register(sectionItem) + } collectionView?.deleteSections(.init(indexes)) - collectionView?.insertSections(.init(firstIndex.. Void, completion: Completion?) { collectionView.performBatchUpdates({ [weak collectionView] in - updates(collectionView) - }, completion: completion) - } - - private func calculateIndexPaths(of cellItems: [CellItem]) -> [IndexPath] { - var indexPaths: [IndexPath] = [] - outer: for (sectionIndex, sectionItem) in sectionItems.enumerated() { - for (cellIndex, cellItem) in sectionItem.cellItems.enumerated() where cellItems.contains(where: { element in - return cellItem === element - }) { - indexPaths.append(.init(row: cellIndex, section: sectionIndex)) - if indexPaths.count == cellItems.count { - break outer - } - } - } - return indexPaths + updates(collectionView) + }, completion: completion) } } diff --git a/Sources/Extensions/CollectionViewManager+UICollectionViewDataSource.swift b/Sources/Extensions/CollectionViewManager+UICollectionViewDataSource.swift index d6504ec..d0a2bab 100644 --- a/Sources/Extensions/CollectionViewManager+UICollectionViewDataSource.swift +++ b/Sources/Extensions/CollectionViewManager+UICollectionViewDataSource.swift @@ -8,16 +8,14 @@ import UIKit.UICollectionView extension CollectionViewManager: UICollectionViewDataSource { - open func collectionView(_ collectionView: UICollectionView, - numberOfItemsInSection section: Int) -> Int { + open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return _sectionItems[section].cellItems.count } - open func collectionView(_ collectionView: UICollectionView, - cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cellItem = self.cellItem(for: indexPath)! let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellItem.reuseType.identifier, for: indexPath) - cellItem.configure(cell: cell, at: indexPath) + cellItem.configure(cell) return cell } @@ -28,14 +26,14 @@ extension CollectionViewManager: UICollectionViewDataSource { open func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { + //TODO: Add same `configure` logic as with cell items let reusableViewItem = sectionItem(for: indexPath)?.reusableViewItems.filter { $0.type.kind == kind }.first let view = reusableViewItem?.view(for: collectionView, at: indexPath) return view ?? UICollectionReusableView() } - open func collectionView(_ collectionView: UICollectionView, - canMoveItemAt indexPath: IndexPath) -> Bool { - return cellItem(for: indexPath)?.canMove(for: collectionView, at: indexPath) ?? false + open func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool { + return cellItem(for: indexPath)?.canMove() ?? false } open func collectionView(_ collectionView: UICollectionView, diff --git a/Sources/Extensions/CollectionViewManager+UICollectionViewDataSourcePrefetching.swift b/Sources/Extensions/CollectionViewManager+UICollectionViewDataSourcePrefetching.swift index 4266b2d..6165882 100644 --- a/Sources/Extensions/CollectionViewManager+UICollectionViewDataSourcePrefetching.swift +++ b/Sources/Extensions/CollectionViewManager+UICollectionViewDataSourcePrefetching.swift @@ -11,7 +11,7 @@ extension CollectionViewManager: UICollectionViewDataSourcePrefetching { open func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) { indexPaths.forEach { indexPath in if let cellItem = cellItem(for: indexPath) { - cellItem.prefetchData(for: collectionView, at: indexPath) + cellItem.prefetchData() } } } @@ -19,7 +19,7 @@ extension CollectionViewManager: UICollectionViewDataSourcePrefetching { open func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) { indexPaths.forEach { indexPath in if let cellItem = cellItem(for: indexPath) { - cellItem.cancelPrefetchingData(for: collectionView, at: indexPath) + cellItem.cancelPrefetchingData() } } } diff --git a/Sources/Extensions/CollectionViewManager+UICollectionViewDelegate.swift b/Sources/Extensions/CollectionViewManager+UICollectionViewDelegate.swift index 2a4ebec..4310c40 100644 --- a/Sources/Extensions/CollectionViewManager+UICollectionViewDelegate.swift +++ b/Sources/Extensions/CollectionViewManager+UICollectionViewDelegate.swift @@ -8,45 +8,42 @@ import UIKit.UICollectionView extension CollectionViewManager: UICollectionViewDelegate { - open func collectionView(_ collectionView: UICollectionView, - shouldHighlightItemAt indexPath: IndexPath) -> Bool { - return cellItem(for: indexPath)?.shouldHighlight(for: collectionView, at: indexPath) ?? false + open func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool { + return cellItem(for: indexPath)?.shouldHighlight() ?? false } - open func collectionView(_ collectionView: UICollectionView, - didHighlightItemAt indexPath: IndexPath) { - cellItem(for: indexPath)?.didHighlight(for: collectionView, at: indexPath) + open func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) { + cellItem(for: indexPath)?.didHighlight() } - open func collectionView(_ collectionView: UICollectionView, - didUnhighlightItemAt indexPath: IndexPath) { - cellItem(for: indexPath)?.didUnhighlight(for: collectionView, at: indexPath) + open func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) { + cellItem(for: indexPath)?.didUnhighlight() } - open func collectionView(_ collectionView: UICollectionView, - shouldSelectItemAt indexPath: IndexPath) -> Bool { - return cellItem(for: indexPath)?.shouldSelect(for: collectionView, at: indexPath) ?? true + open func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { + return cellItem(for: indexPath)?.shouldSelect() ?? true } - open func collectionView(_ collectionView: UICollectionView, - shouldDeselectItemAt indexPath: IndexPath) -> Bool { - return cellItem(for: indexPath)?.shouldDeselect(for: collectionView, at: indexPath) ?? true + open func collectionView(_ collectionView: UICollectionView, shouldDeselectItemAt indexPath: IndexPath) -> Bool { + return cellItem(for: indexPath)?.shouldDeselect() ?? true } - open func collectionView(_ collectionView: UICollectionView, - didSelectItemAt indexPath: IndexPath) { - cellItem(for: indexPath)?.didSelect(for: collectionView, at: indexPath) + open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + cellItem(for: indexPath)?.didSelect() } - open func collectionView(_ collectionView: UICollectionView, - didDeselectItemAt indexPath: IndexPath) { - cellItem(for: indexPath)?.didDeselect(for: collectionView, at: indexPath) + open func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { + cellItem(for: indexPath)?.didDeselect() + } + + open func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + cellItem(for: indexPath)?.willDisplay(cell: cell) } open func collectionView(_ collectionView: UICollectionView, - willDisplay cell: UICollectionViewCell, + didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - cellItem(for: indexPath)?.willDisplay(cell: cell, for: collectionView, at: indexPath) + cellItem(for: indexPath)?.didEndDisplaying(cell: cell) } open func collectionView(_ collectionView: UICollectionView, @@ -56,12 +53,6 @@ extension CollectionViewManager: UICollectionViewDelegate { cellItem(for: indexPath)?.willDisplay(view: view, for: elementKind, for: collectionView, at: indexPath) } - open func collectionView(_ collectionView: UICollectionView, - didEndDisplaying cell: UICollectionViewCell, - forItemAt indexPath: IndexPath) { - cellItem(for: indexPath)?.didEndDisplaying(cell: cell, for: collectionView, at: indexPath) - } - open func collectionView(_ collectionView: UICollectionView, didEndDisplayingSupplementaryView view: UICollectionReusableView, forElementOfKind elementKind: String, diff --git a/Sources/Extensions/CollectionViewManager+UICollectionViewDelegateFlowLayout.swift b/Sources/Extensions/CollectionViewManager+UICollectionViewDelegateFlowLayout.swift index acec44a..5bb34ce 100644 --- a/Sources/Extensions/CollectionViewManager+UICollectionViewDelegateFlowLayout.swift +++ b/Sources/Extensions/CollectionViewManager+UICollectionViewDelegateFlowLayout.swift @@ -14,28 +14,25 @@ extension CollectionViewManager: UICollectionViewDelegateFlowLayout { guard let cellItem = cellItem(for: indexPath) else { return .zero } - return cellItem.size(for: collectionView, with: collectionViewLayout, at: indexPath) + return cellItem.size() } open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { - let sectionItem = _sectionItems[section] - return sectionItem.inset(for: collectionView, with: collectionViewLayout) + return _sectionItems[section].insets } open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { - let sectionItem = _sectionItems[section] - return sectionItem.minimumLineSpacing(for: collectionView, with: collectionViewLayout) + return _sectionItems[section].minimumLineSpacing } open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { - let sectionItem = _sectionItems[section] - return sectionItem.minimumInteritemSpacing(for: collectionView, with: collectionViewLayout) + return _sectionItems[section].minimumInteritemSpacing } open func collectionView(_ collectionView: UICollectionView, diff --git a/Sources/Items/GeneralCollectionViewSectionItem.swift b/Sources/Items/GeneralCollectionViewSectionItem.swift index 7c97255..2c0e8b2 100644 --- a/Sources/Items/GeneralCollectionViewSectionItem.swift +++ b/Sources/Items/GeneralCollectionViewSectionItem.swift @@ -15,21 +15,8 @@ open class GeneralCollectionViewSectionItem: CollectionViewSectionItem { public var minimumInteritemSpacing: CGFloat = 0 public var insets: UIEdgeInsets = .zero - public init(cellItems: [CollectionViewManager.CellItem] = [], - reusableViewItems: [CollectionViewReusableViewItem] = []) { + public init(cellItems: [CollectionViewManager.CellItem] = [], reusableViewItems: [CollectionViewReusableViewItem] = []) { self.cellItems = cellItems self.reusableViewItems = reusableViewItems } - - public func inset(for collectionView: UICollectionView, with layout: UICollectionViewLayout) -> UIEdgeInsets { - return insets - } - - public func minimumLineSpacing(for collectionView: UICollectionView, with layout: UICollectionViewLayout) -> CGFloat { - return minimumLineSpacing - } - - public func minimumInteritemSpacing(for collectionView: UICollectionView, with layout: UICollectionViewLayout) -> CGFloat { - return minimumInteritemSpacing - } } diff --git a/Sources/Protocols/CellItem/CollectionViewCellItem.swift b/Sources/Protocols/CellItem/CollectionViewCellItem.swift index 35d0387..c6a9dfc 100644 --- a/Sources/Protocols/CellItem/CollectionViewCellItem.swift +++ b/Sources/Protocols/CellItem/CollectionViewCellItem.swift @@ -27,26 +27,27 @@ public protocol CollectionViewReuseCellItem: AnyObject { // MARK: - CollectionViewSizeCellItem public protocol CollectionViewSizeCellItem: AnyObject { - func size(for collectionView: UICollectionView, with layout: UICollectionViewLayout, at indexPath: IndexPath) -> CGSize + func size() -> CGSize } // MARK: - CollectionViewConfigureCellItem public protocol CollectionViewConfigureCellItem: AnyObject { - func configure(cell: UICollectionViewCell, at indexPath: IndexPath) + func configure(_ cell: UICollectionViewCell) } -// MARK: - CollectionViewConfigureCellItem +// MARK: - CollectionViewSiblingCellItem public protocol CollectionViewSiblingCellItem: AnyObject { var collectionView: UICollectionView { get set } + var indexPath: IndexPath { get set } } extension CollectionViewSiblingCellItem { public var collectionView: UICollectionView { get { - if let collectionView = objc_getAssociatedObject(self, &AssociatedKeys.collectionView) as? UICollectionView { - return collectionView + if let object = objc_getAssociatedObject(self, &AssociatedKeys.collectionView) as? UICollectionView { + return object } fatalError("You should never get this error if you use collection view tools properly. " + "The reason is that you create cell item and didn't set collection view") @@ -55,49 +56,65 @@ extension CollectionViewSiblingCellItem { objc_setAssociatedObject(self, &AssociatedKeys.collectionView, newValue, .OBJC_ASSOCIATION_ASSIGN) } } + + public var indexPath: IndexPath { + get { + if let object = objc_getAssociatedObject(self, &AssociatedKeys.indexPath) as? IndexPath { + return object + } + fatalError("You should never get this error if you use collection view tools properly. " + + "The reason is that you create cell item and didn't set index path") + } + set { + objc_setAssociatedObject(self, &AssociatedKeys.indexPath, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } } // MARK: - CollectionViewGeneralCellItem -public typealias ActionHandler = ((UICollectionView, IndexPath) -> Void) -public typealias ActionResolver = ((UICollectionView, IndexPath) -> Bool) +public typealias ActionHandler = () -> Void +public typealias ActionResolver = () -> Bool +public typealias CellActionHandler = (UICollectionViewCell) -> Void +public typealias ViewActionHandler = (UICollectionReusableView, String, UICollectionView, IndexPath) -> Void public protocol CollectionViewGeneralCellItem: AnyObject { - var itemShouldHighlightHandler: ActionResolver? { get set } + var itemShouldHighlightResolver: ActionResolver? { get set } var itemDidHighlightHandler: ActionHandler? { get set } var itemDidUnhighlightHandler: ActionHandler? { get set } var itemDidSelectHandler: ActionHandler? { get set } var itemDidDeselectHandler: ActionHandler? { get set } - var itemShouldSelectHandler: ActionResolver? { get set } - var itemShouldDeselectHandler: ActionResolver? { get set } + var itemShouldSelectResolver: ActionResolver? { get set } + var itemShouldDeselectResolver: ActionResolver? { get set } + + var itemWillDisplayCellHandler: CellActionHandler? { get set } + var itemDidEndDisplayingCellHandler: CellActionHandler? { get set } + var itemWillDisplayViewHandler: ViewActionHandler? { get set } + var itemDidEndDisplayingViewHandler: ViewActionHandler? { get set } - var itemWillDisplayCellHandler: ActionHandler? { get set } - var itemWillDisplayViewHandler: ActionHandler? { get set } - var itemDidEndDisplayingCellHandler: ActionHandler? { get set } - var itemDidEndDisplayingViewHandler: ActionHandler? { get set } + var itemCanMoveResolver: ActionResolver? { get set } - var itemCanMoveHandler: ActionResolver? { get set } + func shouldHighlight() -> Bool + func didHighlight() + func didUnhighlight() - func shouldHighlight(for collectionView: UICollectionView, at indexPath: IndexPath) -> Bool - func didHighlight(for collectionView: UICollectionView, at indexPath: IndexPath) - func didUnhighlight(for collectionView: UICollectionView, at indexPath: IndexPath) + func shouldSelect() -> Bool + func shouldDeselect() -> Bool + func didSelect() + func didDeselect() - func shouldSelect(for collectionView: UICollectionView, at indexPath: IndexPath) -> Bool - func shouldDeselect(for collectionView: UICollectionView, at indexPath: IndexPath) -> Bool - func didSelect(for collectionView: UICollectionView, at indexPath: IndexPath) - func didDeselect(for collectionView: UICollectionView, at indexPath: IndexPath) + func willDisplay(cell: UICollectionViewCell) + func didEndDisplaying(cell: UICollectionViewCell) - func willDisplay(cell: UICollectionViewCell, for collectionView: UICollectionView, at indexPath: IndexPath) func willDisplay(view: UICollectionReusableView, for elementKind: String, for collectionView: UICollectionView, at indexPath: IndexPath) - func didEndDisplaying(cell: UICollectionViewCell, for collectionView: UICollectionView, at indexPath: IndexPath) func didEndDisplaying(view: UICollectionReusableView, for elementKind: String, for collectionView: UICollectionView, at indexPath: IndexPath) - func canMove(for collectionView: UICollectionView, at indexPath: IndexPath) -> Bool + func canMove() -> Bool } private enum AssociatedKeys { @@ -116,13 +133,14 @@ private enum AssociatedKeys { static var canMoveHandler = "rsb_canMoveHandler" static var collectionView = "rsb_collectionView" + static var indexPath = "rsb_indexPath" } public extension CollectionViewGeneralCellItem { // MARK: - Handlers - var itemShouldHighlightHandler: ActionResolver? { + var itemShouldHighlightResolver: ActionResolver? { get { return ClosureWrapper.handler(for: self, key: &AssociatedKeys.shouldHighlightHandler) } @@ -167,7 +185,7 @@ public extension CollectionViewGeneralCellItem { } } - var itemShouldSelectHandler: ActionResolver? { + var itemShouldSelectResolver: ActionResolver? { get { return ClosureWrapper.handler(for: self, key: &AssociatedKeys.shouldSelectHandler) } @@ -176,7 +194,7 @@ public extension CollectionViewGeneralCellItem { } } - var itemShouldDeselectHandler: ActionResolver? { + var itemShouldDeselectResolver: ActionResolver? { get { return ClosureWrapper.handler(for: self, key: &AssociatedKeys.shouldDeselectHandler) } @@ -185,43 +203,43 @@ public extension CollectionViewGeneralCellItem { } } - var itemWillDisplayCellHandler: ActionHandler? { + var itemWillDisplayCellHandler: CellActionHandler? { get { - return ClosureWrapper.handler(for: self, key: &AssociatedKeys.willDisplayCellHandler) + return ClosureWrapper.handler(for: self, key: &AssociatedKeys.willDisplayCellHandler) } set { - ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.willDisplayCellHandler) + ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.willDisplayCellHandler) } } - var itemWillDisplayViewHandler: ActionHandler? { + var itemDidEndDisplayingCellHandler: CellActionHandler? { get { - return ClosureWrapper.handler(for: self, key: &AssociatedKeys.willDisplayViewHandler) + return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didEndDisplayingCellHandler) } set { - ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.willDisplayViewHandler) + ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didEndDisplayingCellHandler) } } - var itemDidEndDisplayingCellHandler: ActionHandler? { + var itemWillDisplayViewHandler: ViewActionHandler? { get { - return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didEndDisplayingCellHandler) + return ClosureWrapper.handler(for: self, key: &AssociatedKeys.willDisplayViewHandler) } set { - ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didEndDisplayingCellHandler) + ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.willDisplayViewHandler) } } - var itemDidEndDisplayingViewHandler: ActionHandler? { + var itemDidEndDisplayingViewHandler: ViewActionHandler? { get { - return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didEndDisplayingViewHandler) + return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didEndDisplayingViewHandler) } set { - ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didEndDisplayingViewHandler) + ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didEndDisplayingViewHandler) } } - var itemCanMoveHandler: ActionResolver? { + var itemCanMoveResolver: ActionResolver? { get { return ClosureWrapper.handler(for: self, key: &AssociatedKeys.canMoveHandler) } @@ -232,61 +250,61 @@ public extension CollectionViewGeneralCellItem { // MARK: - Functions - func size(for collectionView: UICollectionView, with layout: UICollectionViewLayout, at indexPath: IndexPath) -> CGSize { - return CGSize(width: 50, height: 50) + func shouldHighlight() -> Bool { + return itemShouldHighlightResolver?() ?? true + } + + func didHighlight() { + itemDidHighlightHandler?() + } + + func didUnhighlight() { + itemDidUnhighlightHandler?() } - func shouldHighlight(for collectionView: UICollectionView, at indexPath: IndexPath) -> Bool { - return itemShouldHighlightHandler?(collectionView, indexPath) ?? true + func shouldSelect() -> Bool { + return itemShouldSelectResolver?() ?? true } - func didHighlight(for collectionView: UICollectionView, at indexPath: IndexPath) { - itemDidHighlightHandler?(collectionView, indexPath) + func shouldDeselect() -> Bool { + return itemShouldDeselectResolver?() ?? true } - func didUnhighlight(for collectionView: UICollectionView, at indexPath: IndexPath) { - itemDidUnhighlightHandler?(collectionView, indexPath) + func didSelect() { + itemDidSelectHandler?() } - func shouldSelect(for collectionView: UICollectionView, at indexPath: IndexPath) -> Bool { - return itemShouldSelectHandler?(collectionView, indexPath) ?? true + func didDeselect() { + itemDidDeselectHandler?() } - func shouldDeselect(for collectionView: UICollectionView, at indexPath: IndexPath) -> Bool { - return itemShouldDeselectHandler?(collectionView, indexPath) ?? true + func willDisplay(cell: UICollectionViewCell) { + itemWillDisplayCellHandler?(cell) } - func didSelect(for collectionView: UICollectionView, at indexPath: IndexPath) { - itemDidSelectHandler?(collectionView, indexPath) + func didEndDisplaying(cell: UICollectionViewCell) { + itemDidEndDisplayingCellHandler?(cell) } - func didDeselect(for collectionView: UICollectionView, at indexPath: IndexPath) { - itemDidDeselectHandler?(collectionView, indexPath) + func canMove() -> Bool { + return itemCanMoveResolver?() ?? false } - func willDisplay(cell: UICollectionViewCell, for collectionView: UICollectionView, at indexPath: IndexPath) { - itemWillDisplayCellHandler?(collectionView, indexPath) + func size() -> CGSize { + return CGSize(width: 50, height: 50) } func willDisplay(view: UICollectionReusableView, for elementKind: String, for collectionView: UICollectionView, at indexPath: IndexPath) { - itemWillDisplayViewHandler?(collectionView, indexPath) - } - - func didEndDisplaying(cell: UICollectionViewCell, for collectionView: UICollectionView, at indexPath: IndexPath) { - itemDidEndDisplayingCellHandler?(collectionView, indexPath) + itemWillDisplayViewHandler?(view, elementKind, collectionView, indexPath) } func didEndDisplaying(view: UICollectionReusableView, for elementKind: String, for collectionView: UICollectionView, at indexPath: IndexPath) { - itemDidEndDisplayingViewHandler?(collectionView, indexPath) - } - - func canMove(for collectionView: UICollectionView, at indexPath: IndexPath) -> Bool { - return itemCanMoveHandler?(collectionView, indexPath) ?? false + itemDidEndDisplayingViewHandler?(view, elementKind, collectionView, indexPath) } } diff --git a/Sources/Protocols/CellItem/CollectionViewCellItemDataSourcePrefetching.swift b/Sources/Protocols/CellItem/CollectionViewCellItemDataSourcePrefetching.swift index 4d532d9..e7481f4 100644 --- a/Sources/Protocols/CellItem/CollectionViewCellItemDataSourcePrefetching.swift +++ b/Sources/Protocols/CellItem/CollectionViewCellItemDataSourcePrefetching.swift @@ -7,11 +7,11 @@ import UIKit.UICollectionView public protocol CollectionViewCellItemDataSource: AnyObject { - func prefetchData(for collectionView: UICollectionView, at indexPath: IndexPath) - func cancelPrefetchingData(for collectionView: UICollectionView, at indexPath: IndexPath) + func prefetchData() + func cancelPrefetchingData() } public extension CollectionViewCellItemDataSource { - func prefetchData(for collectionView: UICollectionView, at indexPath: IndexPath) {} - func cancelPrefetchingData(for collectionView: UICollectionView, at indexPath: IndexPath) {} + func prefetchData() {} + func cancelPrefetchingData() {} } diff --git a/Sources/Protocols/SectionItem/CollectionViewSectionItem.swift b/Sources/Protocols/SectionItem/CollectionViewSectionItem.swift index 53be0df..1bb0160 100644 --- a/Sources/Protocols/SectionItem/CollectionViewSectionItem.swift +++ b/Sources/Protocols/SectionItem/CollectionViewSectionItem.swift @@ -5,28 +5,55 @@ // import UIKit.UICollectionView +import ObjectiveC.runtime -public protocol CollectionViewSectionItem: AnyObject { - +public protocol CollectionViewSectionItem: CollectionViewSiblingSectionItem { var cellItems: [CollectionViewManager.CellItem] { get set } var reusableViewItems: [CollectionViewReusableViewItem] { get set } - func inset(for collectionView: UICollectionView, with layout: UICollectionViewLayout) -> UIEdgeInsets - func minimumLineSpacing(for collectionView: UICollectionView, with layout: UICollectionViewLayout) -> CGFloat - func minimumInteritemSpacing(for collectionView: UICollectionView, with layout: UICollectionViewLayout) -> CGFloat + var minimumLineSpacing: CGFloat { get set } + var minimumInteritemSpacing: CGFloat { get set } + var insets: UIEdgeInsets { get set } } -public extension CollectionViewSectionItem { - - func inset(for collectionView: UICollectionView, with layout: UICollectionViewLayout) -> UIEdgeInsets { - return .zero - } - - func minimumLineSpacing(for collectionView: UICollectionView, with layout: UICollectionViewLayout) -> CGFloat { - return 0 +// MARK: - CollectionViewSiblingSectionItem + +public protocol CollectionViewSiblingSectionItem: AnyObject { + var collectionView: UICollectionView { get set } + var index: Int { get set } +} + +extension CollectionViewSiblingSectionItem { + public var collectionView: UICollectionView { + get { + if let object = objc_getAssociatedObject(self, &AssociatedKeys.collectionView) as? UICollectionView { + return object + } + fatalError("You should never get this error if you use collection view tools properly. " + + "The reason is that you create cell item and didn't set collection view") + } + set { + objc_setAssociatedObject(self, &AssociatedKeys.collectionView, newValue, .OBJC_ASSOCIATION_ASSIGN) + } } - func minimumInteritemSpacing(for collectionView: UICollectionView, with layout: UICollectionViewLayout) -> CGFloat { - return 0 + public var index: Int { + get { + if let object = objc_getAssociatedObject(self, &AssociatedKeys.index) as? Int { + return object + } + fatalError("You should never get this error if you use collection view tools properly. " + + "The reason is that you create section item and didn't set index") + } + set { + objc_setAssociatedObject(self, &AssociatedKeys.index, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } } } + +// MARK: AssociatedKeys + +private enum AssociatedKeys { + static var index = "rsb_index" + static var collectionView = "rsb_collectionView" +} From 6c809e073bf1e11b3bae9567a52cf27dbe25226f Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 31 Aug 2018 14:25:04 +0600 Subject: [PATCH 30/54] Add example section item --- .../project.pbxproj | 12 ++++++++++++ .../SectionItems/ExampleSectionItem.swift | 8 ++++++++ 2 files changed, 20 insertions(+) create mode 100644 Example/CollectionViewToolsExample/SectionItems/ExampleSectionItem.swift diff --git a/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj b/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj index f90f803..2be914d 100644 --- a/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj +++ b/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 0ACA6FC91F626AF900782C1E /* ImageCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACA6FC81F626AF900782C1E /* ImageCellItem.swift */; }; 0ACA6FCC1F626D2E00782C1E /* ImageCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACA6FCB1F626D2E00782C1E /* ImageCollectionViewCell.swift */; }; 0ACA6FCE1F6275D200782C1E /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACA6FCD1F6275D200782C1E /* DetailViewController.swift */; }; + 629A63FBA018222A355686F6 /* ExampleSectionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A688DD33578289A57B452 /* ExampleSectionItem.swift */; }; 629A66269E82069645F0C493 /* Int+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A6A87FA655E721B318C15 /* Int+Random.swift */; }; 629A6B0205FC8F49878C89C1 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A6D6C61B4795949B30938 /* MainViewController.swift */; }; 629A6E35B86008DC359DB40B /* TextCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A6D49B5E8DB9CAACD07CE /* TextCellItem.swift */; }; @@ -46,6 +47,7 @@ 0ACA6FCB1F626D2E00782C1E /* ImageCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCollectionViewCell.swift; sourceTree = ""; }; 0ACA6FCD1F6275D200782C1E /* DetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; 629A60E846B05B88D62C7880 /* TextCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextCollectionViewCell.swift; sourceTree = ""; }; + 629A688DD33578289A57B452 /* ExampleSectionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExampleSectionItem.swift; sourceTree = ""; }; 629A6A35641443892980C187 /* Collection+Shuffle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Collection+Shuffle.swift"; sourceTree = ""; }; 629A6A87FA655E721B318C15 /* Int+Random.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Int+Random.swift"; sourceTree = ""; }; 629A6D49B5E8DB9CAACD07CE /* TextCellItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextCellItem.swift; sourceTree = ""; }; @@ -94,6 +96,7 @@ 0A37638E1F62556400A80613 /* Info.plist */, 629A69A736A251CE646CE017 /* Extensions */, 629A6D6C61B4795949B30938 /* MainViewController.swift */, + 629A6B7B0DC722197E2D2AC9 /* SectionItems */, ); path = CollectionViewToolsExample; sourceTree = ""; @@ -133,6 +136,14 @@ path = Extensions; sourceTree = ""; }; + 629A6B7B0DC722197E2D2AC9 /* SectionItems */ = { + isa = PBXGroup; + children = ( + 629A688DD33578289A57B452 /* ExampleSectionItem.swift */, + ); + path = SectionItems; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -216,6 +227,7 @@ 629A6F192B106310AE2F4456 /* Collection+Shuffle.swift in Sources */, 629A66269E82069645F0C493 /* Int+Random.swift in Sources */, 629A6B0205FC8F49878C89C1 /* MainViewController.swift in Sources */, + 629A63FBA018222A355686F6 /* ExampleSectionItem.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/CollectionViewToolsExample/SectionItems/ExampleSectionItem.swift b/Example/CollectionViewToolsExample/SectionItems/ExampleSectionItem.swift new file mode 100644 index 0000000..e749bea --- /dev/null +++ b/Example/CollectionViewToolsExample/SectionItems/ExampleSectionItem.swift @@ -0,0 +1,8 @@ +// +// Copyright (c) 2018 Rosberry. All rights reserved. +// + +import Foundation + +final class ExampleSectionItem { +} From fc3ad9380a081cee628ca4c7e36e859e2aabb279 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 31 Aug 2018 14:28:16 +0600 Subject: [PATCH 31/54] Add printing of deinits to track it in debug --- .../CellItems/ImageCellItem.swift | 4 ++++ .../CellItems/TextCellItem.swift | 4 ++++ .../SectionItems/ExampleSectionItem.swift | 7 +++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift index 999ddc9..155d103 100644 --- a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift @@ -28,6 +28,10 @@ final class ImageCellItem: CollectionViewCellItem { self.selectionHandler = selectionHandler } + deinit { + print("\(self) deinit") + } + func size() -> CGSize { let ratio = image.size.width / image.size.height let width = collectionView.bounds.width / 2 - 16 diff --git a/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift b/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift index 68fb153..73b0841 100644 --- a/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift @@ -12,6 +12,10 @@ final class TextCellItem: CollectionViewCellItem { self.text = text } + deinit { + print("\(self) deinit") + } + private typealias Cell = TextCollectionViewCell private(set) var reuseType: ReuseType = .class(Cell.self) diff --git a/Example/CollectionViewToolsExample/SectionItems/ExampleSectionItem.swift b/Example/CollectionViewToolsExample/SectionItems/ExampleSectionItem.swift index e749bea..f98fc6b 100644 --- a/Example/CollectionViewToolsExample/SectionItems/ExampleSectionItem.swift +++ b/Example/CollectionViewToolsExample/SectionItems/ExampleSectionItem.swift @@ -2,7 +2,10 @@ // Copyright (c) 2018 Rosberry. All rights reserved. // -import Foundation +import CollectionViewTools -final class ExampleSectionItem { +final class ExampleSectionItem: GeneralCollectionViewSectionItem { + deinit { + print("\(self) deinit") + } } From 2c64c6501cbae039e14a665f77d5ec2fa4807552 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 31 Aug 2018 14:28:30 +0600 Subject: [PATCH 32/54] Implement setting section item to cel litems --- .../CellItems/ImageCellItem.swift | 7 ++++++- .../MainViewController.swift | 4 ++-- Sources/CollectionViewManager.swift | 6 +++++- .../CellItem/CollectionViewCellItem.swift | 14 ++++++++++++++ 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift index 155d103..1cbde62 100644 --- a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift @@ -33,8 +33,13 @@ final class ImageCellItem: CollectionViewCellItem { } func size() -> CGSize { + var shift: CGFloat = 0 + if let sectionItem = sectionItem { + shift = sectionItem.insets.left / 2 + sectionItem.insets.right / 2 + shift += shift / 2 + } let ratio = image.size.width / image.size.height - let width = collectionView.bounds.width / 2 - 16 + let width = collectionView.bounds.width / 2 - shift return .init(width: width, height: width / ratio) } diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index 629c2e4..c3619bf 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -91,7 +91,7 @@ class MainViewController: UIViewController { // MARK: - Factory methods func makeImagesSectionItem(images: [UIImage]) -> CollectionViewSectionItem { - let sectionItem = GeneralCollectionViewSectionItem() + let sectionItem = ExampleSectionItem() sectionItem.cellItems = images.map { image in return makeImageCellItem(image: image) } @@ -115,7 +115,7 @@ class MainViewController: UIViewController { // MARK: Actions cell items func makeActionsSectionItem() -> CollectionViewSectionItem { - let sectionItem = GeneralCollectionViewSectionItem() + let sectionItem = ExampleSectionItem() sectionItem.cellItems = [ makeResetActionCellItem(), // Insert cells diff --git a/Sources/CollectionViewManager.swift b/Sources/CollectionViewManager.swift index c01c7ba..baef0f4 100644 --- a/Sources/CollectionViewManager.swift +++ b/Sources/CollectionViewManager.swift @@ -137,14 +137,16 @@ open class CollectionViewManager: NSObject { // MARK: - Registration /// Use this function to force cells and reusable views registration process if you override add/replace/reload methods - /// Also in this method section item got set collection view and perform setting index paths for cell items. + /// Also in this method section item got set collection view and perform setting index paths and section item for cell items. /// So pay attention to the order of operations for section items updates methods. Set correct index for section item first. + /// And don't forget to set `sectionItem` property for cell items. /// /// - Parameter sectionItem: The section item with cell items which need to be registered open func register(_ sectionItem: CollectionViewSectionItem) { sectionItem.collectionView = collectionView sectionItem.cellItems.enumerated().forEach { (index, cellItem) in cellItem.indexPath = .init(row: index, section: sectionItem.index) + cellItem.sectionItem = sectionItem register(cellItem) } sectionItem.reusableViewItems.forEach { reusableViewItem in @@ -217,6 +219,7 @@ open class CollectionViewManager: NSObject { zip(cellItems, indexes).forEach { cellItem, index in register(cellItem) sectionItem.cellItems.insert(cellItem, at: index) + cellItem.sectionItem = sectionItem } self?.recalculateIndexPaths(in: sectionItem) @@ -268,6 +271,7 @@ open class CollectionViewManager: NSObject { perform(updates: { [weak self] collectionView in cellItems.forEach { cellItem in register(cellItem) + cellItem.sectionItem = sectionItem } if indexes.count == cellItems.count { diff --git a/Sources/Protocols/CellItem/CollectionViewCellItem.swift b/Sources/Protocols/CellItem/CollectionViewCellItem.swift index c6a9dfc..0ff0d69 100644 --- a/Sources/Protocols/CellItem/CollectionViewCellItem.swift +++ b/Sources/Protocols/CellItem/CollectionViewCellItem.swift @@ -41,6 +41,7 @@ public protocol CollectionViewConfigureCellItem: AnyObject { public protocol CollectionViewSiblingCellItem: AnyObject { var collectionView: UICollectionView { get set } var indexPath: IndexPath { get set } + var sectionItem: CollectionViewSectionItem? { get set } } extension CollectionViewSiblingCellItem { @@ -69,6 +70,18 @@ extension CollectionViewSiblingCellItem { objc_setAssociatedObject(self, &AssociatedKeys.indexPath, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } + + public weak var sectionItem: CollectionViewSectionItem? { + get { + if let object = objc_getAssociatedObject(self, &AssociatedKeys.sectionItem) as? CollectionViewSectionItem { + return object + } + return nil + } + set { + objc_setAssociatedObject(self, &AssociatedKeys.sectionItem, newValue, .OBJC_ASSOCIATION_ASSIGN) + } + } } // MARK: - CollectionViewGeneralCellItem @@ -134,6 +147,7 @@ private enum AssociatedKeys { static var collectionView = "rsb_collectionView" static var indexPath = "rsb_indexPath" + static var sectionItem = "rsb_sectionItem" } public extension CollectionViewGeneralCellItem { From 85f351987f188c460807dcf18f216c2dff8b1a97 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 31 Aug 2018 14:30:17 +0600 Subject: [PATCH 33/54] Remove redundant any collection view cell item --- CollectionViewTools.xcodeproj/project.pbxproj | 4 --- .../CellItem/AnyCollectionViewCellItem.swift | 36 ------------------- 2 files changed, 40 deletions(-) delete mode 100644 Sources/Protocols/CellItem/AnyCollectionViewCellItem.swift diff --git a/CollectionViewTools.xcodeproj/project.pbxproj b/CollectionViewTools.xcodeproj/project.pbxproj index 0d30298..49c1c23 100644 --- a/CollectionViewTools.xcodeproj/project.pbxproj +++ b/CollectionViewTools.xcodeproj/project.pbxproj @@ -23,7 +23,6 @@ 0A3763731F62545A00A80613 /* CollectionViewCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763621F62545A00A80613 /* CollectionViewCellItem.swift */; }; 0A3763741F62545A00A80613 /* CollectionViewReusableViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763641F62545A00A80613 /* CollectionViewReusableViewItem.swift */; }; 0A3763751F62545A00A80613 /* CollectionViewSectionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763661F62545A00A80613 /* CollectionViewSectionItem.swift */; }; - 629A67012CD6843FB2869A01 /* AnyCollectionViewCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A6094DC950478A5E96F92 /* AnyCollectionViewCellItem.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -56,7 +55,6 @@ 0A3763621F62545A00A80613 /* CollectionViewCellItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCellItem.swift; sourceTree = ""; }; 0A3763641F62545A00A80613 /* CollectionViewReusableViewItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewReusableViewItem.swift; sourceTree = ""; }; 0A3763661F62545A00A80613 /* CollectionViewSectionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewSectionItem.swift; sourceTree = ""; }; - 629A6094DC950478A5E96F92 /* AnyCollectionViewCellItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyCollectionViewCellItem.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -163,7 +161,6 @@ children = ( 0A3763611F62545A00A80613 /* CollectionViewCellItemDataSourcePrefetching.swift */, 0A3763621F62545A00A80613 /* CollectionViewCellItem.swift */, - 629A6094DC950478A5E96F92 /* AnyCollectionViewCellItem.swift */, ); path = CellItem; sourceTree = ""; @@ -311,7 +308,6 @@ 0A3763671F62545A00A80613 /* ClosureWrapper.swift in Sources */, 0A37636E1F62545A00A80613 /* CollectionViewManager+UIScrollViewDelegate.swift in Sources */, 0A3763751F62545A00A80613 /* CollectionViewSectionItem.swift in Sources */, - 629A67012CD6843FB2869A01 /* AnyCollectionViewCellItem.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/Protocols/CellItem/AnyCollectionViewCellItem.swift b/Sources/Protocols/CellItem/AnyCollectionViewCellItem.swift deleted file mode 100644 index c3bf078..0000000 --- a/Sources/Protocols/CellItem/AnyCollectionViewCellItem.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// Copyright (c) 2018 Rosberry. All rights reserved. -// - -import UIKit.UICollectionView - -//open class AnyCollectionViewCellItem: CollectionViewCellItem { -// -// public typealias Cell = T -// -// private let _configure: (T, IndexPath) -> Void -// private let _reuseType: () -> ReuseType -// -// public var reuseType: ReuseType { -// return _reuseType() -// } -// -// public init(_ cellItem: U) where U.Cell == T { -// _configure = { [weak cellItem] (cell, indexPath) in -// guard let cellItem = cellItem else { -// return -// } -// cellItem.configure(cell: cell, at: indexPath) -// } -// _reuseType = { [weak cellItem] in -// guard let cellItem = cellItem else { -// fatalError("It is impossible to create cell item without reuse type") -// } -// return cellItem.reuseType -// } -// } -// -// public func configure(cell: Cell, at indexPath: IndexPath) { -// _configure(cell, indexPath) -// } -//} From e62db36f7e6c41d38a8e387535129e686383f5e3 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 21 Sep 2018 11:00:03 +0600 Subject: [PATCH 34/54] Improve section items updates performance --- .../MainViewController.swift | 1 + Sources/CollectionViewManager.swift | 46 +++++++++++++------ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index c3619bf..906a0fa 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -40,6 +40,7 @@ class MainViewController: UIViewController { lazy var mainCollectionView: UICollectionView = { let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) collectionView.backgroundColor = .clear + collectionView.alwaysBounceVertical = true return collectionView }() diff --git a/Sources/CollectionViewManager.swift b/Sources/CollectionViewManager.swift index baef0f4..519cf59 100644 --- a/Sources/CollectionViewManager.swift +++ b/Sources/CollectionViewManager.swift @@ -32,14 +32,7 @@ open class CollectionViewManager: NSObject { _ destinationIndexPath: IndexPath) -> Void)? /// Use this property instead of `sectionItems` internally to avoid every time reload during update operations. - internal var _sectionItems = [CollectionViewSectionItem]() { - didSet { - _sectionItems.enumerated().forEach { (index, sectionItem) in - sectionItem.index = index - register(sectionItem) - } - } - } + internal var _sectionItems = [CollectionViewSectionItem]() /// Array of `CollectionViewSectionItem` objects, which respond for configuration of specified section in collection view. /// Setting this property leads collection view to reload data. If you don't need this behaviour use update methods instead. @@ -49,10 +42,13 @@ open class CollectionViewManager: NSObject { } set { _sectionItems = newValue + registerSectionItems() collectionView.reloadData() } } + // MARK: Life cycle + public init(collectionView: UICollectionView) { self.collectionView = collectionView super.init() @@ -63,6 +59,8 @@ open class CollectionViewManager: NSObject { } } + // MARK: - Subscripts + /// Accesses the section item at the specified position. /// /// - Parameter index: The index of the section item to access. @@ -80,6 +78,8 @@ open class CollectionViewManager: NSObject { return cellItem(for: indexPath) } + // MARK: - Common methods + /// Reloads cells, associated with passed cell items. /// /// - Parameters: @@ -134,6 +134,13 @@ open class CollectionViewManager: NSObject { } } + /// Use this method if you need to set new section items without reloading data. + /// This method invokes register methods. + open func updateSectionItemsWithoutReloadData(_ sectionItems: [CollectionViewSectionItem]) { + _sectionItems = sectionItems + registerSectionItems() + } + // MARK: - Registration /// Use this function to force cells and reusable views registration process if you override add/replace/reload methods @@ -182,10 +189,15 @@ open class CollectionViewManager: NSObject { } } - /// Use this function to force update indexes for all section items during custom update operations. - open func recalculateSectionsIndexes() { + /// Use this function to force update indexes for all section + /// items and inner cell items during custom update operations. + open func recalculateIndexes() { _sectionItems.enumerated().forEach { index, sectionItem in sectionItem.index = index + sectionItem.cellItems.enumerated().forEach { (index, cellItem) in + cellItem.indexPath = .init(row: index, section: sectionItem.index) + cellItem.sectionItem = sectionItem + } } } @@ -359,7 +371,7 @@ open class CollectionViewManager: NSObject { zip(sectionItems, indexes).forEach { sectionItem, index in _sectionItems.insert(sectionItem, at: index) } - self?.recalculateSectionsIndexes() + self?.recalculateIndexes() sectionItems.forEach { sectionItem in register(sectionItem) } @@ -417,7 +429,7 @@ open class CollectionViewManager: NSObject { } _sectionItems.insert(contentsOf: sectionItems, at: firstIndex) - self?.recalculateSectionsIndexes() + self?.recalculateIndexes() sectionItems.forEach { sectionItem in register(sectionItem) @@ -452,7 +464,7 @@ open class CollectionViewManager: NSObject { indexes.forEach { index in _sectionItems.remove(at: index) } - self?.recalculateSectionsIndexes() + self?.recalculateIndexes() collectionView?.deleteSections(IndexSet(indexes)) }, completion: completion) } @@ -465,4 +477,12 @@ open class CollectionViewManager: NSObject { updates(collectionView) }, completion: completion) } + + /// Use this method to perform register and set indexes operations for all section items. + private func registerSectionItems() { + _sectionItems.enumerated().forEach { (index, sectionItem) in + sectionItem.index = index + register(sectionItem) + } + } } From 4babcf48bf1771fc90617052db1d439e5fda8566 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 21 Sep 2018 11:14:36 +0600 Subject: [PATCH 35/54] Update to swift 4.2 syntax --- CollectionViewTools.xcodeproj/project.pbxproj | 8 +++----- .../CollectionViewToolsExample.xcodeproj/project.pbxproj | 8 +++----- Example/CollectionViewToolsExample/AppDelegate.swift | 2 +- Sources/CollectionViewManager.swift | 2 +- .../ReuseViewItem/CollectionViewReusableViewItem.swift | 6 +++--- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/CollectionViewTools.xcodeproj/project.pbxproj b/CollectionViewTools.xcodeproj/project.pbxproj index 49c1c23..5cb6474 100644 --- a/CollectionViewTools.xcodeproj/project.pbxproj +++ b/CollectionViewTools.xcodeproj/project.pbxproj @@ -244,7 +244,7 @@ 0A3763241F6251A600A80613 = { CreatedOnToolsVersion = 8.3.3; DevelopmentTeam = GPVA8JVMU3; - LastSwiftMigration = 0910; + LastSwiftMigration = 1000; ProvisioningStyle = Automatic; }; 0A37632D1F6251A600A80613 = { @@ -463,8 +463,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rosberry.CollectionViewTools; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -484,8 +483,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.rosberry.CollectionViewTools; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj b/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj index 2be914d..c0a66fe 100644 --- a/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj +++ b/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj @@ -178,7 +178,7 @@ 0A37637E1F62556300A80613 = { CreatedOnToolsVersion = 8.3.3; DevelopmentTeam = GPVA8JVMU3; - LastSwiftMigration = 0910; + LastSwiftMigration = 1000; ProvisioningStyle = Automatic; }; }; @@ -364,8 +364,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.rosberry.CollectionViewToolsExample; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -379,8 +378,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.rosberry.CollectionViewToolsExample; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/Example/CollectionViewToolsExample/AppDelegate.swift b/Example/CollectionViewToolsExample/AppDelegate.swift index 4286127..8040c79 100644 --- a/Example/CollectionViewToolsExample/AppDelegate.swift +++ b/Example/CollectionViewToolsExample/AppDelegate.swift @@ -11,7 +11,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window?.backgroundColor = .white window?.rootViewController = UINavigationController(rootViewController: MainViewController()) diff --git a/Sources/CollectionViewManager.swift b/Sources/CollectionViewManager.swift index 519cf59..62ef98f 100644 --- a/Sources/CollectionViewManager.swift +++ b/Sources/CollectionViewManager.swift @@ -103,7 +103,7 @@ open class CollectionViewManager: NSObject { /// - scrollPosition: A constant that identifies a relative position in the collection view (top, middle, bottom) for item when /// scrolling concludes. See UICollectionViewScrollPosition for descriptions of valid constants. /// - animated: true if you want to animate the change in position; false if it should be immediate. - open func scroll(to cellItem: CellItem, at scrollPosition: UICollectionViewScrollPosition, animated: Bool = true) { + open func scroll(to cellItem: CellItem, at scrollPosition: UICollectionView.ScrollPosition, animated: Bool = true) { collectionView.scrollToItem(at: cellItem.indexPath, at: scrollPosition, animated: animated) } diff --git a/Sources/Protocols/ReuseViewItem/CollectionViewReusableViewItem.swift b/Sources/Protocols/ReuseViewItem/CollectionViewReusableViewItem.swift index 03393e0..910f4c9 100644 --- a/Sources/Protocols/ReuseViewItem/CollectionViewReusableViewItem.swift +++ b/Sources/Protocols/ReuseViewItem/CollectionViewReusableViewItem.swift @@ -12,9 +12,9 @@ public enum ReusableViewType { public var kind: String { switch self { case .header: - return UICollectionElementKindSectionHeader - case .footer: return - UICollectionElementKindSectionFooter + return UICollectionView.elementKindSectionHeader + case .footer: + return UICollectionView.elementKindSectionFooter } } } From 5f1d83d02b04f1273c12e04698b840e9b0f19f9c Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 21 Sep 2018 13:36:44 +0600 Subject: [PATCH 36/54] Update README --- .../MainViewController.swift | 2 +- README.md | 39 ++++++++----------- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index 906a0fa..0b806f7 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -374,7 +374,7 @@ class MainViewController: UIViewController { guard let `self` = self else { return } - + var sectionItems: [CollectionViewSectionItem] = [] for _ in 0..<2 { sectionItems.append(self.makeImagesSectionItem(images: self.shuffledImages)) diff --git a/README.md b/README.md index 787954e..8ffc173 100755 --- a/README.md +++ b/README.md @@ -14,11 +14,10 @@ Effective framework, similar to [TableViewTools](https://github.com/rosberry/Tab - Separate layer that synchronizes data with the cell appearance - Full implementation of UICollectionViewDelegate and UICollectionViewDataSource under the hood - Support of protocols and subclasses as data models -- No type casts and switches required ## Requirements -- iOS 8.0+ +- iOS 8.2+ - Xcode 8.0+ ## Installation @@ -31,10 +30,10 @@ github "rosberry/CollectionViewTools" ``` #### CocoaPods -You can use [CocoaPods](http://cocoapods.org/) to install `Product Name` by adding it to your `Podfile`: +You can use [CocoaPods](http://cocoapods.org/) to install `CollectionViewTools` by adding it to your `Podfile`: ```ruby -platform :ios, '8.0' +platform :ios, '8.2' use_frameworks! pod 'CollectionViewTools' ``` @@ -47,20 +46,17 @@ Drag `Sources` folder from [last release](https://github.com/rosberry/Collection #### Creating manager ```swift -manager = CollectionViewManager(collectionView: collectionView) +let manager = CollectionViewManager(collectionView: collectionView) ``` #### Creating section ```swift let titles = ["Item 1", "Item 2", "Item 3"] -var cellItems = [ExampleCollectionViewCellItem]() -titles.forEach { title in -let cellItem = ExampleCollectionViewCellItem(title: title) - cellItems.append(cellItem) +var cellItems = titles.map { title in + return ExampleCollectionViewCellItem(title: title) } - -let sectionItem = CollectionViewSectionItem(cellItems: cellItems) +let sectionItem = GeneralCollectionViewSectionItem(cellItems: cellItems) manager.sectionItems = [sectionItem] ``` @@ -69,32 +65,31 @@ manager.sectionItems = [sectionItem] ```swift class ExampleCollectionViewCellItem: CollectionViewCellItem { - private let title: String + typealias Cell = ExampleCollectionViewCell + private(set) var reuseType: ReuseType = .class(Cell.self) - var reuseType = ReuseType(cellClass: ExampleCollectionViewCell.self) + private let title: String init(title: String) { self.title = title } - func size(for collectionView: UICollectionView, at indexPath: IndexPath) -> CGSize { - return CGSize(width: 100, height: 40) - } - - func cell(for collectionView: UICollectionView, at indexPath: IndexPath) -> UICollectionViewCell { - let cell: ExampleCollectionViewCell = collectionView.dequeueReusableCell(for: indexPath) + func configure(_ cell: UICollectionViewCell) { + guard let cell = cell as? Cell else { + return + } cell.titleLabel.text = title - return cell } - func size(for collectionView: UICollectionView, with layout: UICollectionViewLayout, at indexPath: IndexPath) -> CGSize { - return size(for: collectionView, at: indexPath) + func size() -> CGSize { + return CGSize(width: 100, height: 40) } } ``` ## Authors +* Anton Kovalev, anton.kovalev@rosberry.com * Dmitry Frishbuter, dmitry.frishbuter@rosberry.com * Artem Novichkov, artem.novichkov@rosberry.com From 8b40963f44307286c56fd8f0e6456b53e56d45ef Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 21 Sep 2018 13:37:55 +0600 Subject: [PATCH 37/54] Update podspec --- CollectionViewTools.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CollectionViewTools.podspec b/CollectionViewTools.podspec index 7e459e6..22c8227 100644 --- a/CollectionViewTools.podspec +++ b/CollectionViewTools.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'CollectionViewTools' - s.version = '0.0.5' + s.version = '0.1.0' s.summary = 'Powerful tool for making UICollectionView usage simple and comfortable.' # This description is used to generate tags and improve search results. @@ -26,7 +26,7 @@ Effective framework, similar to TableViewTools for making your UICollectionView s.author = { 'Dmitry Frishbuter' => 'dmitry.frishbuter@rosberry.com' } s.source = { :git => 'https://github.com/DmitryFrishbuter/CollectionViewTools.git', :tag => s.version.to_s } - s.ios.deployment_target = '8.0' + s.ios.deployment_target = '8.2' s.source_files = 'Sources/**/*' From 2e12cb20786bdb6ec6efa85fdb26f877715b9df5 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 28 Sep 2018 10:12:33 +0600 Subject: [PATCH 38/54] Fix wrong associated keys --- Sources/Protocols/CellItem/CollectionViewCellItem.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Protocols/CellItem/CollectionViewCellItem.swift b/Sources/Protocols/CellItem/CollectionViewCellItem.swift index 0ff0d69..34526f4 100644 --- a/Sources/Protocols/CellItem/CollectionViewCellItem.swift +++ b/Sources/Protocols/CellItem/CollectionViewCellItem.swift @@ -183,19 +183,19 @@ public extension CollectionViewGeneralCellItem { var itemDidSelectHandler: ActionHandler? { get { - return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didDeselectHandler) + return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didSelectHandler) } set { - ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didDeselectHandler) + ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didSelectHandler) } } var itemDidDeselectHandler: ActionHandler? { get { - return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didSelectHandler) + return ClosureWrapper.handler(for: self, key: &AssociatedKeys.didDeselectHandler) } set { - ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didSelectHandler) + ClosureWrapper.setHandler(newValue, for: self, key: &AssociatedKeys.didDeselectHandler) } } From 2e53193b0e68ba37102c1ed143417c2e56d4f149 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 28 Sep 2018 10:26:13 +0600 Subject: [PATCH 39/54] Update travis CI configuration --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c0f0f8c..4e098ec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: swift -osx_image: xcode8 +osx_image: xcode10 env: global: - PROJECT=CollectionViewTools.xcodeproj @@ -7,6 +7,8 @@ env: - IOS_SDK=iphonesimulator10.0 matrix: - DESTINATION="OS=10.0,name=iPhone 7 Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" + - DESTINATION="OS=11.0,name=iPhone X" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" + - DESTINATION="OS=12.0,name=iPhone XS Max" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" script: - set -o pipefail From 98d88a3799395a9f62756bb9dd5d6327c4d39758 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 28 Sep 2018 10:46:53 +0600 Subject: [PATCH 40/54] Update Travis config --- .travis.yml | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4e098ec..1be39a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,22 @@ language: swift osx_image: xcode10 + env: - global: - - PROJECT=CollectionViewTools.xcodeproj - - IOS_FRAMEWORK_SCHEME="CollectionViewTools" - - IOS_SDK=iphonesimulator10.0 - matrix: - - DESTINATION="OS=10.0,name=iPhone 7 Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" - - DESTINATION="OS=11.0,name=iPhone X" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" - - DESTINATION="OS=12.0,name=iPhone XS Max" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" +- LC_CTYPE=en_US.UTF-8 LANG=en_US.UTF-8 + +before_install: +- brew update +- brew outdated xctool || brew upgrade xctool + +global: +- PROJECT=CollectionViewTools.xcodeproj +- IOS_FRAMEWORK_SCHEME="CollectionViewTools" + +matrix: +- DESTINATION="OS=10.0,name=iPhone 7 Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" +- DESTINATION="OS=11.0,name=iPhone X" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" +- DESTINATION="OS=12.0,name=iPhone XS Max" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" script: - - set -o pipefail - - xcodebuild -project "$PROJECT" -scheme "$IOS_FRAMEWORK_SCHEME" -sdk "$IOS_SDK" -destination "$DESTINATION" build test | xcpretty -c --test --color +- set -o pipefail +- xcodebuild -project "$PROJECT" -scheme "$IOS_FRAMEWORK_SCHEME" -sdk "$IOS_SDK" -destination "$DESTINATION" build test | xcpretty -c --test --color From a46822e4942f82de6c5bc37286e0e13fb6cb421a Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 28 Sep 2018 10:50:16 +0600 Subject: [PATCH 41/54] Update Travis config --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 1be39a1..409fd24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ before_install: global: - PROJECT=CollectionViewTools.xcodeproj - IOS_FRAMEWORK_SCHEME="CollectionViewTools" +- IOS_SDK=iphonesimulator matrix: - DESTINATION="OS=10.0,name=iPhone 7 Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" From 0c75edee892a31feea2a594846b42c36344c8a83 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 28 Sep 2018 10:55:01 +0600 Subject: [PATCH 42/54] Update Travis config --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 409fd24..2034217 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,8 +14,6 @@ global: - IOS_SDK=iphonesimulator matrix: -- DESTINATION="OS=10.0,name=iPhone 7 Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" -- DESTINATION="OS=11.0,name=iPhone X" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" - DESTINATION="OS=12.0,name=iPhone XS Max" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" script: From 9a6ea7da6e89cdcf7850adecaf406834149b87ba Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 28 Sep 2018 11:06:44 +0600 Subject: [PATCH 43/54] Update Travis config --- .travis.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2034217..cf9f9fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,9 +13,6 @@ global: - IOS_FRAMEWORK_SCHEME="CollectionViewTools" - IOS_SDK=iphonesimulator -matrix: -- DESTINATION="OS=12.0,name=iPhone XS Max" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" - script: - set -o pipefail -- xcodebuild -project "$PROJECT" -scheme "$IOS_FRAMEWORK_SCHEME" -sdk "$IOS_SDK" -destination "$DESTINATION" build test | xcpretty -c --test --color +- xcodebuild -project "$PROJECT" -scheme "$IOS_FRAMEWORK_SCHEME" -sdk "$IOS_SDK" build test | xcpretty -c --test --color From abe2fa30b54d072246cd0400b00599713d23946c Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 28 Sep 2018 11:16:45 +0600 Subject: [PATCH 44/54] Update Travis config --- .travis.yml | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index cf9f9fe..96d09e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,21 @@ language: swift osx_image: xcode10 -env: -- LC_CTYPE=en_US.UTF-8 LANG=en_US.UTF-8 - before_install: -- brew update -- brew outdated xctool || brew upgrade xctool + - brew update + - brew outdated xctool || brew upgrade xctool + +env: + global: + - PROJECT=CollectionViewTools.xcodeproj + - IOS_FRAMEWORK_SCHEME=CollectionViewTools + - IOS_SDK=iphonesimulator -global: -- PROJECT=CollectionViewTools.xcodeproj -- IOS_FRAMEWORK_SCHEME="CollectionViewTools" -- IOS_SDK=iphonesimulator + matrix: + - DESTINATION="OS=10.0,name=iPhone 7 Plus" + - DESTINATION="OS=11.0,name=iPhone X" + - DESTINATION="OS=12.0,name=iPhone XS Max" script: -- set -o pipefail -- xcodebuild -project "$PROJECT" -scheme "$IOS_FRAMEWORK_SCHEME" -sdk "$IOS_SDK" build test | xcpretty -c --test --color + - set -o pipefail + - xcodebuild -project "$PROJECT" -scheme "$IOS_FRAMEWORK_SCHEME" -sdk "$IOS_SDK" -destination "$DESTINATION" build test | xcpretty -c --test --color From 7eb5fc85fdf0b7f79cbb336c0ba5f4768f4eb655 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 28 Sep 2018 11:23:15 +0600 Subject: [PATCH 45/54] Update Travis config --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 96d09e2..602d555 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,8 +12,8 @@ env: - IOS_SDK=iphonesimulator matrix: - - DESTINATION="OS=10.0,name=iPhone 7 Plus" - - DESTINATION="OS=11.0,name=iPhone X" + - DESTINATION="OS=10.1,name=iPhone 7 Plus" + - DESTINATION="OS=11.0.1,name=iPhone X" - DESTINATION="OS=12.0,name=iPhone XS Max" script: From 6eed7056ab861767f0115f98b1fd17b473e766cf Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 28 Sep 2018 13:12:39 +0600 Subject: [PATCH 46/54] Fix code review remarks --- .../project.pbxproj | 16 ---------- .../CellItems/TextCellItem.swift | 5 ++-- .../Extensions/Collection+Shuffle.swift | 29 ------------------- .../Extensions/Int+Random.swift | 20 ------------- .../MainViewController.swift | 6 ++-- 5 files changed, 5 insertions(+), 71 deletions(-) delete mode 100644 Example/CollectionViewToolsExample/Extensions/Collection+Shuffle.swift delete mode 100644 Example/CollectionViewToolsExample/Extensions/Int+Random.swift diff --git a/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj b/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj index c0a66fe..4616080 100644 --- a/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj +++ b/Example/CollectionViewToolsExample.xcodeproj/project.pbxproj @@ -14,10 +14,8 @@ 0ACA6FCC1F626D2E00782C1E /* ImageCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACA6FCB1F626D2E00782C1E /* ImageCollectionViewCell.swift */; }; 0ACA6FCE1F6275D200782C1E /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ACA6FCD1F6275D200782C1E /* DetailViewController.swift */; }; 629A63FBA018222A355686F6 /* ExampleSectionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A688DD33578289A57B452 /* ExampleSectionItem.swift */; }; - 629A66269E82069645F0C493 /* Int+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A6A87FA655E721B318C15 /* Int+Random.swift */; }; 629A6B0205FC8F49878C89C1 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A6D6C61B4795949B30938 /* MainViewController.swift */; }; 629A6E35B86008DC359DB40B /* TextCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A6D49B5E8DB9CAACD07CE /* TextCellItem.swift */; }; - 629A6F192B106310AE2F4456 /* Collection+Shuffle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A6A35641443892980C187 /* Collection+Shuffle.swift */; }; 629A6F552D5D656B36F4F52C /* TextCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A60E846B05B88D62C7880 /* TextCollectionViewCell.swift */; }; FED796BA20EF6B3B0044B82F /* CollectionViewTools.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE6E86B31FB961B20027A8FE /* CollectionViewTools.framework */; }; FED796BB20EF6B3B0044B82F /* CollectionViewTools.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FE6E86B31FB961B20027A8FE /* CollectionViewTools.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -48,8 +46,6 @@ 0ACA6FCD1F6275D200782C1E /* DetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; 629A60E846B05B88D62C7880 /* TextCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextCollectionViewCell.swift; sourceTree = ""; }; 629A688DD33578289A57B452 /* ExampleSectionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExampleSectionItem.swift; sourceTree = ""; }; - 629A6A35641443892980C187 /* Collection+Shuffle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Collection+Shuffle.swift"; sourceTree = ""; }; - 629A6A87FA655E721B318C15 /* Int+Random.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Int+Random.swift"; sourceTree = ""; }; 629A6D49B5E8DB9CAACD07CE /* TextCellItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextCellItem.swift; sourceTree = ""; }; 629A6D6C61B4795949B30938 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; FE6E86B31FB961B20027A8FE /* CollectionViewTools.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CollectionViewTools.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -94,7 +90,6 @@ 0A3763891F62556300A80613 /* Assets.xcassets */, 0A37638B1F62556400A80613 /* LaunchScreen.storyboard */, 0A37638E1F62556400A80613 /* Info.plist */, - 629A69A736A251CE646CE017 /* Extensions */, 629A6D6C61B4795949B30938 /* MainViewController.swift */, 629A6B7B0DC722197E2D2AC9 /* SectionItems */, ); @@ -127,15 +122,6 @@ path = Cells; sourceTree = ""; }; - 629A69A736A251CE646CE017 /* Extensions */ = { - isa = PBXGroup; - children = ( - 629A6A35641443892980C187 /* Collection+Shuffle.swift */, - 629A6A87FA655E721B318C15 /* Int+Random.swift */, - ); - path = Extensions; - sourceTree = ""; - }; 629A6B7B0DC722197E2D2AC9 /* SectionItems */ = { isa = PBXGroup; children = ( @@ -224,8 +210,6 @@ 0A3763831F62556300A80613 /* AppDelegate.swift in Sources */, 629A6E35B86008DC359DB40B /* TextCellItem.swift in Sources */, 629A6F552D5D656B36F4F52C /* TextCollectionViewCell.swift in Sources */, - 629A6F192B106310AE2F4456 /* Collection+Shuffle.swift in Sources */, - 629A66269E82069645F0C493 /* Int+Random.swift in Sources */, 629A6B0205FC8F49878C89C1 /* MainViewController.swift in Sources */, 629A63FBA018222A355686F6 /* ExampleSectionItem.swift in Sources */, ); diff --git a/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift b/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift index 73b0841..d74b4ce 100644 --- a/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift @@ -6,6 +6,8 @@ import CollectionViewTools final class TextCellItem: CollectionViewCellItem { + private typealias Cell = TextCollectionViewCell + private(set) var reuseType: ReuseType = .class(Cell.self) private let text: String init(text: String) { @@ -16,9 +18,6 @@ final class TextCellItem: CollectionViewCellItem { print("\(self) deinit") } - private typealias Cell = TextCollectionViewCell - private(set) var reuseType: ReuseType = .class(Cell.self) - func configure(_ cell: UICollectionViewCell) { guard let cell = cell as? Cell else { return diff --git a/Example/CollectionViewToolsExample/Extensions/Collection+Shuffle.swift b/Example/CollectionViewToolsExample/Extensions/Collection+Shuffle.swift deleted file mode 100644 index 4084bd3..0000000 --- a/Example/CollectionViewToolsExample/Extensions/Collection+Shuffle.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright (c) 2018 Rosberry. All rights reserved. -// - -import Foundation - -extension MutableCollection { - /// Shuffles the contents of this collection. - mutating func shuffle() { - let c = count - guard c > 1 else { return } - - for (firstUnshuffled, unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) { - // Change `Int` in the next line to `IndexDistance` in < Swift 4.1 - let d: Int = numericCast(arc4random_uniform(numericCast(unshuffledCount))) - let i = index(firstUnshuffled, offsetBy: d) - swapAt(firstUnshuffled, i) - } - } -} - -extension Sequence { - /// Returns an array with the contents of this sequence, shuffled. - func shuffled() -> [Element] { - var result = Array(self) - result.shuffle() - return result - } -} diff --git a/Example/CollectionViewToolsExample/Extensions/Int+Random.swift b/Example/CollectionViewToolsExample/Extensions/Int+Random.swift deleted file mode 100644 index 184a88f..0000000 --- a/Example/CollectionViewToolsExample/Extensions/Int+Random.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// Copyright (c) 2018 Rosberry. All rights reserved. -// - -import Foundation - -extension Int { - static func random(range: Range) -> Int { - var offset = 0 - - if range.lowerBound < 0 { - offset = abs(range.upperBound) - } - - let minValue = UInt32(range.lowerBound + offset) - let maxValue = UInt32(range.upperBound + offset) - - return Int(minValue + arc4random_uniform(maxValue - minValue)) - offset - } -} diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index 0b806f7..d2041ac 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -296,14 +296,14 @@ class MainViewController: UIViewController { return } - let sectionIndex = nonEmptySectionsIndexes[Int.random(range: 0.. 0 else { return } - let cellIndex = Int.random(range: 0.. Date: Fri, 28 Sep 2018 13:26:58 +0600 Subject: [PATCH 47/54] Add comment for images method in example --- Example/CollectionViewToolsExample/MainViewController.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index d2041ac..ae83745 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -14,6 +14,8 @@ class MainViewController: UIViewController { } var images: [UIImage] { var images: [UIImage] = [] + // You can change number of cell items in section + // for tests easily by increasing upper bound of range. for _ in 0..<1 { images.append(contentsOf: shuffledImages) } From 96703fd132dfc2b222e34f1bac4da83ad4bfc045 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 28 Sep 2018 13:36:19 +0600 Subject: [PATCH 48/54] Update swift badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ffc173..4bed363 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # CollectionViewTools [![Platform](https://img.shields.io/cocoapods/p/CollectionViewTools.svg?style=flat)](http://cocoapods.org/pods/CollectionViewTools) -[![Swift Version](https://img.shields.io/badge/swift-3.0-orange.svg)](https://swift.org/) +[![Swift Version](https://img.shields.io/badge/swift-4.2-orange.svg)](https://swift.org/) [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-blue.svg)](https://github.com/Carthage/Carthage) [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/CollectionViewTools.svg)](https://img.shields.io/cocoapods/v/CollectionViewTools.svg) [![SPM Compatible](https://img.shields.io/badge/spm-compatible-brightgreen.svg?style=flat)](https://img.shields.io/badge/spm-compatible-brightgreen.svg?style=flat) From 3706568a616819012182100f77107425cd9cc6ff Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 28 Sep 2018 13:48:03 +0600 Subject: [PATCH 49/54] Add update section items method for @medvedzzz --- .../MainViewController.swift | 4 ++- Sources/CollectionViewManager.swift | 28 +++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Example/CollectionViewToolsExample/MainViewController.swift b/Example/CollectionViewToolsExample/MainViewController.swift index ae83745..e63a5dd 100644 --- a/Example/CollectionViewToolsExample/MainViewController.swift +++ b/Example/CollectionViewToolsExample/MainViewController.swift @@ -81,7 +81,9 @@ class MainViewController: UIViewController { // MARK: - Private private func resetMainCollection() { - mainCollectionViewManager.sectionItems = [makeImagesSectionItem(images: images)] + mainCollectionViewManager.update([makeImagesSectionItem(images: images)], shouldReloadData: true) { + print("Reload complete") + } mainCollectionView.contentOffset = .zero } diff --git a/Sources/CollectionViewManager.swift b/Sources/CollectionViewManager.swift index 62ef98f..9eb281e 100644 --- a/Sources/CollectionViewManager.swift +++ b/Sources/CollectionViewManager.swift @@ -41,9 +41,7 @@ open class CollectionViewManager: NSObject { return _sectionItems } set { - _sectionItems = newValue - registerSectionItems() - collectionView.reloadData() + update(newValue, shouldReloadData: true, completion: nil) } } @@ -134,11 +132,23 @@ open class CollectionViewManager: NSObject { } } - /// Use this method if you need to set new section items without reloading data. + /// Use this method if you need to set new section items. /// This method invokes register methods. - open func updateSectionItemsWithoutReloadData(_ sectionItems: [CollectionViewSectionItem]) { - _sectionItems = sectionItems - registerSectionItems() + /// - Parameters: + /// - sectionItems: Array of `CollectionViewSectionItem` objects, which respond for configuration of specified section in + //// collection view. + /// - shouldReloadData: Set this parameter to true will invoke reloadData of the collection view. + /// - completion: Will be called after reload data is finished. + open func update(_ sectionItems: [CollectionViewSectionItem], shouldReloadData: Bool, completion: (() -> Void)?) { + UIView.animate(withDuration: 0, animations: { + self._sectionItems = sectionItems + self.registerSectionItems() + if shouldReloadData { + self.collectionView.reloadData() + } + }, completion: { _ in + completion?() + }) } // MARK: - Registration @@ -474,8 +484,8 @@ open class CollectionViewManager: NSObject { /// Special wrapper for more convenient collection view updates. private func perform(updates: (UICollectionView?) -> Void, completion: Completion?) { collectionView.performBatchUpdates({ [weak collectionView] in - updates(collectionView) - }, completion: completion) + updates(collectionView) + }, completion: completion) } /// Use this method to perform register and set indexes operations for all section items. From e8f0fd83cffa63e65b32eb341028c9b6779c9c1c Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 28 Sep 2018 15:38:40 +0600 Subject: [PATCH 50/54] Add print warnings and errors functions --- CollectionViewTools.xcodeproj/project.pbxproj | 12 ++++++++++++ Sources/Helpers/Print.swift | 17 +++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 Sources/Helpers/Print.swift diff --git a/CollectionViewTools.xcodeproj/project.pbxproj b/CollectionViewTools.xcodeproj/project.pbxproj index 5cb6474..0fd20c6 100644 --- a/CollectionViewTools.xcodeproj/project.pbxproj +++ b/CollectionViewTools.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ 0A3763731F62545A00A80613 /* CollectionViewCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763621F62545A00A80613 /* CollectionViewCellItem.swift */; }; 0A3763741F62545A00A80613 /* CollectionViewReusableViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763641F62545A00A80613 /* CollectionViewReusableViewItem.swift */; }; 0A3763751F62545A00A80613 /* CollectionViewSectionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A3763661F62545A00A80613 /* CollectionViewSectionItem.swift */; }; + 629A6091BFB23B9D95D9BF56 /* Print.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629A64DA578C05AC298ECFFD /* Print.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -55,6 +56,7 @@ 0A3763621F62545A00A80613 /* CollectionViewCellItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCellItem.swift; sourceTree = ""; }; 0A3763641F62545A00A80613 /* CollectionViewReusableViewItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewReusableViewItem.swift; sourceTree = ""; }; 0A3763661F62545A00A80613 /* CollectionViewSectionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewSectionItem.swift; sourceTree = ""; }; + 629A64DA578C05AC298ECFFD /* Print.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Print.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -113,6 +115,7 @@ 0A3763551F62545A00A80613 /* Extensions */, 0A37635D1F62545A00A80613 /* Items */, 0A37635F1F62545A00A80613 /* Protocols */, + 629A633A0A14B7B8D623A113 /* Helpers */, ); path = Sources; sourceTree = ""; @@ -181,6 +184,14 @@ path = SectionItem; sourceTree = ""; }; + 629A633A0A14B7B8D623A113 /* Helpers */ = { + isa = PBXGroup; + children = ( + 629A64DA578C05AC298ECFFD /* Print.swift */, + ); + path = Helpers; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -308,6 +319,7 @@ 0A3763671F62545A00A80613 /* ClosureWrapper.swift in Sources */, 0A37636E1F62545A00A80613 /* CollectionViewManager+UIScrollViewDelegate.swift in Sources */, 0A3763751F62545A00A80613 /* CollectionViewSectionItem.swift in Sources */, + 629A6091BFB23B9D95D9BF56 /* Print.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/Helpers/Print.swift b/Sources/Helpers/Print.swift new file mode 100644 index 0000000..928aac2 --- /dev/null +++ b/Sources/Helpers/Print.swift @@ -0,0 +1,17 @@ +// +// Copyright (c) 2018 Rosberry. All rights reserved. +// + +import Foundation + +internal func printWarning(_ message: String) { + print("⚠️ [COLLECTION VIEW TOOLS] [WARNING] \(message)") +} + +internal func printError(_ message: String) { + print("❌ [COLLECTION VIEW TOOLS] [ERROR] \(message)") +} + +internal func printContextWarning(_ message: String) { + printWarning("\(message)\nBe sure to use methods for update section items and cell items context variables correctly") +} From af8ff34a9b92b697ad62542608d515ecaacd9082 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Fri, 28 Sep 2018 15:43:09 +0600 Subject: [PATCH 51/54] Make context vairables optional according to @disabled comment --- .../CellItems/ImageCellItem.swift | 2 +- .../CellItems/TextCellItem.swift | 6 +- Sources/CollectionViewManager.swift | 65 +++++++++++++++---- .../CellItem/CollectionViewCellItem.swift | 17 ++--- .../CollectionViewSectionItem.swift | 16 ++--- 5 files changed, 74 insertions(+), 32 deletions(-) diff --git a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift index 1cbde62..b0ff9ad 100644 --- a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift @@ -39,7 +39,7 @@ final class ImageCellItem: CollectionViewCellItem { shift += shift / 2 } let ratio = image.size.width / image.size.height - let width = collectionView.bounds.width / 2 - shift + let width = collectionView?.bounds.width ?? UIScreen.main.bounds.width / 2 - shift return .init(width: width, height: width / ratio) } diff --git a/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift b/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift index d74b4ce..0fe683f 100644 --- a/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift @@ -29,7 +29,9 @@ final class TextCellItem: CollectionViewCellItem { func size() -> CGSize { let cell: Cell = type(of: self).sizeCell configure(cell) - let cellSize = cell.sizeThatFits(.init(width: collectionView.bounds.size.width, height: .greatestFiniteMagnitude)) - return .init(width: cellSize.width + 2 * 12, height: collectionView.bounds.height / 1.4) + let cellSize = cell.sizeThatFits(.init(width: collectionView?.bounds.size.width ?? UIScreen.main.bounds.width, + height: .greatestFiniteMagnitude)) + return .init(width: cellSize.width + 2 * 12, + height: collectionView?.bounds.height ?? 100 / 1.4) } } diff --git a/Sources/CollectionViewManager.swift b/Sources/CollectionViewManager.swift index 9eb281e..396b897 100644 --- a/Sources/CollectionViewManager.swift +++ b/Sources/CollectionViewManager.swift @@ -84,7 +84,7 @@ open class CollectionViewManager: NSObject { /// - cellItems: Cell items to reload /// - completion: A closure that either specifies any additional actions which should be performed after reloading. open func reloadCellItems(_ cellItems: [CellItem], completion: Completion? = nil) { - let indexPaths: [IndexPath] = cellItems.map { cellItem in + let indexPaths: [IndexPath] = cellItems.compactMap { cellItem in return cellItem.indexPath } perform(updates: { collectionView in @@ -102,7 +102,13 @@ open class CollectionViewManager: NSObject { /// scrolling concludes. See UICollectionViewScrollPosition for descriptions of valid constants. /// - animated: true if you want to animate the change in position; false if it should be immediate. open func scroll(to cellItem: CellItem, at scrollPosition: UICollectionView.ScrollPosition, animated: Bool = true) { - collectionView.scrollToItem(at: cellItem.indexPath, at: scrollPosition, animated: animated) + if let indexPath = cellItem.indexPath { + collectionView.scrollToItem(at: indexPath, at: scrollPosition, animated: animated) + } + else { + printContextWarning("It is impossible to scroll to cellItem \(cellItem)" + + "because indexPath isn't set") + } } // MARK: - Helpers @@ -162,7 +168,13 @@ open class CollectionViewManager: NSObject { open func register(_ sectionItem: CollectionViewSectionItem) { sectionItem.collectionView = collectionView sectionItem.cellItems.enumerated().forEach { (index, cellItem) in - cellItem.indexPath = .init(row: index, section: sectionItem.index) + if let sectionIndex = sectionItem.index { + cellItem.indexPath = IndexPath(row: index, section: sectionIndex) + } + else { + printContextWarning("It is impossible to setup indexPath to cellItem \(cellItem) " + + "because there is no index in sectionItem \(sectionItem)") + } cellItem.sectionItem = sectionItem register(cellItem) } @@ -195,7 +207,13 @@ open class CollectionViewManager: NSObject { /// - Parameter sectionItem: The section item with cell items needed to recalculate index paths. open func recalculateIndexPaths(in sectionItem: CollectionViewSectionItem) { sectionItem.cellItems.enumerated().forEach { index, cellItem in - cellItem.indexPath = .init(row: index, section: sectionItem.index) + if let sectionIndex = sectionItem.index { + cellItem.indexPath = IndexPath(row: index, section: sectionIndex) + } + else { + printContextWarning("It is impossible to setup indexPath to cellItem \(cellItem) " + + "because there is no index in sectionItem \(sectionItem)") + } } } @@ -205,7 +223,13 @@ open class CollectionViewManager: NSObject { _sectionItems.enumerated().forEach { index, sectionItem in sectionItem.index = index sectionItem.cellItems.enumerated().forEach { (index, cellItem) in - cellItem.indexPath = .init(row: index, section: sectionItem.index) + if let sectionIndex = sectionItem.index { + cellItem.indexPath = IndexPath(row: index, section: sectionIndex) + } + else { + printContextWarning("It is impossible to setup indexPath to cellItem \(cellItem) " + + "because there is no index in sectionItem \(sectionItem)") + } cellItem.sectionItem = sectionItem } } @@ -221,10 +245,14 @@ open class CollectionViewManager: NSObject { /// - sectionItem: Section item within which cell items should be set /// - completion: A closure that either specifies any additional actions which should be performed after setting. open func replaceAllCellItems(in sectionItem: SectionItem, with cellItems: [CellItem], completion: Completion? = nil) { + guard let sectionIndex = sectionItem.index else { + return + } + perform(updates: { collectionView in sectionItem.cellItems = cellItems register(sectionItem) - collectionView?.reloadSections([sectionItem.index]) + collectionView?.reloadSections([sectionIndex]) }, completion: completion) } @@ -245,7 +273,7 @@ open class CollectionViewManager: NSObject { } self?.recalculateIndexPaths(in: sectionItem) - let indexPaths: [IndexPath] = cellItems.map { cellItem in + let indexPaths: [IndexPath] = cellItems.compactMap { cellItem in return cellItem.indexPath } @@ -288,7 +316,11 @@ open class CollectionViewManager: NSObject { return } - let section = sectionItem.index + guard let section = sectionItem.index else { + printContextWarning("It is impossible to replace cell items in sectionItem \(sectionItem)" + + "because there is no index in it") + return + } perform(updates: { [weak self] collectionView in cellItems.forEach { cellItem in @@ -299,7 +331,7 @@ open class CollectionViewManager: NSObject { if indexes.count == cellItems.count { zip(cellItems, indexes).forEach { cellItem, index in sectionItem.cellItems[index] = cellItem - cellItem.indexPath = .init(row: index, section: sectionItem.index) + cellItem.indexPath = IndexPath(row: index, section: section) } let indexPaths: [IndexPath] = indexes.map { index in return .init(row: index, section: section) @@ -315,7 +347,7 @@ open class CollectionViewManager: NSObject { } let insertIndexPaths: [IndexPath] = Array(firstIndex.. Date: Tue, 2 Oct 2018 09:35:52 +0600 Subject: [PATCH 52/54] Add explicit weak for collection view property --- .../CollectionViewToolsExample/CellItems/ImageCellItem.swift | 2 +- Example/CollectionViewToolsExample/CellItems/TextCellItem.swift | 2 +- Sources/Protocols/CellItem/CollectionViewCellItem.swift | 2 +- Sources/Protocols/SectionItem/CollectionViewSectionItem.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift index b0ff9ad..b4f3937 100644 --- a/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/ImageCellItem.swift @@ -39,7 +39,7 @@ final class ImageCellItem: CollectionViewCellItem { shift += shift / 2 } let ratio = image.size.width / image.size.height - let width = collectionView?.bounds.width ?? UIScreen.main.bounds.width / 2 - shift + let width = (collectionView?.bounds.width ?? UIScreen.main.bounds.width) / 2 - shift return .init(width: width, height: width / ratio) } diff --git a/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift b/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift index 0fe683f..ae5ac98 100644 --- a/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift +++ b/Example/CollectionViewToolsExample/CellItems/TextCellItem.swift @@ -32,6 +32,6 @@ final class TextCellItem: CollectionViewCellItem { let cellSize = cell.sizeThatFits(.init(width: collectionView?.bounds.size.width ?? UIScreen.main.bounds.width, height: .greatestFiniteMagnitude)) return .init(width: cellSize.width + 2 * 12, - height: collectionView?.bounds.height ?? 100 / 1.4) + height: (collectionView?.bounds.height ?? 100) / 1.4) } } diff --git a/Sources/Protocols/CellItem/CollectionViewCellItem.swift b/Sources/Protocols/CellItem/CollectionViewCellItem.swift index 3a8a96f..7437a63 100644 --- a/Sources/Protocols/CellItem/CollectionViewCellItem.swift +++ b/Sources/Protocols/CellItem/CollectionViewCellItem.swift @@ -45,7 +45,7 @@ public protocol CollectionViewSiblingCellItem: AnyObject { } extension CollectionViewSiblingCellItem { - public var collectionView: UICollectionView? { + public weak var collectionView: UICollectionView? { get { if let object = objc_getAssociatedObject(self, &AssociatedKeys.collectionView) as? UICollectionView { return object diff --git a/Sources/Protocols/SectionItem/CollectionViewSectionItem.swift b/Sources/Protocols/SectionItem/CollectionViewSectionItem.swift index 742256f..0635320 100644 --- a/Sources/Protocols/SectionItem/CollectionViewSectionItem.swift +++ b/Sources/Protocols/SectionItem/CollectionViewSectionItem.swift @@ -24,7 +24,7 @@ public protocol CollectionViewSiblingSectionItem: AnyObject { } extension CollectionViewSiblingSectionItem { - public var collectionView: UICollectionView? { + public weak var collectionView: UICollectionView? { get { if let object = objc_getAssociatedObject(self, &AssociatedKeys.collectionView) as? UICollectionView { return object From f82b603ee0f667fee8c726a7e9ee6bbcb5920f3d Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Tue, 2 Oct 2018 09:48:39 +0600 Subject: [PATCH 53/54] Replace `.sorted().reversed()` with `sorted(by: >)` --- Sources/CollectionViewManager.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/CollectionViewManager.swift b/Sources/CollectionViewManager.swift index 396b897..2a9022d 100644 --- a/Sources/CollectionViewManager.swift +++ b/Sources/CollectionViewManager.swift @@ -341,7 +341,7 @@ open class CollectionViewManager: NSObject { else { var removeIndexPaths: [IndexPath] = [] let firstIndex = indexes[0] - indexes.sorted().reversed().forEach { index in + indexes.sorted(by: >).forEach { index in sectionItem.cellItems.remove(at: index) removeIndexPaths.append(.init(row: index, section: section)) } @@ -368,7 +368,7 @@ open class CollectionViewManager: NSObject { let indexPaths = cellItems.compactMap { cellItem in return cellItem.indexPath } - indexPaths.sorted().reversed().forEach { indexPath in + indexPaths.sorted(by: >).forEach { indexPath in _sectionItems[indexPath.section].cellItems.remove(at: indexPath.row) } @@ -397,7 +397,7 @@ open class CollectionViewManager: NSObject { } } - indexes.sorted().reversed().forEach { index in + indexes.sorted(by: >).forEach { index in sectionItem.cellItems.remove(at: index) } @@ -473,7 +473,7 @@ open class CollectionViewManager: NSObject { } else { let firstIndex = indexes[0] - indexes.sorted().reversed().forEach { index in + indexes.sorted(by: >).forEach { index in _sectionItems.remove(at: index) } From 6e6de5116562c86850bbbf5f16f71a0347bf7dd7 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Tue, 2 Oct 2018 10:11:55 +0600 Subject: [PATCH 54/54] Replace forEach with for cycle --- Sources/CollectionViewManager.swift | 60 +++++++++++++++-------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/Sources/CollectionViewManager.swift b/Sources/CollectionViewManager.swift index 2a9022d..783ec12 100644 --- a/Sources/CollectionViewManager.swift +++ b/Sources/CollectionViewManager.swift @@ -167,7 +167,8 @@ open class CollectionViewManager: NSObject { /// - Parameter sectionItem: The section item with cell items which need to be registered open func register(_ sectionItem: CollectionViewSectionItem) { sectionItem.collectionView = collectionView - sectionItem.cellItems.enumerated().forEach { (index, cellItem) in + for index in 0..).forEach { index in + for index in indexes.sorted(by: >) { sectionItem.cellItems.remove(at: index) removeIndexPaths.append(.init(row: index, section: section)) } @@ -368,7 +369,7 @@ open class CollectionViewManager: NSObject { let indexPaths = cellItems.compactMap { cellItem in return cellItem.indexPath } - indexPaths.sorted(by: >).forEach { indexPath in + for indexPath in indexPaths.sorted(by: >) { _sectionItems[indexPath.section].cellItems.remove(at: indexPath.row) } @@ -397,7 +398,7 @@ open class CollectionViewManager: NSObject { } } - indexes.sorted(by: >).forEach { index in + for index in indexes.sorted(by: >) { sectionItem.cellItems.remove(at: index) } @@ -417,11 +418,12 @@ open class CollectionViewManager: NSObject { /// - completion: A closure that either specifies any additional actions which should be performed after insertion. open func insert(_ sectionItems: [CollectionViewSectionItem], at indexes: [Int], completion: Completion? = nil) { perform(updates: { [weak self] collectionView in - zip(sectionItems, indexes).forEach { sectionItem, index in + for (sectionItem, index) in zip(sectionItems, indexes) { _sectionItems.insert(sectionItem, at: index) } self?.recalculateIndexes() - sectionItems.forEach { sectionItem in + for section in 0..).forEach { index in + for index in indexes.sorted(by: >) { _sectionItems.remove(at: index) } _sectionItems.insert(contentsOf: sectionItems, at: firstIndex) self?.recalculateIndexes() - sectionItems.forEach { sectionItem in + for index in 0..