diff --git a/CHANGELOG.md b/CHANGELOG.md index cf39713cb..dea0dce88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ This release closes the [2.0.0 milestone](https://github.com/Instagram/IGListKit - Added an additional initializer for `IGListSingleSectionController` to be able to support single sections created from nibs. An example can be found [here](Example/IGListKitExamples/ViewControllers/SingleSectionViewController.swift). - Fixed `-[IGListAdapter reloadDataWithCompletion:]` not returning early when `collectionView` or `dataSource` is nil and `completion` is nil. [Ben Asher](https://github.com/benasher44) [(#51)](https://github.com/Instagram/IGListKit/pull/51) - Added `-isFirstSection` and `-isLastSection` APIs to `IGListSectionController` - +- Added support for cells created from storyboard. [Bofei Zhu](https://github.com/zhubofei) [(#92)](https://github.com/Instagram/IGListKit/pull/92) 1.0.0 ----- diff --git a/Example/Demo.storyboard b/Example/Demo.storyboard new file mode 100644 index 000000000..7d582f9df --- /dev/null +++ b/Example/Demo.storyboard @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/IGListKitExamples.xcodeproj/project.pbxproj b/Example/IGListKitExamples.xcodeproj/project.pbxproj index 8d0019daa..f908d0fe4 100644 --- a/Example/IGListKitExamples.xcodeproj/project.pbxproj +++ b/Example/IGListKitExamples.xcodeproj/project.pbxproj @@ -40,6 +40,11 @@ 2991F9301D7BC0E400B0C58F /* EmptyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2991F92F1D7BC0E400B0C58F /* EmptyViewController.swift */; }; 299B54001D6BD6630074A202 /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 299B53FF1D6BD6630074A202 /* SearchViewController.swift */; }; 814F1E00410200822610BB49 /* Pods_IGListKitExamples.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52A8DC2D07A93D7AA55BC993 /* Pods_IGListKitExamples.framework */; }; + 821BC4B41DB8B25B00172ED0 /* Demo.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 821BC4B31DB8B25B00172ED0 /* Demo.storyboard */; }; + 821BC4B61DB8B3DC00172ED0 /* StoryboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 821BC4B51DB8B3DC00172ED0 /* StoryboardViewController.swift */; }; + 821BC4B81DB8B48300172ED0 /* StoryboardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 821BC4B71DB8B48300172ED0 /* StoryboardCell.swift */; }; + 821BC4BA1DB8B61200172ED0 /* StoryboardLabelSectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 821BC4B91DB8B61200172ED0 /* StoryboardLabelSectionController.swift */; }; + 82D91B691DBA0EF300E62758 /* SingleSectionStoryboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82D91B681DBA0EF300E62758 /* SingleSectionStoryboardViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -79,6 +84,11 @@ 299B53FF1D6BD6630074A202 /* SearchViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = ""; }; 4125DCD99578FDEF3C373BA0 /* Pods-IGListKitExamples.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IGListKitExamples.release.xcconfig"; path = "Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.release.xcconfig"; sourceTree = ""; }; 52A8DC2D07A93D7AA55BC993 /* Pods_IGListKitExamples.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_IGListKitExamples.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 821BC4B31DB8B25B00172ED0 /* Demo.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Demo.storyboard; sourceTree = ""; }; + 821BC4B51DB8B3DC00172ED0 /* StoryboardViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoryboardViewController.swift; sourceTree = ""; }; + 821BC4B71DB8B48300172ED0 /* StoryboardCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoryboardCell.swift; sourceTree = ""; }; + 821BC4B91DB8B61200172ED0 /* StoryboardLabelSectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoryboardLabelSectionController.swift; sourceTree = ""; }; + 82D91B681DBA0EF300E62758 /* SingleSectionStoryboardViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleSectionStoryboardViewController.swift; sourceTree = ""; }; FE05AB853448A0705AF80427 /* Pods-IGListKitExamples.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IGListKitExamples.debug.xcconfig"; path = "Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -114,6 +124,7 @@ 2942FF891D9F39E00015D24B /* RemoveSectionController.swift */, 2942FF8A1D9F39E00015D24B /* SearchSectionController.swift */, 2942FF8B1D9F39E00015D24B /* UserSectionController.swift */, + 821BC4B91DB8B61200172ED0 /* StoryboardLabelSectionController.swift */, 2981BA361DB869FF00A987F9 /* WorkingRangeSectionController.swift */, ); path = SectionControllers; @@ -148,6 +159,7 @@ 2942FF821D9F39E00015D24B /* SectionControllers */, 2961B3A31D68B0B5001C9451 /* ViewControllers */, 2961B3A61D68B0B5001C9451 /* Views */, + 822B29F21DB8AA4700010000 /* Storyboard */, ); path = IGListKitExamples; sourceTree = ""; @@ -162,7 +174,9 @@ 2991F9231D7BB89F00B0C58F /* NestedAdapterViewController.swift */, 299B53FF1D6BD6630074A202 /* SearchViewController.swift */, 26271C8D1DAE9D3F0073E116 /* SingleSectionViewController.swift */, + 821BC4B51DB8B3DC00172ED0 /* StoryboardViewController.swift */, 2981BA381DB874BB00A987F9 /* WorkingRangeViewController.swift */, + 82D91B681DBA0EF300E62758 /* SingleSectionStoryboardViewController.swift */, ); path = ViewControllers; sourceTree = ""; @@ -179,6 +193,7 @@ 2961B3A81D68B0B5001C9451 /* SpinnerCell.swift */, 26271C931DAE9F050073E116 /* NibCell.swift */, 26271C911DAE9EFC0073E116 /* NibCell.xib */, + 821BC4B71DB8B48300172ED0 /* StoryboardCell.swift */, 2981BA341DB868A500A987F9 /* ImageCell.swift */, ); path = Views; @@ -201,6 +216,15 @@ name = Pods; sourceTree = ""; }; + 822B29F21DB8AA4700010000 /* Storyboard */ = { + isa = PBXGroup; + children = ( + 821BC4B31DB8B25B00172ED0 /* Demo.storyboard */, + ); + name = Storyboard; + path = ..; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -267,6 +291,7 @@ 2961B3981D68B031001C9451 /* LaunchScreen.storyboard in Resources */, 26271C921DAE9EFC0073E116 /* NibCell.xib in Resources */, 2961B3951D68B031001C9451 /* Assets.xcassets in Resources */, + 821BC4B41DB8B25B00172ED0 /* Demo.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -332,6 +357,7 @@ 2942FF8C1D9F39E00015D24B /* DemoSectionController.swift in Sources */, 2981BA351DB868A500A987F9 /* ImageCell.swift in Sources */, 2942FF931D9F39E00015D24B /* SearchSectionController.swift in Sources */, + 82D91B691DBA0EF300E62758 /* SingleSectionStoryboardViewController.swift in Sources */, 2942FF911D9F39E00015D24B /* LabelSectionController.swift in Sources */, 2981BA391DB874BB00A987F9 /* WorkingRangeViewController.swift in Sources */, 2961B3AC1D68B0B5001C9451 /* LoadMoreViewController.swift in Sources */, @@ -347,13 +373,16 @@ 2942FF8D1D9F39E00015D24B /* EmbeddedSectionController.swift in Sources */, 2991F9281D7BB9EC00B0C58F /* EmbeddedCollectionViewCell.swift in Sources */, 2942FF8F1D9F39E00015D24B /* GridSectionController.swift in Sources */, + 821BC4B81DB8B48300172ED0 /* StoryboardCell.swift in Sources */, 2942FF921D9F39E00015D24B /* RemoveSectionController.swift in Sources */, 26271C8E1DAE9D3F0073E116 /* SingleSectionViewController.swift in Sources */, 2961B3AD1D68B0B5001C9451 /* LabelCell.swift in Sources */, 2942FF901D9F39E00015D24B /* HorizontalSectionController.swift in Sources */, 2981BA371DB869FF00A987F9 /* WorkingRangeSectionController.swift in Sources */, 2961B3AB1D68B0B5001C9451 /* DemosViewController.swift in Sources */, + 821BC4B61DB8B3DC00172ED0 /* StoryboardViewController.swift in Sources */, 2942FF8E1D9F39E00015D24B /* ExpandableSectionController.swift in Sources */, + 821BC4BA1DB8B61200172ED0 /* StoryboardLabelSectionController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/IGListKitExamples/SectionControllers/DemoSectionController.swift b/Example/IGListKitExamples/SectionControllers/DemoSectionController.swift index 99d8805db..6b84f019b 100644 --- a/Example/IGListKitExamples/SectionControllers/DemoSectionController.swift +++ b/Example/IGListKitExamples/SectionControllers/DemoSectionController.swift @@ -19,13 +19,16 @@ class DemoItem: NSObject { let name: String let controllerClass: UIViewController.Type + let controllerIdentifier: String? init( name: String, - controllerClass: UIViewController.Type + controllerClass: UIViewController.Type, + controllerIdentifier: String? = nil ) { self.name = name self.controllerClass = controllerClass + self.controllerIdentifier = controllerIdentifier } } @@ -53,7 +56,12 @@ class DemoSectionController: IGListSectionController, IGListSectionType { } func didSelectItem(at index: Int) { - if let controller = object?.controllerClass.init() { + if let identifier = object?.controllerIdentifier { + let storyboard = UIStoryboard(name: "Demo", bundle: nil) + let controller = storyboard.instantiateViewController(withIdentifier: identifier) + controller.title = object?.name + viewController?.navigationController?.pushViewController(controller, animated: true) + } else if let controller = object?.controllerClass.init() { controller.title = object?.name viewController?.navigationController?.pushViewController(controller, animated: true) } diff --git a/Example/IGListKitExamples/SectionControllers/StoryboardLabelSectionController.swift b/Example/IGListKitExamples/SectionControllers/StoryboardLabelSectionController.swift new file mode 100644 index 000000000..154401e29 --- /dev/null +++ b/Example/IGListKitExamples/SectionControllers/StoryboardLabelSectionController.swift @@ -0,0 +1,42 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import UIKit +import IGListKit + +class StoryboardLabelSectionController: IGListSectionController, IGListSectionType { + + var object: String? + + func numberOfItems() -> Int { + return 1 + } + + func sizeForItem(at index: Int) -> CGSize { + return CGSize(width: collectionContext!.containerSize.width, height: 55) + } + + func cellForItem(at index: Int) -> UICollectionViewCell { + let cell = collectionContext!.dequeueReusableCellFromStoryboard(withIdentifier: "cell", for: self, at: index) as! StoryboardCell + cell.textLabel.text = object + return cell + } + + func didUpdate(to object: Any) { + self.object = object as? String + } + + func didSelectItem(at index: Int) {} + +} diff --git a/Example/IGListKitExamples/ViewControllers/DemosViewController.swift b/Example/IGListKitExamples/ViewControllers/DemosViewController.swift index 43fac76df..2e354d0b7 100644 --- a/Example/IGListKitExamples/ViewControllers/DemosViewController.swift +++ b/Example/IGListKitExamples/ViewControllers/DemosViewController.swift @@ -29,6 +29,8 @@ class DemosViewController: UIViewController, IGListAdapterDataSource { DemoItem(name: "Nested Adapter", controllerClass: NestedAdapterViewController.self), DemoItem(name: "Empty View", controllerClass: EmptyViewController.self), DemoItem(name: "Single Section Controller", controllerClass: SingleSectionViewController.self), + DemoItem(name: "Storyboard", controllerClass: SingleSectionViewController.self, controllerIdentifier: "demo"), + DemoItem(name: "Single Section Storyboard", controllerClass: SingleSectionViewController.self, controllerIdentifier: "singleSectionDemo"), DemoItem(name: "Working Range", controllerClass: WorkingRangeViewController.self) ] diff --git a/Example/IGListKitExamples/ViewControllers/SingleSectionStoryboardViewController.swift b/Example/IGListKitExamples/ViewControllers/SingleSectionStoryboardViewController.swift new file mode 100644 index 000000000..045c6cb86 --- /dev/null +++ b/Example/IGListKitExamples/ViewControllers/SingleSectionStoryboardViewController.swift @@ -0,0 +1,71 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import UIKit +import IGListKit + +final class SingleSectionStoryboardViewController: UIViewController, IGListAdapterDataSource, IGListSingleSectionControllerDelegate { + + lazy var adapter: IGListAdapter = { + return IGListAdapter(updater: IGListAdapterUpdater(), viewController: self, workingRangeSize: 0) + }() + + @IBOutlet weak var collectionView: IGListCollectionView! + + let data = Array(0..<20) + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + adapter.collectionView = collectionView + adapter.dataSource = self + } + + //MARK: - IGListAdapterDataSource + + func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] { + return data as [IGListDiffable] + } + + func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController { + let configureBlock = { (data: Any, cell: UICollectionViewCell) in + guard let cell = cell as? StoryboardCell, let number = data as? Int else { return } + cell.textLabel.text = "Cell: \(number + 1)" + } + let sizeBlock = { (context: IGListCollectionContext?) -> CGSize in + guard let context = context else { return .zero } + return CGSize(width: context.containerSize.width, height: 44) + } + let sectionController = IGListSingleSectionController(storyboardCellIdentifier: "cell", + configureBlock: configureBlock, + sizeBlock: sizeBlock) + sectionController.selectionDelegate = self + return sectionController + } + + func emptyView(for listAdapter: IGListAdapter) -> UIView? { + return nil + } + + // MARK: - IGListSingleSectionControllerDelegate + + func didSelect(_ sectionController: IGListSingleSectionController) { + let section = adapter.section(for: sectionController) + 1 + let alert = UIAlertController(title: "Section \(section) was selected \u{1F389}", message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Dismiss", style: .default, handler: nil)) + present(alert, animated: true, completion: nil) + } + +} diff --git a/Example/IGListKitExamples/ViewControllers/SingleSectionViewController.swift b/Example/IGListKitExamples/ViewControllers/SingleSectionViewController.swift index b19e6619b..782706b91 100644 --- a/Example/IGListKitExamples/ViewControllers/SingleSectionViewController.swift +++ b/Example/IGListKitExamples/ViewControllers/SingleSectionViewController.swift @@ -65,15 +65,13 @@ final class SingleSectionViewController: UIViewController, IGListAdapterDataSour return sectionController } - func emptyView(for listAdapter: IGListAdapter) -> UIView? { - return nil - } + func emptyView(for listAdapter: IGListAdapter) -> UIView? { return nil } // MARK: - IGListSingleSectionControllerDelegate func didSelect(_ sectionController: IGListSingleSectionController) { let section = adapter.section(for: sectionController) + 1 - let alert = UIAlertController(title: "Section \(section) was selected 🎉", message: nil, preferredStyle: .alert) + let alert = UIAlertController(title: "Section \(section) was selected \u{1F389}", message: nil, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Dismiss", style: .default, handler: nil)) present(alert, animated: true, completion: nil) } diff --git a/Example/IGListKitExamples/ViewControllers/StoryboardViewController.swift b/Example/IGListKitExamples/ViewControllers/StoryboardViewController.swift new file mode 100644 index 000000000..38b006514 --- /dev/null +++ b/Example/IGListKitExamples/ViewControllers/StoryboardViewController.swift @@ -0,0 +1,46 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import UIKit +import IGListKit + +class StoryboardViewController: UIViewController, IGListAdapterDataSource { + + @IBOutlet weak var collectionView: IGListCollectionView! + + lazy var adapter: IGListAdapter = { + return IGListAdapter(updater: IGListAdapterUpdater(), viewController: self, workingRangeSize: 0) + }() + + lazy var words = "Maecenas faucibus mollis interdum Praesent commodo cursus magna, vel scelerisque nisl consectetur et".components(separatedBy: " ") + + override func viewDidLoad() { + super.viewDidLoad() + adapter.collectionView = collectionView + adapter.dataSource = self + } + + //MARK: IGListAdapterDataSource + + func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] { + let items: [IGListDiffable] = words as [IGListDiffable] + return items + } + + func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController { + return StoryboardLabelSectionController() + } + + func emptyView(for listAdapter: IGListAdapter) -> UIView? { return nil } +} diff --git a/Example/IGListKitExamples/Views/StoryboardCell.swift b/Example/IGListKitExamples/Views/StoryboardCell.swift new file mode 100644 index 000000000..84d78316a --- /dev/null +++ b/Example/IGListKitExamples/Views/StoryboardCell.swift @@ -0,0 +1,19 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import UIKit + +class StoryboardCell: UICollectionViewCell { + @IBOutlet weak var textLabel: UILabel! +} diff --git a/IGListKit.xcodeproj/project.pbxproj b/IGListKit.xcodeproj/project.pbxproj index c1472c8be..11f96c307 100644 --- a/IGListKit.xcodeproj/project.pbxproj +++ b/IGListKit.xcodeproj/project.pbxproj @@ -23,6 +23,11 @@ 296177061D9D54E300F40F34 /* IGListStackedSectionControllerInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 296177021D9D54E300F40F34 /* IGListStackedSectionControllerInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; 29EA6C491DB43A8000957A88 /* IGTestNibCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 294369B01DB1B7AE0025F6E7 /* IGTestNibCell.xib */; }; 5C81083F8E7AEF4B3EBE8871 /* Pods_IGListKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD40284889DE182FFC7F471E /* Pods_IGListKitTests.framework */; }; + 821BC4C01DB8C9D500172ED0 /* IGListSingleStoryboardItemControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 821BC4BE1DB8C95300172ED0 /* IGListSingleStoryboardItemControllerTests.m */; }; + 821BC4C41DB8CEF800172ED0 /* IGTestStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 821BC4C21DB8CAE900172ED0 /* IGTestStoryboard.storyboard */; }; + 821BC4CB1DB8D60100172ED0 /* IGTestSingleStoryboardViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 821BC4C81DB8D5B200172ED0 /* IGTestSingleStoryboardViewController.m */; }; + 821BC4D01DB8D90900172ED0 /* IGTestStoryboardCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 821BC4CD1DB8D8C500172ED0 /* IGTestStoryboardCell.m */; }; + 821BC4D31DB981AB00172ED0 /* IGTestSingleStoryboardItemDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 821BC4D21DB981AB00172ED0 /* IGTestSingleStoryboardItemDataSource.m */; }; 88144F071D870EDC007C7F66 /* IGListAdapterE2ETests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EE21D870EDC007C7F66 /* IGListAdapterE2ETests.m */; }; 88144F081D870EDC007C7F66 /* IGListAdapterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EE31D870EDC007C7F66 /* IGListAdapterTests.m */; }; 88144F091D870EDC007C7F66 /* IGListAdapterUpdaterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EE41D870EDC007C7F66 /* IGListAdapterUpdaterTests.m */; }; @@ -128,6 +133,14 @@ 296177011D9D54E300F40F34 /* IGListSectionMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListSectionMap.m; sourceTree = ""; }; 296177021D9D54E300F40F34 /* IGListStackedSectionControllerInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListStackedSectionControllerInternal.h; sourceTree = ""; }; 6BCA3FF59943AD1DAC2077E3 /* Pods-IGListKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IGListKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-IGListKitTests/Pods-IGListKitTests.release.xcconfig"; sourceTree = ""; }; + 821BC4BE1DB8C95300172ED0 /* IGListSingleStoryboardItemControllerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListSingleStoryboardItemControllerTests.m; sourceTree = ""; }; + 821BC4C21DB8CAE900172ED0 /* IGTestStoryboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = IGTestStoryboard.storyboard; sourceTree = ""; }; + 821BC4C71DB8D5B200172ED0 /* IGTestSingleStoryboardViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGTestSingleStoryboardViewController.h; sourceTree = ""; }; + 821BC4C81DB8D5B200172ED0 /* IGTestSingleStoryboardViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGTestSingleStoryboardViewController.m; sourceTree = ""; }; + 821BC4CC1DB8D8C500172ED0 /* IGTestStoryboardCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGTestStoryboardCell.h; sourceTree = ""; }; + 821BC4CD1DB8D8C500172ED0 /* IGTestStoryboardCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGTestStoryboardCell.m; sourceTree = ""; }; + 821BC4D11DB9816E00172ED0 /* IGTestSingleStoryboardItemDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGTestSingleStoryboardItemDataSource.h; sourceTree = ""; }; + 821BC4D21DB981AB00172ED0 /* IGTestSingleStoryboardItemDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGTestSingleStoryboardItemDataSource.m; sourceTree = ""; }; 88144EE21D870EDC007C7F66 /* IGListAdapterE2ETests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListAdapterE2ETests.m; sourceTree = ""; }; 88144EE31D870EDC007C7F66 /* IGListAdapterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListAdapterTests.m; sourceTree = ""; }; 88144EE41D870EDC007C7F66 /* IGListAdapterUpdaterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListAdapterUpdaterTests.m; sourceTree = ""; }; @@ -246,6 +259,7 @@ isa = PBXGroup; children = ( 294369B01DB1B7AE0025F6E7 /* IGTestNibCell.xib */, + 821BC4C21DB8CAE900172ED0 /* IGTestStoryboard.storyboard */, ); path = Assets; sourceTree = ""; @@ -272,6 +286,8 @@ 88144EF81D870EDC007C7F66 /* IGListTestUICollectionViewDataSource.m */, 88144EF91D870EDC007C7F66 /* IGTestCell.h */, 88144EFA1D870EDC007C7F66 /* IGTestCell.m */, + 821BC4CC1DB8D8C500172ED0 /* IGTestStoryboardCell.h */, + 821BC4CD1DB8D8C500172ED0 /* IGTestStoryboardCell.m */, 88144EFB1D870EDC007C7F66 /* IGTestDelegateController.h */, 88144EFC1D870EDC007C7F66 /* IGTestDelegateController.m */, 88144EFD1D870EDC007C7F66 /* IGTestDelegateDataSource.h */, @@ -282,6 +298,10 @@ 88144F021D870EDC007C7F66 /* IGTestSingleItemDataSource.m */, 26271C881DAE94E40073E116 /* IGTestSingleNibItemDataSource.h */, 26271C891DAE94E40073E116 /* IGTestSingleNibItemDataSource.m */, + 821BC4D11DB9816E00172ED0 /* IGTestSingleStoryboardItemDataSource.h */, + 821BC4D21DB981AB00172ED0 /* IGTestSingleStoryboardItemDataSource.m */, + 821BC4C71DB8D5B200172ED0 /* IGTestSingleStoryboardViewController.h */, + 821BC4C81DB8D5B200172ED0 /* IGTestSingleStoryboardViewController.m */, 88144F031D870EDC007C7F66 /* IGTestStackedDataSource.h */, 88144F041D870EDC007C7F66 /* IGTestStackedDataSource.m */, 88144F051D870EDC007C7F66 /* IGTestSupplementarySource.h */, @@ -405,6 +425,7 @@ 88144EEC1D870EDC007C7F66 /* IGListObjectMapTests.m */, 88144EED1D870EDC007C7F66 /* IGListSingleItemControllerTests.m */, 26271C8B1DAE96740073E116 /* IGListSingleNibItemControllerTests.m */, + 821BC4BE1DB8C95300172ED0 /* IGListSingleStoryboardItemControllerTests.m */, 88144EEE1D870EDC007C7F66 /* IGListStackItemControllerTests.m */, 88144EEF1D870EDC007C7F66 /* IGListWorkingRangeHandlerTests.m */, 887D0B571D870E1E009E01F7 /* Info.plist */, @@ -569,6 +590,7 @@ buildActionMask = 2147483647; files = ( 29EA6C491DB43A8000957A88 /* IGTestNibCell.xib in Resources */, + 821BC4C41DB8CEF800172ED0 /* IGTestStoryboard.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -660,6 +682,7 @@ 88144F1B1D870EDC007C7F66 /* IGTestSingleItemDataSource.m in Sources */, 88144F0E1D870EDC007C7F66 /* IGListIndexResultTests.m in Sources */, 88144F171D870EDC007C7F66 /* IGTestCell.m in Sources */, + 821BC4C01DB8C9D500172ED0 /* IGListSingleStoryboardItemControllerTests.m in Sources */, 88144F141D870EDC007C7F66 /* IGListTestOffsettingLayout.m in Sources */, 88144F131D870EDC007C7F66 /* IGListTestAdapterDataSource.m in Sources */, 88144F071D870EDC007C7F66 /* IGListAdapterE2ETests.m in Sources */, @@ -672,9 +695,12 @@ 26271C8C1DAE96740073E116 /* IGListSingleNibItemControllerTests.m in Sources */, 88144F101D870EDC007C7F66 /* IGListSingleItemControllerTests.m in Sources */, 88144F121D870EDC007C7F66 /* IGListWorkingRangeHandlerTests.m in Sources */, + 821BC4D31DB981AB00172ED0 /* IGTestSingleStoryboardItemDataSource.m in Sources */, 88144F151D870EDC007C7F66 /* IGListTestSection.m in Sources */, 88144F1D1D870EDC007C7F66 /* IGTestSupplementarySource.m in Sources */, 88144F081D870EDC007C7F66 /* IGListAdapterTests.m in Sources */, + 821BC4CB1DB8D60100172ED0 /* IGTestSingleStoryboardViewController.m in Sources */, + 821BC4D01DB8D90900172ED0 /* IGTestStoryboardCell.m in Sources */, 88144F161D870EDC007C7F66 /* IGListTestUICollectionViewDataSource.m in Sources */, 88144F091D870EDC007C7F66 /* IGListAdapterUpdaterTests.m in Sources */, 88144F0F1D870EDC007C7F66 /* IGListObjectMapTests.m in Sources */, diff --git a/Source/IGListAdapter.m b/Source/IGListAdapter.m index e35ad6b66..2a3b90f54 100644 --- a/Source/IGListAdapter.m +++ b/Source/IGListAdapter.m @@ -727,6 +727,18 @@ - (__kindof UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass return [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath]; } +- (__kindof UICollectionViewCell *)dequeueReusableCellFromStoryboardWithIdentifier:(NSString *)identifier + forSectionController:(IGListSectionController *)sectionController + atIndex:(NSInteger)index { + IGAssertMainThread(); + IGParameterAssert(sectionController != nil); + IGParameterAssert(identifier.length > 0); + UICollectionView *collectionView = self.collectionView; + IGAssert(collectionView != nil, @"Reloading adapter without a collection view."); + NSIndexPath *indexPath = [self indexPathForSectionController:sectionController index:index]; + return [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath]; +} + - (UICollectionViewCell *)dequeueReusableCellWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle forSectionController:(IGListSectionController *)sectionController diff --git a/Source/IGListCollectionContext.h b/Source/IGListCollectionContext.h index aeaf8db88..28ba5dc80 100644 --- a/Source/IGListCollectionContext.h +++ b/Source/IGListCollectionContext.h @@ -108,6 +108,21 @@ NS_ASSUME_NONNULL_BEGIN forSectionController:(IGListSectionController *)sectionController atIndex:(NSInteger)index; +/** + Dequeues a storyboard prototype cell from the UICollectionView reuse pool. + + @param identifier The identifier of the cell prototype in storyboard. + @param sectionController The section controller requesting this information. + @param index The index of the cell. + + @return A cell dequeued from the reuse pool or newly created. + + @note This method uses a string representation of the cell class as the identifier. + */ +- (__kindof UICollectionViewCell *)dequeueReusableCellFromStoryboardWithIdentifier:(NSString *)identifier + forSectionController:(IGListSectionController *)sectionController + atIndex:(NSInteger)index; + /** Dequeues a supplementary view from the UICollectionView reuse pool. diff --git a/Source/IGListSingleSectionController.h b/Source/IGListSingleSectionController.h index 3ea83c578..1c939a2ac 100644 --- a/Source/IGListSingleSectionController.h +++ b/Source/IGListSingleSectionController.h @@ -76,6 +76,22 @@ IGLK_SUBCLASSING_RESTRICTED configureBlock:(IGListSingleSectionCellConfigureBlock)configureBlock sizeBlock:(IGListSingleSectionCellSizeBlock)sizeBlock; +/** + Create a new section controller for a given storyboard cell identifier that will always have only one cell when present in a feed. + + @param identifier The identifier of the cell prototype in storyboard. + @param configureBlock A block that configures the cell with the item given to the section controller. + @param sizeBlock A block that returns the size for the cell given the collection context. + + @return A new section controller. + + @warning Be VERY CAREFUL not to create retain cycles by holding strong references to: the object that owns the adapter + (usually "self") or the IGListAdapter. Pass in locally scoped objects or use weak references! + */ +- (instancetype)initWithStoryboardCellIdentifier:(NSString *)identifier + configureBlock:(IGListSingleSectionCellConfigureBlock)configureBlock + sizeBlock:(IGListSingleSectionCellSizeBlock)sizeBlock; + /** An optional delegate that handles selection and deselection. */ diff --git a/Source/IGListSingleSectionController.m b/Source/IGListSingleSectionController.m index 26109d6ca..8cdc4c221 100644 --- a/Source/IGListSingleSectionController.m +++ b/Source/IGListSingleSectionController.m @@ -15,6 +15,7 @@ @interface IGListSingleSectionController () @property (nonatomic, strong, readonly) NSString *nibName; @property (nonatomic, strong, readonly) NSBundle *bundle; +@property (nonatomic, strong, readonly) NSString *identifier; @property (nonatomic, strong, readonly) Class cellClass; @property (nonatomic, strong, readonly) IGListSingleSectionCellConfigureBlock configureBlock; @property (nonatomic, strong, readonly) IGListSingleSectionCellSizeBlock sizeBlock; @@ -30,6 +31,7 @@ - (instancetype)initWithCellClass:(Class)cellClass sizeBlock:(IGListSingleSectionCellSizeBlock)sizeBlock { IGParameterAssert(cellClass != nil); IGParameterAssert(configureBlock != nil); + IGParameterAssert(sizeBlock != nil); if (self = [super init]) { _cellClass = cellClass; _configureBlock = [configureBlock copy]; @@ -44,6 +46,7 @@ - (instancetype)initWithNibName:(NSString *)nibName sizeBlock:(IGListSingleSectionCellSizeBlock)sizeBlock { IGParameterAssert(nibName != nil); IGParameterAssert(configureBlock != nil); + IGParameterAssert(sizeBlock != nil); if (self = [super init]) { _nibName = nibName; _bundle = bundle; @@ -53,6 +56,21 @@ - (instancetype)initWithNibName:(NSString *)nibName return self; } +- (instancetype)initWithStoryboardCellIdentifier:(NSString *)identifier + configureBlock:(IGListSingleSectionCellConfigureBlock)configureBlock + sizeBlock:(IGListSingleSectionCellSizeBlock)sizeBlock { + IGParameterAssert(identifier.length > 0); + IGParameterAssert(configureBlock != nil); + IGParameterAssert(sizeBlock != nil); + if (self = [super init]) { + _identifier = [identifier copy]; + _configureBlock = [configureBlock copy]; + _sizeBlock = [sizeBlock copy]; + } + return self; + +} + #pragma mark - IGListSectionType - (NSInteger)numberOfItems { @@ -72,6 +90,10 @@ - (UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index { bundle:self.bundle forSectionController:self atIndex:index]; + } else if ([self.identifier length] > 0) { + cell = [collectionContext dequeueReusableCellFromStoryboardWithIdentifier:self.identifier + forSectionController:self + atIndex:index]; } else { cell = [collectionContext dequeueReusableCellOfClass:self.cellClass forSectionController:self atIndex:index]; } diff --git a/Source/IGListStackedSectionController.m b/Source/IGListStackedSectionController.m index c3b3a9139..cf4e480bb 100644 --- a/Source/IGListStackedSectionController.m +++ b/Source/IGListStackedSectionController.m @@ -199,8 +199,7 @@ - (UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass - (UICollectionViewCell *)dequeueReusableCellWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle forSectionController:(IGListSectionController *)sectionController - atIndex:(NSInteger)index -{ + atIndex:(NSInteger)index { const NSUInteger offset = [self offsetForSectionController:sectionController]; return (UICollectionViewCell *_Nonnull)[self.collectionContext dequeueReusableCellWithNibName:nibName bundle:bundle @@ -208,6 +207,15 @@ - (UICollectionViewCell *)dequeueReusableCellWithNibName:(NSString *)nibName atIndex:(index + offset)]; } +- (UICollectionViewCell *)dequeueReusableCellFromStoryboardWithIdentifier:(NSString *)identifier + forSectionController:(IGListSectionController *)sectionController + atIndex:(NSInteger)index { + const NSUInteger offset = [self offsetForSectionController:sectionController]; + return (UICollectionViewCell *_Nonnull)[self.collectionContext dequeueReusableCellFromStoryboardWithIdentifier:identifier + forSectionController:self + atIndex:(index + offset)]; +} + - (UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind forSectionController:(IGListSectionController *)sectionController class:(Class)viewClass diff --git a/Tests/Assets/IGTestStoryboard.storyboard b/Tests/Assets/IGTestStoryboard.storyboard new file mode 100644 index 000000000..4a6be3b16 --- /dev/null +++ b/Tests/Assets/IGTestStoryboard.storyboard @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/IGListSingleStoryboardItemControllerTests.m b/Tests/IGListSingleStoryboardItemControllerTests.m new file mode 100644 index 000000000..a939ec8d3 --- /dev/null +++ b/Tests/IGListSingleStoryboardItemControllerTests.m @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "IGTestStoryboardCell.h" +#import "IGTestSingleStoryboardItemDataSource.h" +#import "IGTestSingleStoryboardViewController.h" + +#define genTestObject(k, v) [[IGTestObject alloc] initWithKey:k value:v] + +#define genExpectation [self expectationWithDescription:NSStringFromSelector(_cmd)] + +@interface IGListSingleStoryboardItemControllerTests : XCTestCase + +@property (nonatomic, strong) IGListCollectionView *collectionView; +@property (nonatomic, strong) IGListAdapter *adapter; +@property (nonatomic, strong) IGListAdapterUpdater *updater; +@property (nonatomic, strong) IGTestSingleStoryboardItemDataSource *dataSource; +@property (nonatomic, strong) IGTestSingleStoryboardViewController *viewController; +@property (nonatomic, strong) UIWindow *window; + +@end + +@implementation IGListSingleStoryboardItemControllerTests + +- (void)setUp { + [super setUp]; + self.window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; + UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"IGTestStoryboard" bundle:[NSBundle bundleForClass:self.class]]; + self.viewController = [storyboard instantiateViewControllerWithIdentifier:@"testVC"]; + [self.window addSubview:self.viewController.view]; + [self.viewController performSelectorOnMainThread:@selector(loadView) withObject:nil waitUntilDone:YES]; + self.collectionView = self.viewController.collectionView; + self.dataSource = [[IGTestSingleStoryboardItemDataSource alloc] init]; + self.updater = [[IGListAdapterUpdater alloc] init]; + self.adapter = [[IGListAdapter alloc] initWithUpdater:self.updater viewController:self.viewController workingRangeSize:2]; +} + +- (void)tearDown { + [super tearDown]; + self.window = nil; + self.viewController = nil; + self.collectionView = nil; + self.adapter = nil; +} + +- (void)setupWithObjects:(NSArray *)objects { + self.dataSource.objects = objects; + self.adapter.collectionView = self.viewController.collectionView; + self.adapter.dataSource = self.dataSource; + [self.collectionView layoutIfNeeded]; +} + +- (void)test_whenDisplayingCollectionView_thatSectionsHaveOneItem { + [self setupWithObjects:@[ + genTestObject(@1, @"Foo"), + genTestObject(@2, @"Bar"), + genTestObject(@3, @"Baz"), + ]]; + XCTAssertEqual([self.collectionView numberOfSections], 3); + XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 1); + XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 1); + XCTAssertEqual([self.collectionView numberOfItemsInSection:2], 1); +} + +- (void)test_whenDisplayingCollectionView_thatCellsAreConfigured { + [self setupWithObjects:@[ + genTestObject(@1, @"Foo"), + genTestObject(@2, @"Bar"), + genTestObject(@3, @"Baz"), + ]]; + IGTestStoryboardCell *cell1 = (IGTestStoryboardCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + IGTestStoryboardCell *cell2 = (IGTestStoryboardCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]; + IGTestStoryboardCell *cell3 = (IGTestStoryboardCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:2]]; + XCTAssertEqualObjects(cell1.label.text, @"Foo"); + XCTAssertEqualObjects(cell2.label.text, @"Bar"); + XCTAssertEqualObjects(cell3.label.text, @"Baz"); +} + +- (void)test_whenDisplayingCollectionView_thatCellsAreSized { + [self setupWithObjects:@[ + genTestObject(@1, @"Foo"), + genTestObject(@2, @"Bar"), + genTestObject(@3, @"Baz"), + ]]; + IGTestStoryboardCell *cell1 = (IGTestStoryboardCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + IGTestStoryboardCell *cell2 = (IGTestStoryboardCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]; + IGTestStoryboardCell *cell3 = (IGTestStoryboardCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:2]]; + XCTAssertEqual(cell1.frame.size.height, 44); + XCTAssertEqual(cell2.frame.size.height, 44); + XCTAssertEqual(cell3.frame.size.height, 44); +} + +- (void)test_whenItemUpdated_thatCellIsConfigured { + [self setupWithObjects:@[ + genTestObject(@1, @"Foo"), + genTestObject(@2, @"Bar"), + genTestObject(@3, @"Baz"), + ]]; + self.dataSource.objects = @[ + genTestObject(@1, @"Foo"), + genTestObject(@2, @"Qux"), // new value + genTestObject(@3, @"Baz"), + ]; + XCTestExpectation *expectation = genExpectation; + [self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) { + IGTestStoryboardCell *cell2 = (IGTestStoryboardCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]; + XCTAssertEqualObjects(cell2.label.text, @"Qux"); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:15 handler:nil]; +} + +@end diff --git a/Tests/Objects/IGTestSingleStoryboardItemDataSource.h b/Tests/Objects/IGTestSingleStoryboardItemDataSource.h new file mode 100644 index 000000000..5af9dd99d --- /dev/null +++ b/Tests/Objects/IGTestSingleStoryboardItemDataSource.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +#import "IGTestObject.h" + +@interface IGTestSingleStoryboardItemDataSource : NSObject + +@property (nonatomic, strong) NSArray *objects; + +@end diff --git a/Tests/Objects/IGTestSingleStoryboardItemDataSource.m b/Tests/Objects/IGTestSingleStoryboardItemDataSource.m new file mode 100644 index 000000000..fe19ef892 --- /dev/null +++ b/Tests/Objects/IGTestSingleStoryboardItemDataSource.m @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGTestSingleStoryboardItemDataSource.h" + +#import + +#import "IGTestStoryboardCell.h" + +@implementation IGTestSingleStoryboardItemDataSource + +- (NSArray> *)objectsForListAdapter:(IGListAdapter *)listAdapter { + return self.objects; +} + +- (IGListSectionController *)listAdapter:(IGListAdapter *)listAdapter sectionControllerForObject:(id)object +{ + void (^configureBlock)(id, __kindof UICollectionViewCell *) = ^(IGTestObject *item, IGTestStoryboardCell *cell) { + cell.label.text = [item.value description]; + }; + CGSize (^sizeBlock)(id) = ^CGSize(id collectionContext) { + return CGSizeMake([collectionContext containerSize].width, 44); + }; + return [[IGListSingleSectionController alloc] initWithStoryboardCellIdentifier:@"IGTestStoryboardCell" + configureBlock:configureBlock + sizeBlock:sizeBlock]; +} + +- (UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter { + return nil; +} + +@end diff --git a/Tests/Objects/IGTestSingleStoryboardViewController.h b/Tests/Objects/IGTestSingleStoryboardViewController.h new file mode 100644 index 000000000..85939fd78 --- /dev/null +++ b/Tests/Objects/IGTestSingleStoryboardViewController.h @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "IGListCollectionView.h" + +#import + +#import "IGTestObject.h" + +@interface IGTestSingleStoryboardViewController : UIViewController + +@property (weak, nonatomic) IBOutlet IGListCollectionView *collectionView; + +@end diff --git a/Tests/Objects/IGTestSingleStoryboardViewController.m b/Tests/Objects/IGTestSingleStoryboardViewController.m new file mode 100644 index 000000000..1ffead515 --- /dev/null +++ b/Tests/Objects/IGTestSingleStoryboardViewController.m @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGTestSingleStoryboardViewController.h" + +#import + +#import "IGTestStoryboardCell.h" + +@interface IGTestSingleStoryboardViewController () + +@end + +@implementation IGTestSingleStoryboardViewController + +@end diff --git a/Tests/Objects/IGTestStoryboardCell.h b/Tests/Objects/IGTestStoryboardCell.h new file mode 100644 index 000000000..e8d8b3cdb --- /dev/null +++ b/Tests/Objects/IGTestStoryboardCell.h @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@interface IGTestStoryboardCell : UICollectionViewCell + +@property (nonatomic, weak) id delegate; +@property (weak, nonatomic) IBOutlet UILabel *label; + +@end diff --git a/Tests/Objects/IGTestStoryboardCell.m b/Tests/Objects/IGTestStoryboardCell.m new file mode 100644 index 000000000..262d377f3 --- /dev/null +++ b/Tests/Objects/IGTestStoryboardCell.m @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "IGTestStoryboardCell.h" + +@implementation IGTestStoryboardCell + +@end