diff --git a/Parchment.xcodeproj/project.pbxproj b/Parchment.xcodeproj/project.pbxproj index c2c18eb1..36334278 100644 --- a/Parchment.xcodeproj/project.pbxproj +++ b/Parchment.xcodeproj/project.pbxproj @@ -81,6 +81,7 @@ 95591F21222C3A0400677B4B /* PagingControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95591F20222C3A0400677B4B /* PagingControllerTests.swift */; }; 95591F23222C522800677B4B /* PagingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95591F22222C522800677B4B /* PagingController.swift */; }; 9568922B222C525C00AFF250 /* CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9568922A222C525C00AFF250 /* CollectionView.swift */; }; + 956EBE5E248BC426003ED4BA /* PagingCollectionViewLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 956EBE5D248BC426003ED4BA /* PagingCollectionViewLayoutTests.swift */; }; 9575BEB32461FEF9002403F6 /* CreateDistance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9575BEB22461FEF9002403F6 /* CreateDistance.swift */; }; 9575BEB52462034B002403F6 /* PagingDistanceRightTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9575BEB42462034B002403F6 /* PagingDistanceRightTests.swift */; }; 9575BEB72463490B002403F6 /* PagingDistanceCenteredTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9575BEB62463490B002403F6 /* PagingDistanceCenteredTests.swift */; }; @@ -270,6 +271,7 @@ 95591F20222C3A0400677B4B /* PagingControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagingControllerTests.swift; sourceTree = ""; }; 95591F22222C522800677B4B /* PagingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagingController.swift; sourceTree = ""; }; 9568922A222C525C00AFF250 /* CollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionView.swift; sourceTree = ""; }; + 956EBE5D248BC426003ED4BA /* PagingCollectionViewLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagingCollectionViewLayoutTests.swift; sourceTree = ""; }; 9575BEB22461FEF9002403F6 /* CreateDistance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateDistance.swift; sourceTree = ""; }; 9575BEB42462034B002403F6 /* PagingDistanceRightTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagingDistanceRightTests.swift; sourceTree = ""; }; 9575BEB62463490B002403F6 /* PagingDistanceCenteredTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagingDistanceCenteredTests.swift; sourceTree = ""; }; @@ -439,6 +441,7 @@ 3E504EC81C7465B000AE1CE3 /* Info.plist */, 954E7DEB1F48AE1300342ECF /* Item.swift */, 95FEEA4424215FCA009B5B64 /* PageViewManagerTests.swift */, + 956EBE5D248BC426003ED4BA /* PagingCollectionViewLayoutTests.swift */, 95591F20222C3A0400677B4B /* PagingControllerTests.swift */, 3E5E93E81CE7E093000762A1 /* PagingDataStructureTests.swift */, 954842621F4252070072038C /* PagingDiffTests.swift */, @@ -958,6 +961,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 956EBE5E248BC426003ED4BA /* PagingCollectionViewLayoutTests.swift in Sources */, 9575BEB52462034B002403F6 /* PagingDistanceRightTests.swift in Sources */, 95FEEA512423F752009B5B64 /* MockPageViewManagerDataSource.swift in Sources */, 95D78FE1228715E100E6EE7C /* MockPagingControllerDataSource.swift in Sources */, diff --git a/Parchment/Classes/PagingCollectionViewLayout.swift b/Parchment/Classes/PagingCollectionViewLayout.swift index 792eb846..396af013 100644 --- a/Parchment/Classes/PagingCollectionViewLayout.swift +++ b/Parchment/Classes/PagingCollectionViewLayout.swift @@ -108,9 +108,9 @@ open class PagingCollectionViewLayout: UICollectionViewLayout, PagingLayout { } } - /// Cache used to store the preferred item width for each - /// self-sizing cell. PagingItem identifier is used as the key. - private var widthCache: [Int: CGFloat] = [:] + /// Cache used to store the preferred item size for each self-sizing + /// cell. PagingItem identifier is used as the key. + private var preferredSizeCache: [Int: CGFloat] = [:] private(set) var contentInsets: UIEdgeInsets = .zero private var contentSize: CGSize = .zero @@ -161,12 +161,12 @@ open class PagingCollectionViewLayout: UICollectionViewLayout, PagingLayout { override open func shouldInvalidateLayout(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> Bool { switch options.menuItemSize { // Invalidate the layout and update the layout attributes with the - // preferred width for each cell. The preferred width is based on + // preferred width for each cell. The preferred size is based on // the layout constraints in each cell. case .selfSizing where originalAttributes is PagingCellLayoutAttributes: if preferredAttributes.frame.width != originalAttributes.frame.width { let pagingItem = visibleItems.pagingItem(for: originalAttributes.indexPath) - widthCache[pagingItem.identifier] = preferredAttributes.frame.width + preferredSizeCache[pagingItem.identifier] = preferredAttributes.frame.width return true } return false @@ -276,8 +276,8 @@ open class PagingCollectionViewLayout: UICollectionViewLayout, PagingLayout { let y = adjustedMenuInsets.top let pagingItem = visibleItems.pagingItem(for: indexPath) - if sizeCache.implementsWidthDelegate { - var width = sizeCache.itemWidth(for: pagingItem) + if sizeCache.implementsSizeDelegate { + var width = sizeCache.itemSize(for: pagingItem) let selectedWidth = sizeCache.itemWidthSelected(for: pagingItem) if let currentPagingItem = state.currentPagingItem, currentPagingItem.isEqual(to: pagingItem) { @@ -294,7 +294,7 @@ open class PagingCollectionViewLayout: UICollectionViewLayout, PagingLayout { case let .sizeToFit(minWidth, height): attributes.frame = CGRect(x: x, y: y, width: minWidth, height: height) case let .selfSizing(estimatedWidth, height): - if let actualWidth = widthCache[pagingItem.identifier] { + if let actualWidth = preferredSizeCache[pagingItem.identifier] { attributes.frame = CGRect(x: x, y: y, width: actualWidth, height: height) } else { attributes.frame = CGRect(x: x, y: y, width: estimatedWidth, height: height) @@ -311,7 +311,7 @@ open class PagingCollectionViewLayout: UICollectionViewLayout, PagingLayout { if previousFrame.maxX - adjustedMenuInsets.left < view.bounds.width { switch (options.menuItemSize) { - case let .sizeToFit(_, height) where sizeCache.implementsWidthDelegate == false: + case let .sizeToFit(_, height) where sizeCache.implementsSizeDelegate == false: let insets = adjustedMenuInsets.left + adjustedMenuInsets.right let spacing = (options.menuItemSpacing * CGFloat(range.upperBound - 1)) let width = (view.bounds.width - insets - spacing) / CGFloat(range.upperBound) @@ -531,7 +531,7 @@ open class PagingCollectionViewLayout: UICollectionViewLayout, PagingLayout { y: attributes.center.y - attributes.bounds.midY, width: attributes.bounds.width, height: attributes.bounds.height) - if sizeCache.implementsWidthDelegate { + if sizeCache.implementsSizeDelegate { let indexPath = IndexPath(item: index, section: 0) let pagingItem = visibleItems.pagingItem(for: indexPath) diff --git a/Parchment/Classes/PagingController.swift b/Parchment/Classes/PagingController.swift index a712aeeb..fe9c3a93 100644 --- a/Parchment/Classes/PagingController.swift +++ b/Parchment/Classes/PagingController.swift @@ -450,7 +450,7 @@ final class PagingController: NSObject { collectionView.setContentOffset(contentOffset, animated: false) } - if sizeCache.implementsWidthDelegate { + if sizeCache.implementsSizeDelegate { invalidationContext.invalidateSizes = true } } @@ -629,14 +629,14 @@ final class PagingController: NSObject { if currentPagingItem.isEqual(to: pagingItem) { return sizeCache.itemWidthSelected(for: pagingItem) } else { - return sizeCache.itemWidth(for: pagingItem) + return sizeCache.itemSize(for: pagingItem) } } private func configureSizeCache(for pagingItem: PagingItem) { if sizeDelegate != nil { - sizeCache.implementsWidthDelegate = true - sizeCache.widthForPagingItem = { [weak self] item, selected in + sizeCache.implementsSizeDelegate = true + sizeCache.sizeForPagingItem = { [weak self] item, selected in return self?.sizeDelegate?.width(for: item, isSelected: selected) } } diff --git a/Parchment/Classes/PagingSizeCache.swift b/Parchment/Classes/PagingSizeCache.swift index f016c341..ff4caa48 100644 --- a/Parchment/Classes/PagingSizeCache.swift +++ b/Parchment/Classes/PagingSizeCache.swift @@ -4,11 +4,11 @@ import UIKit class PagingSizeCache { var options: PagingOptions - var implementsWidthDelegate: Bool = false - var widthForPagingItem: ((PagingItem, Bool) -> CGFloat?)? + var implementsSizeDelegate: Bool = false + var sizeForPagingItem: ((PagingItem, Bool) -> CGFloat?)? - private var widthCache: [Int: CGFloat] = [:] - private var selectedWidthCache: [Int: CGFloat] = [:] + private var sizeCache: [Int: CGFloat] = [:] + private var selectedSizeCache: [Int: CGFloat] = [:] init(options: PagingOptions) { self.options = options @@ -37,27 +37,27 @@ class PagingSizeCache { } func clear() { - self.widthCache = [:] - self.selectedWidthCache = [:] + self.sizeCache = [:] + self.selectedSizeCache = [:] } - func itemWidth(for pagingItem: PagingItem) -> CGFloat { - if let width = widthCache[pagingItem.identifier] { - return width + func itemSize(for pagingItem: PagingItem) -> CGFloat { + if let size = sizeCache[pagingItem.identifier] { + return size } else { - let width = widthForPagingItem?(pagingItem, false) - widthCache[pagingItem.identifier] = width - return width ?? options.estimatedItemWidth + let size = sizeForPagingItem?(pagingItem, false) + sizeCache[pagingItem.identifier] = size + return size ?? options.estimatedItemWidth } } func itemWidthSelected(for pagingItem: PagingItem) -> CGFloat { - if let width = selectedWidthCache[pagingItem.identifier] { - return width + if let size = selectedSizeCache[pagingItem.identifier] { + return size } else { - let width = widthForPagingItem?(pagingItem, true) - selectedWidthCache[pagingItem.identifier] = width - return width ?? options.estimatedItemWidth + let size = sizeForPagingItem?(pagingItem, true) + selectedSizeCache[pagingItem.identifier] = size + return size ?? options.estimatedItemWidth } } diff --git a/Parchment/Structs/PagingDistance.swift b/Parchment/Structs/PagingDistance.swift index 4c2cd69b..a11c7059 100644 --- a/Parchment/Structs/PagingDistance.swift +++ b/Parchment/Structs/PagingDistance.swift @@ -147,12 +147,12 @@ struct PagingDistance { let originalDistance = distance distance = contentSize - (contentOffset + viewSize) - if sizeCache.implementsWidthDelegate { + if sizeCache.implementsSizeDelegate { let toWidth = sizeCache.itemWidthSelected(for: toItem) distance += toWidth - toSize if let _ = fromAttributes { - let fromWidth = sizeCache.itemWidth(for: fromItem) + let fromWidth = sizeCache.itemSize(for: fromItem) distance -= fromSize - fromWidth } @@ -183,10 +183,10 @@ struct PagingDistance { // backwards or the current item is scrolled out of view the // difference doesn't matter as the change in frame of the current // item won't have any affect on the position of the upcoming item. - if sizeCache.implementsWidthDelegate { + if sizeCache.implementsSizeDelegate { if let _ = fromAttributes { if fromItem.isBefore(item: toItem) { - let fromWidth = sizeCache.itemWidth(for: fromItem) + let fromWidth = sizeCache.itemSize(for: fromItem) let fromDiff = fromSize - fromWidth distance -= fromDiff } @@ -202,7 +202,7 @@ struct PagingDistance { let width = contentOffset + viewSize var distance = currentPosition - width - if sizeCache.implementsWidthDelegate { + if sizeCache.implementsSizeDelegate { let toWidth = sizeCache.itemWidthSelected(for: toItem) // If we have layout attributes for the current item it means @@ -214,7 +214,7 @@ struct PagingDistance { let toDiff = toWidth - toSize distance += toDiff } else { - let fromWidth = sizeCache.itemWidth(for: fromItem) + let fromWidth = sizeCache.itemSize(for: fromItem) let fromDiff = fromSize - fromWidth let toDiff = toWidth - toSize distance -= fromDiff @@ -240,9 +240,9 @@ struct PagingDistance { let distanceBetweenCells = toCenter - fromCenter distance = distanceBetweenCells - distanceToCenter - if sizeCache.implementsWidthDelegate { + if sizeCache.implementsSizeDelegate { let toWidth = sizeCache.itemWidthSelected(for: toItem) - let fromWidth = sizeCache.itemWidth(for: fromItem) + let fromWidth = sizeCache.itemSize(for: fromItem) if toItem.isBefore(item: fromItem) { distance = -(toSize + (fromCenter - (toCenter + (toSize / 2))) - (toWidth / 2)) - distanceToCenter @@ -251,7 +251,7 @@ struct PagingDistance { distance = fromWidth + (toCenter - (fromCenter + (fromSize / 2))) + toDiff - (fromSize / 2) - distanceToCenter } } - } else if sizeCache.implementsWidthDelegate { + } else if sizeCache.implementsSizeDelegate { let toWidth = sizeCache.itemWidthSelected(for: toItem) let toDiff = toWidth - toSize distance += toDiff / 2 diff --git a/ParchmentTests/PagingCollectionViewLayoutTests.swift b/ParchmentTests/PagingCollectionViewLayoutTests.swift new file mode 100644 index 00000000..3631e2b8 --- /dev/null +++ b/ParchmentTests/PagingCollectionViewLayoutTests.swift @@ -0,0 +1,364 @@ +import UIKit +import XCTest +@testable import Parchment + +final class PagingCollectionViewLayoutTests: XCTestCase { + private var window: UIWindow! + private var options: PagingOptions! + private var dataSource: DataSource! + private var sizeCache: PagingSizeCache! + private var layout: PagingCollectionViewLayout! + private var collectionView: UICollectionView! + + override func setUp() { + options = PagingOptions() + sizeCache = PagingSizeCache(options: options) + } + + // MARK: - Cell Frames + + func testCellFramesForItemSizeFixed() { + options.menuItemSize = .fixed(width: 100, height: 50) + setupLayout() + let frames = sortedCellFrames() + + XCTAssertEqual(frames, [ + CGRect(x: 0, y: 0, width: 100, height: 50), + CGRect(x: 100, y: 0, width: 100, height: 50), + CGRect(x: 200, y: 0, width: 100, height: 50), + ]) + } + + func testCellFramesForItemSizeToFit() { + options.menuItemSize = .sizeToFit(minWidth: 10, height: 50) + options.menuHorizontalAlignment = .center + setupLayout() + let frames = sortedCellFrames() + let expectedWidth = UIScreen.main.bounds.width / 3 + + XCTAssertEqual(frames, [ + CGRect(x: 0, y: 0, width: expectedWidth, height: 50), + CGRect(x: expectedWidth, y: 0, width: expectedWidth, height: 50), + CGRect(x: expectedWidth * 2, y: 0, width: expectedWidth, height: 50), + ]) + } + + func testCellFramesForItemSizeToFitWhenMinWidthExtendsOutside() { + let minWidth = UIScreen.main.bounds.width + options.menuItemSize = .sizeToFit(minWidth: minWidth, height: 50) + options.menuHorizontalAlignment = .center + setupLayout() + let frames = sortedCellFrames() + + XCTAssertEqual(frames, [ + CGRect(x: 0, y: 0, width: minWidth, height: 50), + CGRect(x: minWidth, y: 0, width: minWidth, height: 50), + CGRect(x: minWidth * 2, y: 0, width: minWidth, height: 50), + ]) + } + + func testCellFramesForItemSizeToFitWhenUsingSizeDelegate() { + sizeCache.implementsSizeDelegate = true + sizeCache.sizeForPagingItem = { item, isSelected in + return 50 + } + setupLayout() + options.menuItemSize = .sizeToFit(minWidth: 10, height: 50) + setupLayout() + let frames = sortedCellFrames() + + // Expects it to use the size delegate width and not size the + // cells to match the bounds. + XCTAssertEqual(frames, [ + CGRect(x: 0, y: 0, width: 50, height: 50), + CGRect(x: 50, y: 0, width: 50, height: 50), + CGRect(x: 100, y: 0, width: 50, height: 50), + ]) + } + + func testCellFramesForItemSizeSelfSizing() { + options.menuItemSize = .selfSizing(estimatedWidth: 0, height: 50) + setupLayout() + let frames = sortedCellFrames() + + XCTAssertEqual(frames, [ + CGRect(x: 0, y: 0, width: 50, height: 50), + CGRect(x: 50, y: 0, width: 100, height: 50), + CGRect(x: 150, y: 0, width: 150, height: 50), + ]) + } + + func testCellFramesForSizeDelegate() { + options.menuItemSize = .fixed(width: 0, height: 50) + sizeCache.implementsSizeDelegate = true + sizeCache.sizeForPagingItem = { item, isSelected in + if isSelected { + return 100 + } else { + return 50 + } + } + setupLayout() + + let frames = sortedCellFrames() + + XCTAssertEqual(frames, [ + CGRect(x: 0, y: 0, width: 100, height: 50), + CGRect(x: 100, y: 0, width: 50, height: 50), + CGRect(x: 150, y: 0, width: 50, height: 50), + ]) + } + + func testCellFramesForSizeDelegateWhenScrollingToItem() { + options.menuItemSize = .fixed(width: 0, height: 50) + sizeCache.implementsSizeDelegate = true + sizeCache.sizeForPagingItem = { item, isSelected in + if isSelected { + return 100 + } else { + return 50 + } + } + setupLayout() + layout.state = .scrolling( + pagingItem: Item(index: 0), + upcomingPagingItem: Item(index: 1), + progress: 0.5, + initialContentOffset: .zero, + distance: 50 + ) + layout.invalidateLayout() + collectionView.layoutIfNeeded() + + let frames = sortedCellFrames() + + XCTAssertEqual(frames, [ + CGRect(x: 0, y: 0, width: 75, height: 50), + CGRect(x: 75, y: 0, width: 75, height: 50), + CGRect(x: 150, y: 0, width: 50, height: 50), + ]) + } + + func testCellFramesForHorizontalMenuAlignment() { + options.menuItemSize = .fixed(width: 10, height: 50) + options.menuHorizontalAlignment = .center + setupLayout() + + let frames = sortedCellFrames() + let expectedInsets = (UIScreen.main.bounds.width - 30) / 2 + + XCTAssertEqual(frames, [ + CGRect(x: expectedInsets, y: 0, width: 10, height: 50), + CGRect(x: 10 + expectedInsets, y: 0, width: 10, height: 50), + CGRect(x: 20 + expectedInsets, y: 0, width: 10, height: 50), + ]) + } + + func testCellFramesForSelectedScrollPositionCentered() { + let expectedWidth = UIScreen.main.bounds.width / 2 + options.menuItemSize = .fixed(width: expectedWidth, height: 50) + options.selectedScrollPosition = .center + setupLayout() + + let frames = sortedCellFrames() + let expectedInsets = (UIScreen.main.bounds.width / 2) - (expectedWidth / 2) + + XCTAssertEqual(frames, [ + CGRect(x: expectedInsets, y: 0, width: expectedWidth, height: 50), + CGRect(x: expectedInsets + expectedWidth, y: 0, width: expectedWidth, height: 50), + CGRect(x: expectedInsets + expectedWidth * 2, y: 0, width: expectedWidth, height: 50), + ]) + } + + // MARK: - Indicator Frame + + func testIndicatorFrame() { + options.menuItemSize = .fixed(width: 100, height: 50) + options.indicatorOptions = .visible(height: 10, zIndex: Int.max, spacing: .zero, insets: .zero) + setupLayout() + + layout.state = .selected(pagingItem: Item(index: 1)) + layout.invalidateLayout() + collectionView.layoutIfNeeded() + + let frame = indicatorFrame() + + XCTAssertEqual(frame, CGRect(x: 100, y: 40, width: 100, height: 10)) + } + + func testIndicatorFrameWithInsets() { + let insets = UIEdgeInsets(top: 0, left: 20, bottom: 20, right: 20) + options.menuItemSize = .fixed(width: 100, height: 50) + options.indicatorOptions = .visible(height: 10, zIndex: Int.max, spacing: .zero, insets: insets) + setupLayout() + + layout.state = .selected(pagingItem: Item(index: 0)) + layout.invalidateLayout() + collectionView.layoutIfNeeded() + + let frame = indicatorFrame() + + XCTAssertEqual(frame, CGRect(x: 20, y: 20, width: 80, height: 10)) + } + + func testIndicatorFrameWithSpacing() { + let spacing = UIEdgeInsets(top: 0, left: 20, bottom: 20, right: 20) + options.menuItemSize = .fixed(width: 100, height: 50) + options.indicatorOptions = .visible(height: 10, zIndex: Int.max, spacing: spacing, insets: .zero) + setupLayout() + + layout.state = .selected(pagingItem: Item(index: 0)) + layout.invalidateLayout() + collectionView.layoutIfNeeded() + + let frame = indicatorFrame() + + XCTAssertEqual(frame, CGRect(x: 20, y: 40, width: 60, height: 10)) + } + + func testIndicatorFrameOutsideFirstItem() { + options.menuItemSize = .fixed(width: 100, height: 50) + options.indicatorOptions = .visible(height: 10, zIndex: Int.max, spacing: .zero, insets: .zero) + setupLayout() + + layout.state = .scrolling( + pagingItem: Item(index: 0), + upcomingPagingItem: nil, + progress: -1, + initialContentOffset: .zero, + distance: 0 + ) + layout.invalidateLayout() + collectionView.layoutIfNeeded() + + let frame = indicatorFrame() + + XCTAssertEqual(frame, CGRect(x: -100, y: 40, width: 100, height: 10)) + } + + func testIndicatorFrameOutsideLastItem() { + options.menuItemSize = .fixed(width: 100, height: 50) + options.indicatorOptions = .visible(height: 10, zIndex: Int.max, spacing: .zero, insets: .zero) + setupLayout() + + layout.state = .scrolling( + pagingItem: Item(index: 3), + upcomingPagingItem: nil, + progress: 1, + initialContentOffset: .zero, + distance: 0 + ) + layout.invalidateLayout() + collectionView.layoutIfNeeded() + + let frame = indicatorFrame() + + XCTAssertEqual(frame, CGRect(x: 300, y: 40, width: 100, height: 10)) + } + + // MARK: - Border Frame + + func testBorderFrame() { + options.menuItemSize = .fixed(width: 100, height: 50) + options.borderOptions = .visible(height: 10, zIndex: Int.max, insets: .zero) + setupLayout() + + let frame = borderFrame() + let expectedWidth = UIScreen.main.bounds.width + + XCTAssertEqual(frame, CGRect(x: 0, y: 40, width:expectedWidth, height: 10)) + } + + func testBorderFrameWithInsets() { + let insets = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20) + options.menuItemSize = .fixed(width: 100, height: 50) + options.borderOptions = .visible(height: 10, zIndex: Int.max, insets: insets) + setupLayout() + + let frame = borderFrame() + let expectedWidth = UIScreen.main.bounds.width - insets.left - insets.right + + XCTAssertEqual(frame, CGRect(x: insets.left, y: 40, width:expectedWidth, height: 10)) + } + + // MARK: - Private + + private func borderFrame() -> CGRect? { + let layoutAttributes = layout.layoutAttributesForElements(in: collectionView.bounds) ?? [] + return layoutAttributes + .filter { $0 is PagingBorderLayoutAttributes } + .map { $0.frame } + .first + } + + private func indicatorFrame() -> CGRect? { + let layoutAttributes = layout.layoutAttributesForElements(in: collectionView.bounds) ?? [] + return layoutAttributes + .filter { $0 is PagingIndicatorLayoutAttributes } + .map { $0.frame } + .first + } + + private func sortedCellFrames() -> [CGRect] { + let layoutAttributes = layout.layoutAttributesForElements(in: collectionView.bounds) ?? [] + return layoutAttributes + .filter { $0 is PagingCellLayoutAttributes } + .sorted { $0.indexPath < $1.indexPath } + .map { $0.frame } + } + + private func setupLayout() { + layout = PagingCollectionViewLayout() + layout.options = options + layout.sizeCache = sizeCache + layout.state = .selected(pagingItem: Item(index: 0)) + layout.visibleItems = PagingItems(items: [ + Item(index: 0), + Item(index: 1), + Item(index: 2) + ]) + + dataSource = DataSource() + collectionView = UICollectionView( + frame: UIScreen.main.bounds, + collectionViewLayout: layout) + collectionView.dataSource = dataSource + collectionView.register( + Cell.self, + forCellWithReuseIdentifier: DataSource.CellIdentifier) + + window = UIWindow(frame: UIScreen.main.bounds) + window.addSubview(collectionView) + window.makeKeyAndVisible() + + // Trigger a layout invalidation by calling layoutIfNeeded + collectionView.layoutIfNeeded() + } +} + +private final class DataSource: NSObject, UICollectionViewDataSource { + static let CellIdentifier = "CellIdentifier" + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + return collectionView.dequeueReusableCell( + withReuseIdentifier: Self.CellIdentifier, + for: indexPath) + } + + func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return 3 + } +} + +private final class Cell: UICollectionViewCell { + override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { + var frame = layoutAttributes.frame + frame.size.width = CGFloat((layoutAttributes.indexPath.item + 1) * 50) + layoutAttributes.frame = frame + return layoutAttributes + } +} diff --git a/ParchmentTests/PagingDistanceCenteredTests.swift b/ParchmentTests/PagingDistanceCenteredTests.swift index 3a7be1cb..e6c50154 100644 --- a/ParchmentTests/PagingDistanceCenteredTests.swift +++ b/ParchmentTests/PagingDistanceCenteredTests.swift @@ -97,8 +97,8 @@ final class PagingDistanceCenteredTests: XCTestCase { /// x: 100 /// ``` func testDistanceCenteredUsingSizeDelegateScrollingForward() { - sizeCache.implementsWidthDelegate = true - sizeCache.widthForPagingItem = { item, isSelected in + sizeCache.implementsSizeDelegate = true + sizeCache.sizeForPagingItem = { item, isSelected in if isSelected { return 100 } else { @@ -132,8 +132,8 @@ final class PagingDistanceCenteredTests: XCTestCase { /// x: 100 /// ``` func testDistanceCenteredUsingSizeDelegateScrollingBackward() { - sizeCache.implementsWidthDelegate = true - sizeCache.widthForPagingItem = { item, isSelected in + sizeCache.implementsSizeDelegate = true + sizeCache.sizeForPagingItem = { item, isSelected in if isSelected { return 100 } else { @@ -168,8 +168,8 @@ final class PagingDistanceCenteredTests: XCTestCase { /// x: 200 /// ``` func testDistanceCenteredUsingSizeDelegateWithoutFromAttributes() { - sizeCache.implementsWidthDelegate = true - sizeCache.widthForPagingItem = { item, isSelected in + sizeCache.implementsSizeDelegate = true + sizeCache.sizeForPagingItem = { item, isSelected in if isSelected { return 100 } else { @@ -230,8 +230,8 @@ final class PagingDistanceCenteredTests: XCTestCase { /// x: 0 /// ``` func testDistanceCenteredToLeadingEdgeWhenUsingSizeDelegate() { - sizeCache.implementsWidthDelegate = true - sizeCache.widthForPagingItem = { item, isSelected in + sizeCache.implementsSizeDelegate = true + sizeCache.sizeForPagingItem = { item, isSelected in if isSelected { return 100 } else { @@ -292,8 +292,8 @@ final class PagingDistanceCenteredTests: XCTestCase { /// x: 600 /// ``` func testDistanceCenteredToTrailingEdgeWhenUsingSizeDelegate() { - sizeCache.implementsWidthDelegate = true - sizeCache.widthForPagingItem = { item, isSelected in + sizeCache.implementsSizeDelegate = true + sizeCache.sizeForPagingItem = { item, isSelected in if isSelected { return 100 } else { @@ -329,8 +329,8 @@ final class PagingDistanceCenteredTests: XCTestCase { /// x: 600 /// ``` func testDistanceCenteredToTrailingEdgeWhenUsingSizeDelegateWithHugeSelectedWidth() { - sizeCache.implementsWidthDelegate = true - sizeCache.widthForPagingItem = { item, isSelected in + sizeCache.implementsSizeDelegate = true + sizeCache.sizeForPagingItem = { item, isSelected in if isSelected { return 500 } else { diff --git a/ParchmentTests/PagingDistanceLeftTests.swift b/ParchmentTests/PagingDistanceLeftTests.swift index c4132643..4ce915d5 100644 --- a/ParchmentTests/PagingDistanceLeftTests.swift +++ b/ParchmentTests/PagingDistanceLeftTests.swift @@ -94,8 +94,8 @@ final class PagingDistanceLeftTests: XCTestCase { /// x: 500 /// ``` func testDistanceLeftUsingSizeDelegateScrollingForward() { - sizeCache.implementsWidthDelegate = true - sizeCache.widthForPagingItem = { item, isSelected in + sizeCache.implementsSizeDelegate = true + sizeCache.sizeForPagingItem = { item, isSelected in if isSelected { return 100 } else { @@ -130,8 +130,8 @@ final class PagingDistanceLeftTests: XCTestCase { /// x: 500 /// ``` func testDistanceLeftUsingSizeDelegateScrollingBackward() { - sizeCache.implementsWidthDelegate = true - sizeCache.widthForPagingItem = { item, isSelected in + sizeCache.implementsSizeDelegate = true + sizeCache.sizeForPagingItem = { item, isSelected in // Expects it to ignore this value when scrolling backwards so // setting it the a big number to notice if it's being used. return 1000 diff --git a/ParchmentTests/PagingDistanceRightTests.swift b/ParchmentTests/PagingDistanceRightTests.swift index c2de1ca6..57510484 100644 --- a/ParchmentTests/PagingDistanceRightTests.swift +++ b/ParchmentTests/PagingDistanceRightTests.swift @@ -98,8 +98,8 @@ final class PagingDistanceRightTests: XCTestCase { /// x: 0 /// ``` func testDistanceRightUsingSizeDelegateScrollingForward() { - sizeCache.implementsWidthDelegate = true - sizeCache.widthForPagingItem = { item, isSelected in + sizeCache.implementsSizeDelegate = true + sizeCache.sizeForPagingItem = { item, isSelected in if isSelected { return 100 } else { @@ -134,8 +134,8 @@ final class PagingDistanceRightTests: XCTestCase { /// x: 200 /// ``` func testDistanceRightUsingSizeDelegateScrollingBackward() { - sizeCache.implementsWidthDelegate = true - sizeCache.widthForPagingItem = { item, isSelected in + sizeCache.implementsSizeDelegate = true + sizeCache.sizeForPagingItem = { item, isSelected in if isSelected { return 100 } else { @@ -170,8 +170,8 @@ final class PagingDistanceRightTests: XCTestCase { /// x: 200 /// ``` func testDistanceRightUsingSizeDelegateWithoutFromAttributes() { - sizeCache.implementsWidthDelegate = true - sizeCache.widthForPagingItem = { item, isSelected in + sizeCache.implementsSizeDelegate = true + sizeCache.sizeForPagingItem = { item, isSelected in if isSelected { return 100 } else {