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