From 9924c6f67f7c60c66b6dba18f24e27604b5b3bcd Mon Sep 17 00:00:00 2001 From: Ruben Cagnie Date: Thu, 6 Jul 2017 22:50:34 -0400 Subject: [PATCH] Support for ordering bricks within a section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added support to specify which brick is repeated at what index for a section. Say a section has 2 bricks, that are each repeated 5 times. If the `orderDataSource` is set, the implementor needs to specify based on the actual index (IndexPath’s item), which brick and index needs to be returned. This gives full control to the implementor to define the order of the bricks Fixes #139 --- BrickKit.xcodeproj/project.pbxproj | 24 +- .../project.pbxproj | 6 + .../Source/Examples/BaseBrickController.swift | 13 +- .../ReorderableBrickViewController.swift | 149 ++++++++ .../Navigation/NavigationDataSource.swift | 1 + .../Models/{BrickModels.swift => Brick.swift} | 104 ------ Source/Models/BrickSection.swift | 132 +++++++ Source/Models/BrickSectionDataSource.swift | 18 +- .../ViewControllers/BrickCollectionView.swift | 2 +- Tests/Bricks/ImageBrickTests.swift | 2 +- Tests/Models/BrickModelsTests.swift | 5 + .../BrickSectionOrderDataSourceTests.swift | 335 ++++++++++++++++++ 12 files changed, 664 insertions(+), 127 deletions(-) create mode 100644 Example/Source/Examples/Simple/ReorderableBrickViewController.swift rename Source/Models/{BrickModels.swift => Brick.swift} (51%) create mode 100644 Source/Models/BrickSection.swift create mode 100644 Tests/Models/BrickSectionOrderDataSourceTests.swift diff --git a/BrickKit.xcodeproj/project.pbxproj b/BrickKit.xcodeproj/project.pbxproj index f293971..6a20eca 100644 --- a/BrickKit.xcodeproj/project.pbxproj +++ b/BrickKit.xcodeproj/project.pbxproj @@ -38,7 +38,7 @@ 4E9A25331DABEB8800D7EA99 /* BrickLayoutSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D9EBD81DA4057000D8C87A /* BrickLayoutSection.swift */; }; 4E9A25351DABEB8D00D7EA99 /* BrickCollectionViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D9EBDB1DA4057000D8C87A /* BrickCollectionViewDataSource.swift */; }; 4E9A25361DABEB8D00D7EA99 /* BrickDimension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D9EBDC1DA4057000D8C87A /* BrickDimension.swift */; }; - 4E9A25371DABEB8D00D7EA99 /* BrickModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D9EBDD1DA4057000D8C87A /* BrickModels.swift */; }; + 4E9A25371DABEB8D00D7EA99 /* Brick.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D9EBDD1DA4057000D8C87A /* Brick.swift */; }; 4E9A25381DABEB8D00D7EA99 /* BrickProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D9EBDE1DA4057000D8C87A /* BrickProtocols.swift */; }; 4E9A25391DABEB8D00D7EA99 /* BrickSectionDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D9EBDF1DA4057000D8C87A /* BrickSectionDataSource.swift */; }; 4E9A253A1DABEB8D00D7EA99 /* CollectionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9350F0D51DA82A430051235F /* CollectionInfo.swift */; }; @@ -120,6 +120,10 @@ 930054031DA724AD00239A13 /* SetZIndexBehaviorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 930054011DA7242800239A13 /* SetZIndexBehaviorTests.swift */; }; 930054051DA7D8A000239A13 /* BaseBrickCellTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 930054041DA7D8A000239A13 /* BaseBrickCellTests.swift */; }; 9303A6D51DA6E00100502803 /* ButtonBrickTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9303A6D41DA6E00100502803 /* ButtonBrickTests.swift */; }; + 9317A9E11F0EE02E00AF2007 /* BrickSectionOrderDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9317A9E01F0EE02E00AF2007 /* BrickSectionOrderDataSourceTests.swift */; }; + 9317A9E21F0EE02E00AF2007 /* BrickSectionOrderDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9317A9E01F0EE02E00AF2007 /* BrickSectionOrderDataSourceTests.swift */; }; + 931A52F01F0D578C00AB0BDF /* BrickSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931A52EF1F0D578C00AB0BDF /* BrickSection.swift */; }; + 931A52F11F0D579000AB0BDF /* BrickSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931A52EF1F0D578C00AB0BDF /* BrickSection.swift */; }; 9323656F1DF4FD4900BE5183 /* BrickAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9323656E1DF4FD4900BE5183 /* BrickAlignment.swift */; }; 932365701DF4FD4900BE5183 /* BrickAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9323656E1DF4FD4900BE5183 /* BrickAlignment.swift */; }; 932365721DF4FE1F00BE5183 /* BrickAlignmentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932365711DF4FE1F00BE5183 /* BrickAlignmentTests.swift */; }; @@ -176,7 +180,7 @@ 93D9EC0B1DA4057000D8C87A /* BrickLayoutSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D9EBD81DA4057000D8C87A /* BrickLayoutSection.swift */; }; 93D9EC0D1DA4057000D8C87A /* BrickCollectionViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D9EBDB1DA4057000D8C87A /* BrickCollectionViewDataSource.swift */; }; 93D9EC0E1DA4057000D8C87A /* BrickDimension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D9EBDC1DA4057000D8C87A /* BrickDimension.swift */; }; - 93D9EC0F1DA4057000D8C87A /* BrickModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D9EBDD1DA4057000D8C87A /* BrickModels.swift */; }; + 93D9EC0F1DA4057000D8C87A /* Brick.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D9EBDD1DA4057000D8C87A /* Brick.swift */; }; 93D9EC101DA4057000D8C87A /* BrickProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D9EBDE1DA4057000D8C87A /* BrickProtocols.swift */; }; 93D9EC111DA4057000D8C87A /* BrickSectionDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D9EBDF1DA4057000D8C87A /* BrickSectionDataSource.swift */; }; 93D9EC121DA4057000D8C87A /* BrickExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D9EBE11DA4057000D8C87A /* BrickExtensions.swift */; }; @@ -283,6 +287,8 @@ 930054011DA7242800239A13 /* SetZIndexBehaviorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetZIndexBehaviorTests.swift; sourceTree = ""; }; 930054041DA7D8A000239A13 /* BaseBrickCellTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseBrickCellTests.swift; sourceTree = ""; }; 9303A6D41DA6E00100502803 /* ButtonBrickTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonBrickTests.swift; sourceTree = ""; }; + 9317A9E01F0EE02E00AF2007 /* BrickSectionOrderDataSourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrickSectionOrderDataSourceTests.swift; sourceTree = ""; }; + 931A52EF1F0D578C00AB0BDF /* BrickSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrickSection.swift; sourceTree = ""; }; 9323656E1DF4FD4900BE5183 /* BrickAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrickAlignment.swift; sourceTree = ""; }; 932365711DF4FE1F00BE5183 /* BrickAlignmentTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrickAlignmentTests.swift; sourceTree = ""; }; 9328EEA51DC19ACE007F2562 /* LazyLoadingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LazyLoadingTests.swift; sourceTree = ""; }; @@ -334,7 +340,7 @@ 93D9EBD81DA4057000D8C87A /* BrickLayoutSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrickLayoutSection.swift; sourceTree = ""; }; 93D9EBDB1DA4057000D8C87A /* BrickCollectionViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrickCollectionViewDataSource.swift; sourceTree = ""; }; 93D9EBDC1DA4057000D8C87A /* BrickDimension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrickDimension.swift; sourceTree = ""; }; - 93D9EBDD1DA4057000D8C87A /* BrickModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrickModels.swift; sourceTree = ""; }; + 93D9EBDD1DA4057000D8C87A /* Brick.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Brick.swift; sourceTree = ""; }; 93D9EBDE1DA4057000D8C87A /* BrickProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrickProtocols.swift; sourceTree = ""; }; 93D9EBDF1DA4057000D8C87A /* BrickSectionDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrickSectionDataSource.swift; sourceTree = ""; }; 93D9EBE11DA4057000D8C87A /* BrickExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrickExtensions.swift; sourceTree = ""; }; @@ -722,7 +728,8 @@ children = ( 93D9EBDB1DA4057000D8C87A /* BrickCollectionViewDataSource.swift */, 93D9EBDC1DA4057000D8C87A /* BrickDimension.swift */, - 93D9EBDD1DA4057000D8C87A /* BrickModels.swift */, + 93D9EBDD1DA4057000D8C87A /* Brick.swift */, + 931A52EF1F0D578C00AB0BDF /* BrickSection.swift */, 93D9EBDE1DA4057000D8C87A /* BrickProtocols.swift */, 93D9EBDF1DA4057000D8C87A /* BrickSectionDataSource.swift */, 9350F0D51DA82A430051235F /* CollectionInfo.swift */, @@ -850,6 +857,7 @@ 93D9EC461DA4057900D8C87A /* BrickModelsTests.swift */, 93D9EC471DA4057900D8C87A /* BrickSectionDataSourceTests.swift */, 932365711DF4FE1F00BE5183 /* BrickAlignmentTests.swift */, + 9317A9E01F0EE02E00AF2007 /* BrickSectionOrderDataSourceTests.swift */, ); path = Models; sourceTree = ""; @@ -1121,7 +1129,7 @@ 4E9A25211DABEB6000D7EA99 /* MinimumStickyLayoutBehavior.swift in Sources */, 4E9A25301DABEB8800D7EA99 /* BrickFlowLayout.swift in Sources */, 4E9A25291DABEB6500D7EA99 /* ButtonBrick.swift in Sources */, - 4E9A25371DABEB8D00D7EA99 /* BrickModels.swift in Sources */, + 4E9A25371DABEB8D00D7EA99 /* Brick.swift in Sources */, 4E9A25321DABEB8800D7EA99 /* BrickLayoutInvalidationContext.swift in Sources */, 4E9A251E1DABEB6000D7EA99 /* CoverFlowLayoutBehavior.swift in Sources */, 9333538B1E368903003CEC85 /* GenericBrick.swift in Sources */, @@ -1137,6 +1145,7 @@ 4E9A25261DABEB6000D7EA99 /* SpotlightLayoutBehavior.swift in Sources */, 4E9A253D1DABEB9300D7EA99 /* FatalError.swift in Sources */, 4E9A25271DABEB6000D7EA99 /* StickyFooterLayoutBehavior.swift in Sources */, + 931A52F11F0D579000AB0BDF /* BrickSection.swift in Sources */, 4E9A253F1DABEB9800D7EA99 /* BrickCollectionView.swift in Sources */, 4E9A25221DABEB6000D7EA99 /* OffsetLayoutBehavior.swift in Sources */, ); @@ -1161,6 +1170,7 @@ 4E9A25661DABECFF00D7EA99 /* FatalErrorTests.swift in Sources */, 4E9A25471DABECDF00D7EA99 /* MinimumStickyLayoutBehaviorTests.swift in Sources */, 4E9A25E01DAC2AF400D7EA99 /* AsynchronousResizableBrick.swift in Sources */, + 9317A9E21F0EE02E00AF2007 /* BrickSectionOrderDataSourceTests.swift in Sources */, 4E9A25561DABECF500D7EA99 /* BrickFlowLayoutEdgeInsetsTests.swift in Sources */, 93E435EE1DB6CB51007C159B /* CoverFlowLayoutBehaviorTests.swift in Sources */, 4E9A25E41DAC2AF400D7EA99 /* ImageDownloaderBrick.swift in Sources */, @@ -1210,7 +1220,7 @@ buildActionMask = 2147483647; files = ( 93D9EBF31DA4057000D8C87A /* SpotlightLayoutBehavior.swift in Sources */, - 93D9EC0F1DA4057000D8C87A /* BrickModels.swift in Sources */, + 93D9EC0F1DA4057000D8C87A /* Brick.swift in Sources */, 93D9EC111DA4057000D8C87A /* BrickSectionDataSource.swift in Sources */, 93739BB51DA454AB00DD4B81 /* CollectionBrick.swift in Sources */, 9350F0D61DA82A430051235F /* CollectionInfo.swift in Sources */, @@ -1246,6 +1256,7 @@ 93D9EBED1DA4057000D8C87A /* MaxZIndexLayoutBehavior.swift in Sources */, 93D9EC141DA4057000D8C87A /* FatalError.swift in Sources */, 93D9EBEE1DA4057000D8C87A /* MinimumStickyLayoutBehavior.swift in Sources */, + 931A52F01F0D578C00AB0BDF /* BrickSection.swift in Sources */, 93D9EC161DA4057000D8C87A /* BrickCollectionView.swift in Sources */, 93739BB81DA454AB00DD4B81 /* ButtonBrick.swift in Sources */, ); @@ -1266,6 +1277,7 @@ 93D9EC5A1DA4057900D8C87A /* OffsetLayoutBehaviorTests.swift in Sources */, 93D9EC7C1DA4057900D8C87A /* BrickModelsTests.swift in Sources */, 93D9EC551DA4057900D8C87A /* CardLayoutBehaviorTests.swift in Sources */, + 9317A9E11F0EE02E00AF2007 /* BrickSectionOrderDataSourceTests.swift in Sources */, 9303A6D51DA6E00100502803 /* ButtonBrickTests.swift in Sources */, 93D9EC821DA4057900D8C87A /* DataSources.swift in Sources */, 93D9EC7D1DA4057900D8C87A /* BrickSectionDataSourceTests.swift in Sources */, diff --git a/Example/BrickKit-Example.xcodeproj/project.pbxproj b/Example/BrickKit-Example.xcodeproj/project.pbxproj index 6930273..f9a97d2 100644 --- a/Example/BrickKit-Example.xcodeproj/project.pbxproj +++ b/Example/BrickKit-Example.xcodeproj/project.pbxproj @@ -157,6 +157,8 @@ 93186B041DDE89610095C849 /* HugeRepeatBrickViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93186B021DDE89610095C849 /* HugeRepeatBrickViewController.swift */; }; 93186B081DDE8A580095C849 /* HugeRepeatCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93186B071DDE8A580095C849 /* HugeRepeatCollectionViewController.swift */; }; 93186B091DDE8A580095C849 /* HugeRepeatCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93186B071DDE8A580095C849 /* HugeRepeatCollectionViewController.swift */; }; + 931A52F31F0D5B9300AB0BDF /* ReorderableBrickViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931A52F21F0D5B9300AB0BDF /* ReorderableBrickViewController.swift */; }; + 931A52F41F0D5B9300AB0BDF /* ReorderableBrickViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 931A52F21F0D5B9300AB0BDF /* ReorderableBrickViewController.swift */; }; 932365671DF449A500BE5183 /* FillBrickViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932365661DF449A500BE5183 /* FillBrickViewController.swift */; }; 9327D03E1DB94A9500D65BCB /* NibLessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9327D03D1DB94A9500D65BCB /* NibLessViewController.swift */; }; 9327D03F1DB94A9500D65BCB /* NibLessViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9327D03D1DB94A9500D65BCB /* NibLessViewController.swift */; }; @@ -349,6 +351,7 @@ 4E8E10F31DB569E100B5BD90 /* EmbeddedSpotlightSnapScrollingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmbeddedSpotlightSnapScrollingViewController.swift; sourceTree = ""; }; 93186B021DDE89610095C849 /* HugeRepeatBrickViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HugeRepeatBrickViewController.swift; sourceTree = ""; }; 93186B071DDE8A580095C849 /* HugeRepeatCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HugeRepeatCollectionViewController.swift; sourceTree = ""; }; + 931A52F21F0D5B9300AB0BDF /* ReorderableBrickViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReorderableBrickViewController.swift; sourceTree = ""; }; 932365661DF449A500BE5183 /* FillBrickViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FillBrickViewController.swift; sourceTree = ""; }; 9327D03D1DB94A9500D65BCB /* NibLessViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NibLessViewController.swift; sourceTree = ""; }; 9327D0421DB94AB300D65BCB /* NiblessBrick.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NiblessBrick.swift; sourceTree = ""; }; @@ -616,6 +619,7 @@ 93186B021DDE89610095C849 /* HugeRepeatBrickViewController.swift */, 93186B071DDE8A580095C849 /* HugeRepeatCollectionViewController.swift */, 938996A41DF77A3200E0F4F1 /* AlignmentBrickViewController.swift */, + 931A52F21F0D5B9300AB0BDF /* ReorderableBrickViewController.swift */, ); path = Simple; sourceTree = ""; @@ -997,6 +1001,7 @@ 4E3BD8241DB1316400541900 /* HideBrickViewController.swift in Sources */, 93EAFE2F1DB57D680036331C /* NavigationTransition.swift in Sources */, 4E3BD7D81DB1316400541900 /* PostBrick.swift in Sources */, + 931A52F31F0D5B9300AB0BDF /* ReorderableBrickViewController.swift in Sources */, 4E3BD8281DB1316400541900 /* InsertBrickViewController.swift in Sources */, 4E3BD8361DB1316400541900 /* SizeClassesBrickViewController.swift in Sources */, 4E3BD8321DB1316400541900 /* PopoverBrickViewController.swift in Sources */, @@ -1025,6 +1030,7 @@ 4E3BD8011DB1316400541900 /* BaseRepeatBrickViewController.swift in Sources */, 4E3BD85D1DB1316400541900 /* StickingFooterBaseViewController.swift in Sources */, 4E3BD84F1DB1316400541900 /* SimpleRepeatFixedWidthViewController.swift in Sources */, + 931A52F41F0D5B9300AB0BDF /* ReorderableBrickViewController.swift in Sources */, 938996A61DF77A3200E0F4F1 /* AlignmentBrickViewController.swift in Sources */, 4E3BD7EF1DB1316400541900 /* WhoToFollowBrick.swift in Sources */, 4E3BD7C71DB1316400541900 /* DailySalesBrick.swift in Sources */, diff --git a/Example/Source/Examples/BaseBrickController.swift b/Example/Source/Examples/BaseBrickController.swift index 734fe7c..a9f09be 100644 --- a/Example/Source/Examples/BaseBrickController.swift +++ b/Example/Source/Examples/BaseBrickController.swift @@ -53,6 +53,16 @@ class BaseBrickController: BrickViewController { } } +extension UILabel { + + func configure(textColor: UIColor?) { + self.textAlignment = .center + self.textColor = textColor + self.numberOfLines = 0 + } + +} + extension LabelBrickCell { static func configure(cell: LabelBrickCell) { @@ -60,8 +70,7 @@ extension LabelBrickCell { } func configure() { - label.textAlignment = .center - label.textColor = brick.backgroundColor.complemetaryColor + label.configure(textColor: brick.backgroundColor.complemetaryColor) } } diff --git a/Example/Source/Examples/Simple/ReorderableBrickViewController.swift b/Example/Source/Examples/Simple/ReorderableBrickViewController.swift new file mode 100644 index 0000000..db10059 --- /dev/null +++ b/Example/Source/Examples/Simple/ReorderableBrickViewController.swift @@ -0,0 +1,149 @@ +// +// ReorderableBrickViewController.swift +// BrickKit-Example +// +// Created by Ruben Cagnie on 6/30/17. +// Copyright © 2017 Wayfair LLC. All rights reserved. +// + +import Foundation +import BrickKit + +fileprivate let repeatCount: Int = 5 + +class ReorderableBrickViewController: BrickViewController, HasTitle { + class var brickTitle: String { + return "Reorderable Section" + } + + class var subTitle: String { + return "Reorderable Sections allow you to specify your own way of ordering bricks within a section" + } + + let label1 = GenericBrick("Label1", backgroundColor: .brickGray1) { label, cell in + cell.edgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) + + label.text = "Label1 - \(cell.index)" + label.configure(textColor: UIColor.brickGray1.complemetaryColor) + } + + let label2 = GenericBrick("Label2", backgroundColor: .brickGray3) { label, cell in + cell.edgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) + + label.text = "Label2 - \(cell.index)" + label.configure(textColor: UIColor.brickGray3.complemetaryColor) + } + + var reorderSection1: BrickSection! + var reorderSection2: BrickSection! + var reorderSection3: BrickSection! + + override func viewDidLoad() { + super.viewDidLoad() + + self.view.backgroundColor = .brickBackground + + label1.repeatCount = repeatCount + label2.repeatCount = repeatCount + + reorderSection1 = BrickSection(bricks: [ + label1, + label2 + ], inset: 5) + reorderSection1.orderDataSource = self + + reorderSection2 = BrickSection(bricks: [ + label1, + label2 + ], inset: 5) + reorderSection2.orderDataSource = self + + reorderSection3 = BrickSection(bricks: [ + label1, + label2 + ], inset: 5) + reorderSection3.orderDataSource = self + + let section = BrickSection(bricks: [ + BrickSection(backgroundColor: .brickGray2, bricks: [ + GenericBrick(backgroundColor: .brickGray5) { label, cell in + cell.edgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) + + label.text = "Normal " + label.configure(textColor: UIColor.brickGray5.complemetaryColor) + }, + reorderSection1, + ], inset: 10, edgeInsets: UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)), + BrickSection(backgroundColor: .brickGray2, bricks: [ + GenericBrick(backgroundColor: .brickGray5) { label, cell in + cell.edgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) + + label.text = "Staggered" + label.configure(textColor: UIColor.brickGray5.complemetaryColor) + }, + reorderSection2, + ], inset: 10, edgeInsets: UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)), + BrickSection(backgroundColor: .brickGray2, bricks: [ + GenericBrick(backgroundColor: .brickGray5) { label, cell in + cell.edgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) + + label.text = "Advanced" + label.configure(textColor: UIColor.brickGray5.complemetaryColor) + }, + reorderSection3 + ], inset: 10, edgeInsets: UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)) + ], inset: 20, edgeInsets: UIEdgeInsets(top: 20, left: 10, bottom: 20, right: 10)) + + self.setSection(section) + + self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(test)) + } + + func test() { + for section in 0.. (Brick, Int)? { + if section === reorderSection2 { + let actualIndex = brickIndex / 2 + if brickIndex % 2 == 0 { + return (section.bricks[0], actualIndex) + } else { + return (section.bricks[1], actualIndex) + } + } else if section === reorderSection3 { + let advancedRepeat = [ + [0, 1, 2, 5, 8], + [3, 4, 6, 7, 9] + ] + let actualIndex: Int + let brick: Brick + if advancedRepeat[0].contains(brickIndex) { + actualIndex = advancedRepeat[0].index(of: brickIndex)! + brick = section.bricks[0] + } else { + actualIndex = advancedRepeat[1].index(of: brickIndex)! + brick = section.bricks[1] + } + return (brick, actualIndex) + } else { + return nil + } + } + +} diff --git a/Example/Source/Navigation/NavigationDataSource.swift b/Example/Source/Navigation/NavigationDataSource.swift index 9bdf121..95e0c4a 100644 --- a/Example/Source/Navigation/NavigationDataSource.swift +++ b/Example/Source/Navigation/NavigationDataSource.swift @@ -51,6 +51,7 @@ class NavigationDataSource { SimpleRepeatFixedWidthViewController.self, SimpleRepeatFixedHeightViewController.self, SimpleRepeatHeightRatioViewController.self, + ReorderableBrickViewController.self, FillBrickViewController.self, MultiSectionBrickViewController.self, MultiDimensionBrickViewController.self, diff --git a/Source/Models/BrickModels.swift b/Source/Models/Brick.swift similarity index 51% rename from Source/Models/BrickModels.swift rename to Source/Models/Brick.swift index d7e85d3..100c5e0 100644 --- a/Source/Models/BrickModels.swift +++ b/Source/Models/Brick.swift @@ -144,107 +144,3 @@ open class Brick: CustomStringConvertible { return description } } - -open class BrickSection: Brick { - open var bricks: [Brick] - open var inset: CGFloat - open var edgeInsets: UIEdgeInsets - open var alignRowHeights: Bool - open var alignment: BrickAlignment - - internal fileprivate(set) var collectionIndex: Int = 0 - internal fileprivate(set) var collectionIdentifier: String = "" - - internal fileprivate(set) var sectionCount: Int = 0 - internal fileprivate(set) var sectionIndexPaths: [CollectionInfo: [Int: IndexPath]] = [:] // Variable that keeps track of the indexpaths of the sections - - /// Optional dictionary that holds the identifier of a brick as a key and the value is the nib that should be used for that brick - /// These nibs will be registered, when setting this BrickSection on a BrickCollectionView - open var nibIdentifiers: [String: UINib]? - - open internal(set) weak var brickCollectionView: BrickCollectionView? - - open weak var repeatCountDataSource: BrickRepeatCountDataSource? { - didSet { - sectionIndexPaths.removeAll() - } - } - - public init(_ identifier: String = "", width: BrickDimension = .ratio(ratio: 1), height: BrickDimension = .auto(estimate: .fixed(size: 0)), backgroundColor: UIColor = UIColor.clear, backgroundView: UIView? = nil, bricks: [Brick], inset: CGFloat = 0, edgeInsets: UIEdgeInsets = UIEdgeInsets.zero, alignRowHeights: Bool = false, alignment: BrickAlignment = BrickAlignment(horizontal: .left, vertical: .top)) { - self.bricks = bricks - self.inset = inset - self.edgeInsets = edgeInsets - self.alignRowHeights = alignRowHeights - self.alignment = alignment - super.init(identifier, size: BrickSize(width: width, height: height), backgroundColor: backgroundColor, backgroundView: backgroundView) - } - - /// Invalidate the brick counts for a given collection. Recalculate where sections are in the tree - func invalidateIfNeeded(in collection: CollectionInfo) -> [Int: IndexPath] { - if let sectionIndexPaths = self.sectionIndexPaths[collection] { - return sectionIndexPaths - } - - return invalidateSectionIndexPaths(in: collection) - } - - /// Invalidate the counts for a given dimension - func invalidateCounts(in collection: CollectionInfo) { - sectionIndexPaths[collection] = nil - } - - func invalidateSectionIndexPaths(in collection: CollectionInfo) -> [Int: IndexPath] { - var sectionIndexPaths: [Int: IndexPath] = [:] - var sectionCount = 0 - - BrickSection.addSection(§ionIndexPaths, sectionCount: §ionCount, bricks: [self], repeatCountDataSource: repeatCountDataSource, in: collection) - - self.sectionIndexPaths[collection] = sectionIndexPaths - self.sectionCount = sectionCount - - return sectionIndexPaths - } - - /// Add a section - static fileprivate func addSection(_ sectionIndexPaths: inout [Int: IndexPath], sectionCount: inout Int, bricks: [Brick], repeatCountDataSource: BrickRepeatCountDataSource?, atIndexPath indexPath: IndexPath? = nil, in collection: CollectionInfo) { - let sectionId = sectionCount - sectionCount += 1 - if let indexPath = indexPath { - sectionIndexPaths[sectionId] = indexPath - } - - var index = 0 - for brick in bricks { - brick.counts[collection] = repeatCountDataSource?.repeatCount(for: brick.identifier, with: collection.index, collectionIdentifier: collection.identifier) ?? brick.count(for: collection) - - if let sectionModel = brick as? BrickSection { - // Do not set a repeat count on a section - if brick.count(for: collection) != 1 { - fatalError("Repeat count on a section is not allowed (requested \(brick.count(for: collection))). Please use `CollectionBrick`") - } - BrickSection.addSection(§ionIndexPaths, sectionCount: §ionCount, bricks: sectionModel.bricks, repeatCountDataSource: sectionModel.repeatCountDataSource ?? repeatCountDataSource, atIndexPath: IndexPath(row: index, section: sectionId), in: collection) - } - - index += brick.count(for: collection) - } - } - - // Mark: - CustomStringConvertible - - /// Convenience method to show description with an indentation - override internal func descriptionWithIndentationLevel(_ indentationLevel: Int) -> String { - var description = super.descriptionWithIndentationLevel(indentationLevel) - description += " inset: \(inset) edgeInsets: \(edgeInsets)" - - var brickDescription = "" - for brick in bricks { - brickDescription += "\n" - brickDescription += "\(brick.descriptionWithIndentationLevel(indentationLevel + 1))" - } - description += brickDescription - - return description - } -} - - diff --git a/Source/Models/BrickSection.swift b/Source/Models/BrickSection.swift new file mode 100644 index 0000000..d08a6a9 --- /dev/null +++ b/Source/Models/BrickSection.swift @@ -0,0 +1,132 @@ +// +// BrickSection.swift +// BrickKit +// +// Created by Ruben Cagnie on 7/5/17. +// Copyright © 2017 Wayfair. All rights reserved. +// + +import Foundation + +public protocol BrickSectionOrderDataSource: class { + func brickAndIndex(atIndex brickIndex: Int, section: BrickSection) -> (Brick, Int)? +} + +open class BrickSection: Brick { + open var bricks: [Brick] + open var inset: CGFloat + open var edgeInsets: UIEdgeInsets + open var alignRowHeights: Bool + open var alignment: BrickAlignment + + internal fileprivate(set) var collectionIndex: Int = 0 + internal fileprivate(set) var collectionIdentifier: String = "" + + internal fileprivate(set) var sectionCount: Int = 0 + internal fileprivate(set) var sectionIndexPaths: [CollectionInfo: [Int: IndexPath]] = [:] // Variable that keeps track of the indexpaths of the sections + + /// Optional dictionary that holds the identifier of a brick as a key and the value is the nib that should be used for that brick + /// These nibs will be registered, when setting this BrickSection on a BrickCollectionView + open var nibIdentifiers: [String: UINib]? + + open internal(set) weak var brickCollectionView: BrickCollectionView? + + open weak var orderDataSource: BrickSectionOrderDataSource? + + open weak var repeatCountDataSource: BrickRepeatCountDataSource? { + didSet { + sectionIndexPaths.removeAll() + } + } + + public init(_ identifier: String = "", width: BrickDimension = .ratio(ratio: 1), height: BrickDimension = .auto(estimate: .fixed(size: 0)), backgroundColor: UIColor = UIColor.clear, backgroundView: UIView? = nil, bricks: [Brick], inset: CGFloat = 0, edgeInsets: UIEdgeInsets = UIEdgeInsets.zero, alignRowHeights: Bool = false, alignment: BrickAlignment = BrickAlignment(horizontal: .left, vertical: .top)) { + self.bricks = bricks + self.inset = inset + self.edgeInsets = edgeInsets + self.alignRowHeights = alignRowHeights + self.alignment = alignment + super.init(identifier, size: BrickSize(width: width, height: height), backgroundColor: backgroundColor, backgroundView: backgroundView) + } + + /// Invalidate the brick counts for a given collection. Recalculate where sections are in the tree + func invalidateIfNeeded(in collection: CollectionInfo) -> [Int: IndexPath] { + if let sectionIndexPaths = self.sectionIndexPaths[collection] { + return sectionIndexPaths + } + + return invalidateSectionIndexPaths(in: collection) + } + + /// Invalidate the counts for a given dimension + func invalidateCounts(in collection: CollectionInfo) { + sectionIndexPaths[collection] = nil + } + + func invalidateSectionIndexPaths(in collection: CollectionInfo) -> [Int: IndexPath] { + var sectionIndexPaths: [Int: IndexPath] = [:] + var sectionCount = 0 + + BrickSection.addSection(§ionIndexPaths, sectionCount: §ionCount, bricks: [self], repeatCountDataSource: repeatCountDataSource, in: collection) + + self.sectionIndexPaths[collection] = sectionIndexPaths + self.sectionCount = sectionCount + + return sectionIndexPaths + } + + func brickAndIndex(atIndex brickIndex: Int, in collection: CollectionInfo) -> (Brick, Int)? { + if let orderDataSource = orderDataSource, let brickAndIndex = orderDataSource.brickAndIndex(atIndex: brickIndex, section: self) { + return brickAndIndex + } else { + var index = 0 + for brick in self.bricks { + if brickIndex < index + brick.count(for: collection) { + return (brick, brickIndex - index) + } + index += brick.count(for: collection) + } + } + return nil + } + + /// Add a section + static fileprivate func addSection(_ sectionIndexPaths: inout [Int: IndexPath], sectionCount: inout Int, bricks: [Brick], repeatCountDataSource: BrickRepeatCountDataSource?, atIndexPath indexPath: IndexPath? = nil, in collection: CollectionInfo) { + let sectionId = sectionCount + sectionCount += 1 + if let indexPath = indexPath { + sectionIndexPaths[sectionId] = indexPath + } + + var index = 0 + for brick in bricks { + brick.counts[collection] = repeatCountDataSource?.repeatCount(for: brick.identifier, with: collection.index, collectionIdentifier: collection.identifier) ?? brick.count(for: collection) + + if let sectionModel = brick as? BrickSection { + // Do not set a repeat count on a section + if brick.count(for: collection) != 1 { + fatalError("Repeat count on a section is not allowed (requested \(brick.count(for: collection))). Please use `CollectionBrick`") + } + BrickSection.addSection(§ionIndexPaths, sectionCount: §ionCount, bricks: sectionModel.bricks, repeatCountDataSource: sectionModel.repeatCountDataSource ?? repeatCountDataSource, atIndexPath: IndexPath(row: index, section: sectionId), in: collection) + } + + index += brick.count(for: collection) + } + } + + // Mark: - CustomStringConvertible + + /// Convenience method to show description with an indentation + override internal func descriptionWithIndentationLevel(_ indentationLevel: Int) -> String { + var description = super.descriptionWithIndentationLevel(indentationLevel) + description += " inset: \(inset) edgeInsets: \(edgeInsets)" + + var brickDescription = "" + for brick in bricks { + brickDescription += "\n" + brickDescription += "\(brick.descriptionWithIndentationLevel(indentationLevel + 1))" + } + description += brickDescription + + return description + } +} diff --git a/Source/Models/BrickSectionDataSource.swift b/Source/Models/BrickSectionDataSource.swift index 143cf77..2a15c7a 100644 --- a/Source/Models/BrickSectionDataSource.swift +++ b/Source/Models/BrickSectionDataSource.swift @@ -49,7 +49,7 @@ extension BrickSection { return brickSection.numberOfBricks(in: collection) } - internal func brickAndIndex(at indexPath: IndexPath, in collection: CollectionInfo) -> (Brick, Int)? { + internal func brickAndIndex(atIndexPath indexPath: IndexPath, in collection: CollectionInfo) -> (Brick, Int)? { _ = invalidateIfNeeded(in: collection) if indexPath.section == 0 { @@ -60,23 +60,15 @@ extension BrickSection { return nil } - var index = 0 - for brick in section.bricks { - if indexPath.item < index + brick.count(for: collection) { - return (brick, indexPath.item - index) - } - index += brick.count(for: collection) - } - - return nil + return section.brickAndIndex(atIndex: indexPath.item, in: collection) } func brick(at indexPath: IndexPath, in collection: CollectionInfo) -> Brick? { - return brickAndIndex(at: indexPath, in: collection)?.0 + return brickAndIndex(atIndexPath: indexPath, in: collection)?.0 } func index(at indexPath: IndexPath, in collection: CollectionInfo) -> Int? { - return brickAndIndex(at: indexPath, in: collection)?.1 + return brickAndIndex(atIndexPath: indexPath, in: collection)?.1 } func indexPathFor(_ section: Int, in collection: CollectionInfo) -> IndexPath? { @@ -95,7 +87,7 @@ extension BrickSection { for section in 0.. BrickInfo { - guard let brickAndIndex = section.brickAndIndex(at: indexPath, in: collectionInfo) else { + guard let brickAndIndex = section.brickAndIndex(atIndexPath: indexPath, in: collectionInfo) else { fatalError("Brick and index not found at indexPath: SECTION - \(indexPath.section) - ITEM: \(indexPath.item). This should never happen") } return (brickAndIndex.0, brickAndIndex.1, collectionInfo.index, collectionInfo.identifier) diff --git a/Tests/Bricks/ImageBrickTests.swift b/Tests/Bricks/ImageBrickTests.swift index b630ce9..c3d9503 100644 --- a/Tests/Bricks/ImageBrickTests.swift +++ b/Tests/Bricks/ImageBrickTests.swift @@ -279,7 +279,7 @@ class ImageBrickTests: XCTestCase { invalidateExpectation.fulfill() } - waitForExpectations(timeout: 1, handler: nil) + waitForExpectations(timeout: 2, handler: nil) let cell2 = brickView.cellForItem(at: IndexPath(item: 0, section: 1)) as? ImageBrickCell cell2?.layoutIfNeeded() diff --git a/Tests/Models/BrickModelsTests.swift b/Tests/Models/BrickModelsTests.swift index 15b5f54..73cb2d3 100644 --- a/Tests/Models/BrickModelsTests.swift +++ b/Tests/Models/BrickModelsTests.swift @@ -57,4 +57,9 @@ class BrickModelsTests: XCTestCase { XCTAssertEqual(section.description, expectedResult) } + + func testSettingAndGettingWidth() { + let brick = Brick(size: BrickSize(width: .fixed(size: 50), height: .auto(estimate: .fixed(size: 50)))) + XCTAssertEqual(brick.width, .fixed(size: 50)) + } } diff --git a/Tests/Models/BrickSectionOrderDataSourceTests.swift b/Tests/Models/BrickSectionOrderDataSourceTests.swift new file mode 100644 index 0000000..d969bc2 --- /dev/null +++ b/Tests/Models/BrickSectionOrderDataSourceTests.swift @@ -0,0 +1,335 @@ +// +// BrickSection+OrderDataSource.swift +// BrickKit +// +// Created by Ruben Cagnie on 7/6/17. +// Copyright © 2017 Wayfair. All rights reserved. +// + +import XCTest +@testable import BrickKit + +class BrickSectionOrderDataSourceTests: XCTestCase { + + var brickCollectionView: BrickCollectionView! + var section: BrickSection! + var orderDataSource: BrickSectionOrderDataSource! + + override func setUp() { + super.setUp() + + brickCollectionView = BrickCollectionView(frame: CGRect(x: 0, y: 0, width: 320, height: 480)) + + let brick1 = DummyBrick("Brick1", height: .fixed(size: 50)) + let brick2 = DummyBrick("Brick2", height: .fixed(size: 50)) + + brick1.repeatCount = 5 + brick2.repeatCount = 5 + + section = BrickSection(bricks: [ + brick1, brick2 + ]) + } + + fileprivate var indexesForBrick1: [Int] { + return indexesForBrick(withIdentifier: "Brick1") + } + + fileprivate var indexesForBrick2: [Int] { + return indexesForBrick(withIdentifier: "Brick2") + } + + fileprivate func indexesForBrick(withIdentifier identifier: String) -> [Int] { + return brickCollectionView.indexPathsForBricksWithIdentifier(identifier).map({$0.item}).sorted(by: <) + } + +} + +// Mark: - Staggered + +class BrickSectionStaggeredOrderDataSourceTests: BrickSectionOrderDataSourceTests { + + override func setUp() { + super.setUp() + + orderDataSource = MockStaggeredBrickSectionOrderDataSource() + section.orderDataSource = orderDataSource + brickCollectionView.setupSectionAndLayout(section) + } + + func testThatIndex0ReturnsBrick0Index0() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 0, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[0]) + XCTAssertEqual(cell?.index, 0) + } + + func testThatIndex1ReturnsBrick1Index0() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 1, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[1]) + XCTAssertEqual(cell?.index, 0) + } + + func testThatIndex2ReturnsBrick0Index1() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 2, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[0]) + XCTAssertEqual(cell?.index, 1) + } + + func testThatIndex3ReturnsBrick1Index1() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 3, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[1]) + XCTAssertEqual(cell?.index, 1) + } + + func testThatIndex4ReturnsBrick0Index2() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 4, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[0]) + XCTAssertEqual(cell?.index, 2) + } + + func testThatIndex5ReturnsBrick1Index2() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 5, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[1]) + XCTAssertEqual(cell?.index, 2) + } + + func testThatIndex6ReturnsBrick0Index3() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 6, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[0]) + XCTAssertEqual(cell?.index, 3) + } + + func testThatIndex7ReturnsBrick1Index3() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 7, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[1]) + XCTAssertEqual(cell?.index, 3) + } + + func testThatIndex8ReturnsBrick0Index4() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 8, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[0]) + XCTAssertEqual(cell?.index, 4) + } + + func testThatIndex9ReturnsBrick1Index4() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 9, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[1]) + XCTAssertEqual(cell?.index, 4) + } + + func testThatReturningBrick1IndexesAreOrdered() { + XCTAssertEqual(indexesForBrick1, [0, 2, 4, 6, 8]) + } + + func testThatReturningBrick2IndexesAreOrdered() { + XCTAssertEqual(indexesForBrick2, [1, 3, 5, 7, 9]) + } + +} + +class MockStaggeredBrickSectionOrderDataSource: BrickSectionOrderDataSource { + + func brickAndIndex(atIndex brickIndex: Int, section: BrickSection) -> (Brick, Int)? { + let actualIndex = brickIndex / 2 + if brickIndex % 2 == 0 { + return (section.bricks[0], actualIndex) + } else { + return (section.bricks[1], actualIndex) + } + } + +} + +// Mark: - Advanced + +class BrickSectionAdvancedOrderDataSourceTests: BrickSectionOrderDataSourceTests { + + override func setUp() { + super.setUp() + + orderDataSource = MockAdvancedBrickSectionOrderDataSource() + section.orderDataSource = orderDataSource + brickCollectionView.setupSectionAndLayout(section) + } + + func testThatIndex0ReturnsBrick0Index0() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 0, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[0]) + XCTAssertEqual(cell?.index, 0) + } + + func testThatIndex1ReturnsBrick0Index1() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 1, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[0]) + XCTAssertEqual(cell?.index, 1) + } + + func testThatIndex2ReturnsBrick0Index2() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 2, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[0]) + XCTAssertEqual(cell?.index, 2) + } + + func testThatIndex3ReturnsBrick1Index0() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 3, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[1]) + XCTAssertEqual(cell?.index, 0) + } + + func testThatIndex4ReturnsBrick1Index1() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 4, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[1]) + XCTAssertEqual(cell?.index, 1) + } + + func testThatIndex5ReturnsBrick0Index3() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 5, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[0]) + XCTAssertEqual(cell?.index, 3) + } + + func testThatIndex6ReturnsBrick1Index2() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 6, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[1]) + XCTAssertEqual(cell?.index, 2) + } + + func testThatIndex7ReturnsBrick1Index3() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 7, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[1]) + XCTAssertEqual(cell?.index, 3) + } + + func testThatIndex8ReturnsBrick0Index4() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 8, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[0]) + XCTAssertEqual(cell?.index, 4) + } + + func testThatIndex9ReturnsBrick1Index4() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 9, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[1]) + XCTAssertEqual(cell?.index, 4) + } + + func testThatReturningBrick1IndexesAreOrdered() { + XCTAssertEqual(indexesForBrick1, [0, 1, 2, 5, 8]) + } + + func testThatReturningBrick2IndexesAreOrdered() { + XCTAssertEqual(indexesForBrick2, [3, 4, 6, 7, 9]) + } + +} + +class MockAdvancedBrickSectionOrderDataSource: BrickSectionOrderDataSource { + + let advancedRepeat: [[Int]] = [ + [0, 1, 2, 5, 8], + [3, 4, 6, 7, 9] + ] + + func brickAndIndex(atIndex brickIndex: Int, section: BrickSection) -> (Brick, Int)? { + let actualIndex: Int + let brick: Brick + if advancedRepeat[0].contains(brickIndex) { + actualIndex = advancedRepeat[0].index(of: brickIndex)! + brick = section.bricks[0] + } else { + actualIndex = advancedRepeat[1].index(of: brickIndex)! + brick = section.bricks[1] + } + return (brick, actualIndex) + } + +} + +// Mark: - nil + +class BrickSectionNilOrderDataSourceTests: BrickSectionOrderDataSourceTests { + + override func setUp() { + super.setUp() + + orderDataSource = MockNilBrickSectionOrderDataSource() + section.orderDataSource = orderDataSource + brickCollectionView.setupSectionAndLayout(section) + } + + func testThatIndex0ReturnsBrick0Index0() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 0, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[0]) + XCTAssertEqual(cell?.index, 0) + } + + func testThatIndex1ReturnsBrick0Index1() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 1, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[0]) + XCTAssertEqual(cell?.index, 1) + } + + func testThatIndex2ReturnsBrick0Index2() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 2, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[0]) + XCTAssertEqual(cell?.index, 2) + } + + func testThatIndex3ReturnsBrick0Index3() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 3, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[0]) + XCTAssertEqual(cell?.index, 3) + } + + func testThatIndex4ReturnsBrick0Index4() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 4, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[0]) + XCTAssertEqual(cell?.index, 4) + } + + func testThatIndex5ReturnsBrick1Index0() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 5, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[1]) + XCTAssertEqual(cell?.index, 0) + } + + func testThatIndex6ReturnsBrick1Index1() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 6, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[1]) + XCTAssertEqual(cell?.index, 1) + } + + func testThatIndex7ReturnsBrick1Index2() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 7, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[1]) + XCTAssertEqual(cell?.index, 2) + } + + func testThatIndex8ReturnsBrick1Index3() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 8, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[1]) + XCTAssertEqual(cell?.index, 3) + } + + func testThatIndex9ReturnsBrick1Index4() { + let cell = brickCollectionView.cellForItem(at: IndexPath(item: 9, section: 1)) as? BrickCell + XCTAssertTrue(cell?._brick === section.bricks[1]) + XCTAssertEqual(cell?.index, 4) + } + + func testThatReturningBrick1IndexesAreOrdered() { + XCTAssertEqual(indexesForBrick1, [0, 1, 2, 3, 4]) + } + + func testThatReturningBrick2IndexesAreOrdered() { + XCTAssertEqual(indexesForBrick2, [5, 6, 7, 8, 9]) + } + +} + +class MockNilBrickSectionOrderDataSource: BrickSectionOrderDataSource { + + func brickAndIndex(atIndex brickIndex: Int, section: BrickSection) -> (Brick, Int)? { + return nil + } + +}