From 10bdfb23f94886d5ba6fc875bf13884e273117bd Mon Sep 17 00:00:00 2001 From: Ryan Nystrom Date: Mon, 5 Dec 2016 17:38:56 -0800 Subject: [PATCH] Add more unit tests to stack section controller Summary: Beefing up our test coverage. Made an improvement to supplementary view behavior along the way. Will update `CHANGELOG.md` once travis finishes w/ link to PR #. - [x] All tests pass. Demo project builds and runs. - [x] I added tests, an experiment, or detailed why my change isn't tested. - [x] I have reviewed the [contributing guide](https://github.com/Instagram/IGListKit/blob/master/.github/CONTRIBUTING.md) Closes https://github.com/Instagram/IGListKit/pull/286 Differential Revision: D4281961 Pulled By: jessesquires fbshipit-source-id: 32b5877bd72250b9a99e600ceffc64d686fa5651 --- CHANGELOG.md | 4 + IGListKit.xcodeproj/project.pbxproj | 4 +- Source/IGListStackedSectionController.m | 44 +--- Tests/IGListStackSectionControllerTests.m | 261 +++++++++++++++++++++- Tests/Objects/IGTestStackedDataSource.m | 33 ++- 5 files changed, 304 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 438f2e5c..2ff651e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -106,6 +106,10 @@ This release closes the [2.0.0 milestone](https://github.com/Instagram/IGListKit - Added `tvOS` example pack. [Sherlouk](https://github.com/Sherlouk) [(#141)](https://github.com/Instagram/IGListKit/pull/141) +- Fixed a bug where `IGListStackSectionController` would only set its supplementary source once. [Ryan Nystrom](https://github.com/rnystrom) [(#286)](https://github.com/Instagram/IGListKit/pull/286) + +- Fixed a bug where `IGListStackSectionController` passed the wrong section controller for will-drag scroll events. [Ryan Nystrom](https://github.com/rnystrom) [(#286)](https://github.com/Instagram/IGListKit/pull/286) + 1.0.0 ----- diff --git a/IGListKit.xcodeproj/project.pbxproj b/IGListKit.xcodeproj/project.pbxproj index 1612c6fe..7454809e 100644 --- a/IGListKit.xcodeproj/project.pbxproj +++ b/IGListKit.xcodeproj/project.pbxproj @@ -455,12 +455,12 @@ 8285404F1DE40D2D00118B94 /* IGListTestAdapterHorizontalDataSource.m */, 8240C7F91DC2F6CF00B3AAE7 /* IGListTestAdapterStoryboardDataSource.h */, 8240C7FA1DC2F6CF00B3AAE7 /* IGListTestAdapterStoryboardDataSource.m */, + 8285404A1DE40C6E00118B94 /* IGListTestHorizontalSection.h */, + 8285404B1DE40C6E00118B94 /* IGListTestHorizontalSection.m */, 88144EF31D870EDC007C7F66 /* IGListTestOffsettingLayout.h */, 88144EF41D870EDC007C7F66 /* IGListTestOffsettingLayout.m */, 88144EF51D870EDC007C7F66 /* IGListTestSection.h */, 88144EF61D870EDC007C7F66 /* IGListTestSection.m */, - 8285404A1DE40C6E00118B94 /* IGListTestHorizontalSection.h */, - 8285404B1DE40C6E00118B94 /* IGListTestHorizontalSection.m */, 8240C7F61DC2F3FB00B3AAE7 /* IGListTestStoryboardSection.h */, 8240C7F71DC2F3FB00B3AAE7 /* IGListTestStoryboardSection.m */, 88144EF71D870EDC007C7F66 /* IGListTestUICollectionViewDataSource.h */, diff --git a/Source/IGListStackedSectionController.m b/Source/IGListStackedSectionController.m index 93d39fdc..366a3f25 100644 --- a/Source/IGListStackedSectionController.m +++ b/Source/IGListStackedSectionController.m @@ -16,32 +16,6 @@ #import "IGListSectionControllerInternal.h" -@interface UICollectionViewCell (IGListStackedSectionController) -@end -@implementation UICollectionViewCell (IGListStackedSectionController) - -static void * kStackedSectionControllerKey = &kStackedSectionControllerKey; - -- (void)ig_setStackedSectionController:(id)stackedSectionController { - objc_setAssociatedObject(self, kStackedSectionControllerKey, stackedSectionController, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -- (id)ig_stackedSectionController { - return objc_getAssociatedObject(self, kStackedSectionControllerKey); -} - -static void * kStackedSectionControllerIndexKey = &kStackedSectionControllerIndexKey; - -- (void)ig_setStackedSectionControllerIndex:(NSInteger)stackedSectionControllerIndex { - objc_setAssociatedObject(self, kStackedSectionControllerIndexKey, @(stackedSectionControllerIndex), OBJC_ASSOCIATION_ASSIGN); -} - -- (NSInteger)ig_stackedSectionControllerIndex { - return [objc_getAssociatedObject(self, kStackedSectionControllerIndexKey) integerValue]; -} - -@end - @implementation IGListStackedSectionController - (instancetype)initWithSectionControllers:(NSArray *> *)sectionControllers { @@ -49,10 +23,6 @@ static void * kStackedSectionControllerIndexKey = &kStackedSectionControllerInde for (IGListSectionController *sectionController in sectionControllers) { sectionController.collectionContext = self; sectionController.viewController = self.viewController; - - if (self.supplementaryViewSource == nil) { - self.supplementaryViewSource = sectionController.supplementaryViewSource; - } } _visibleSectionControllers = [[NSCountedSet alloc] init]; @@ -123,6 +93,15 @@ static void * kStackedSectionControllerIndexKey = &kStackedSectionControllerInde return itemIndexes; } +- (id)supplementaryViewSource { + for (IGListSectionController *sectionController in self.sectionControllers) { + id supplementaryViewSource = sectionController.supplementaryViewSource; + if (supplementaryViewSource != nil) { + return supplementaryViewSource; + } + } + return nil; +} #pragma mark - IGListSectionType @@ -305,9 +284,6 @@ static void * kStackedSectionControllerIndexKey = &kStackedSectionControllerInde IGListSectionController *childSectionController = [self sectionControllerForObjectIndex:index]; const NSUInteger localIndex = [self localIndexForSectionController:childSectionController index:index]; - [cell ig_setStackedSectionController:childSectionController]; - [cell ig_setStackedSectionControllerIndex:localIndex]; - NSCountedSet *visibleSectionControllers = self.visibleSectionControllers; id displayDelegate = [childSectionController displayDelegate]; @@ -346,7 +322,7 @@ static void * kStackedSectionControllerIndexKey = &kStackedSectionControllerInde - (void)listAdapter:(IGListAdapter *)listAdapter willBeginDraggingSectionController:(IGListSectionController *)sectionController { for (IGListSectionController *childSectionController in self.sectionControllers) { - [[childSectionController scrollDelegate] listAdapter:listAdapter willBeginDraggingSectionController:sectionController]; + [[childSectionController scrollDelegate] listAdapter:listAdapter willBeginDraggingSectionController:childSectionController]; } } diff --git a/Tests/IGListStackSectionControllerTests.m b/Tests/IGListStackSectionControllerTests.m index 72461310..fcfead45 100644 --- a/Tests/IGListStackSectionControllerTests.m +++ b/Tests/IGListStackSectionControllerTests.m @@ -17,7 +17,13 @@ #import "IGListDisplayHandler.h" #import "IGListStackedSectionControllerInternal.h" #import "IGListTestSection.h" +#import "IGTestCell.h" #import "IGTestStackedDataSource.h" +#import "IGTestStoryboardCell.h" +#import "IGTestStoryboardViewController.h" +#import "IGTestSupplementarySource.h" +#import "IGTestSupplementarySource.h" +#import "IGTestStoryboardSupplementarySource.h" static const CGRect kStackTestFrame = (CGRect){{0.0, 0.0}, {100.0, 100.0}}; @@ -37,9 +43,15 @@ static const CGRect kStackTestFrame = (CGRect){{0.0, 0.0}, {100.0, 100.0}}; self.window = [[UIWindow alloc] initWithFrame:kStackTestFrame]; - UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; - self.collectionView = [[IGListCollectionView alloc] initWithFrame:kStackTestFrame collectionViewLayout:layout]; - [self.window addSubview:self.collectionView]; + UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"IGTestStoryboard" bundle:[NSBundle bundleForClass:self.class]]; + IGTestStoryboardViewController *vc = [storyboard instantiateViewControllerWithIdentifier:@"testVC"]; + self.window.rootViewController = vc; + [self.window addSubview:vc.view]; + [vc performSelectorOnMainThread:@selector(loadView) withObject:nil waitUntilDone:YES]; + self.collectionView = vc.collectionView; + + vc.view.frame = kStackTestFrame; + self.collectionView.frame = kStackTestFrame; self.dataSource = [[IGTestStackedDataSource alloc] init]; self.adapter = [[IGListAdapter alloc] initWithUpdater:[IGListAdapterUpdater new] viewController:nil workingRangeSize:0]; @@ -431,4 +443,247 @@ static const CGRect kStackTestFrame = (CGRect){{0.0, 0.0}, {100.0, 100.0}}; [self waitForExpectationsWithTimeout:15 handler:nil]; } +- (void)test_whenSelectingItems_thatChildSectionControllersSelected { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@1, @2, @3]], + [[IGTestObject alloc] initWithKey:@1 value:@[@1, @2, @3]], + [[IGTestObject alloc] initWithKey:@2 value:@[@1, @1]] + ]]; + + [self.adapter collectionView:self.collectionView didSelectItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + [self.adapter collectionView:self.collectionView didSelectItemAtIndexPath:[NSIndexPath indexPathForItem:2 inSection:1]]; + [self.adapter collectionView:self.collectionView didSelectItemAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:2]]; + + IGListStackedSectionController *stack0 = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + IGListStackedSectionController *stack1 = [self.adapter sectionControllerForObject:self.dataSource.objects[1]]; + IGListStackedSectionController *stack2 = [self.adapter sectionControllerForObject:self.dataSource.objects[2]]; + + XCTAssertTrue([stack0.sectionControllers[0] wasSelected]); + XCTAssertFalse([stack0.sectionControllers[1] wasSelected]); + XCTAssertFalse([stack0.sectionControllers[2] wasSelected]); + XCTAssertFalse([stack1.sectionControllers[0] wasSelected]); + XCTAssertTrue([stack1.sectionControllers[1] wasSelected]); + XCTAssertFalse([stack1.sectionControllers[2] wasSelected]); + XCTAssertFalse([stack2.sectionControllers[0] wasSelected]); + XCTAssertTrue([stack2.sectionControllers[1] wasSelected]); +} + +- (void)test_whenUsingNibs_withStoryboards_thatCellsAreConfigured { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@1, @"nib", @"storyboard"]], + ]]; + + UICollectionViewCell *cell0 = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + IGTestCell *cell1 = (IGTestCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:0]]; + IGTestStoryboardCell *cell2 = (IGTestStoryboardCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:2 inSection:0]]; + + XCTAssertEqualObjects(cell0.class, [UICollectionViewCell class]); + XCTAssertEqualObjects(cell1.class, [IGTestCell class]); + XCTAssertEqualObjects(cell2.class, [IGTestStoryboardCell class]); + + XCTAssertEqualObjects(cell1.label.text, @"nib"); + XCTAssertEqualObjects(cell2.label.text, @"storyboard"); +} + +- (void)test_whenForwardingDidScrollEvent_thatChildSectionControllersReceiveEvent { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@1, @2, @3]], + [[IGTestObject alloc] initWithKey:@2 value:@[@1, @1]] + ]]; + + id mockScrollDelegate = [OCMockObject mockForProtocol:@protocol(IGListScrollDelegate)]; + + IGListStackedSectionController *stack0 = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + IGListStackedSectionController *stack1 = [self.adapter sectionControllerForObject:self.dataSource.objects[1]]; + + [stack0.sectionControllers[0] setScrollDelegate:mockScrollDelegate]; + [stack0.sectionControllers[1] setScrollDelegate:mockScrollDelegate]; + [stack0.sectionControllers[2] setScrollDelegate:mockScrollDelegate]; + [stack1.sectionControllers[0] setScrollDelegate:mockScrollDelegate]; + [stack1.sectionControllers[1] setScrollDelegate:mockScrollDelegate]; + + [[mockScrollDelegate expect] listAdapter:self.adapter didScrollSectionController:stack0.sectionControllers[0]]; + [[mockScrollDelegate expect] listAdapter:self.adapter didScrollSectionController:stack0.sectionControllers[1]]; + [[mockScrollDelegate expect] listAdapter:self.adapter didScrollSectionController:stack0.sectionControllers[2]]; + [[mockScrollDelegate expect] listAdapter:self.adapter didScrollSectionController:stack1.sectionControllers[0]]; + [[mockScrollDelegate expect] listAdapter:self.adapter didScrollSectionController:stack1.sectionControllers[1]]; + + [self.adapter scrollViewDidScroll:self.collectionView]; + + [mockScrollDelegate verify]; +} + +- (void)test_whenForwardingWillBeginDraggingEvent_thatChildSectionControllersReceiveEvent { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@1, @2, @3]], + [[IGTestObject alloc] initWithKey:@2 value:@[@1, @1]] + ]]; + + id mockScrollDelegate = [OCMockObject mockForProtocol:@protocol(IGListScrollDelegate)]; + + IGListStackedSectionController *stack0 = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + IGListStackedSectionController *stack1 = [self.adapter sectionControllerForObject:self.dataSource.objects[1]]; + + [stack0.sectionControllers[0] setScrollDelegate:mockScrollDelegate]; + [stack0.sectionControllers[1] setScrollDelegate:mockScrollDelegate]; + [stack0.sectionControllers[2] setScrollDelegate:mockScrollDelegate]; + [stack1.sectionControllers[0] setScrollDelegate:mockScrollDelegate]; + [stack1.sectionControllers[1] setScrollDelegate:mockScrollDelegate]; + + [[mockScrollDelegate expect] listAdapter:self.adapter willBeginDraggingSectionController:stack0.sectionControllers[0]]; + [[mockScrollDelegate expect] listAdapter:self.adapter willBeginDraggingSectionController:stack0.sectionControllers[1]]; + [[mockScrollDelegate expect] listAdapter:self.adapter willBeginDraggingSectionController:stack0.sectionControllers[2]]; + [[mockScrollDelegate expect] listAdapter:self.adapter willBeginDraggingSectionController:stack1.sectionControllers[0]]; + [[mockScrollDelegate expect] listAdapter:self.adapter willBeginDraggingSectionController:stack1.sectionControllers[1]]; + + [self.adapter scrollViewWillBeginDragging:self.collectionView]; + + [mockScrollDelegate verify]; +} + +- (void)test_whenForwardingDidEndDraggingEvent_thatChildSectionControllersReceiveEvent { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@1, @2, @3]], + [[IGTestObject alloc] initWithKey:@2 value:@[@1, @1]] + ]]; + + id mockScrollDelegate = [OCMockObject mockForProtocol:@protocol(IGListScrollDelegate)]; + + IGListStackedSectionController *stack0 = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + IGListStackedSectionController *stack1 = [self.adapter sectionControllerForObject:self.dataSource.objects[1]]; + + [stack0.sectionControllers[0] setScrollDelegate:mockScrollDelegate]; + [stack0.sectionControllers[1] setScrollDelegate:mockScrollDelegate]; + [stack0.sectionControllers[2] setScrollDelegate:mockScrollDelegate]; + [stack1.sectionControllers[0] setScrollDelegate:mockScrollDelegate]; + [stack1.sectionControllers[1] setScrollDelegate:mockScrollDelegate]; + + [[mockScrollDelegate expect] listAdapter:self.adapter didEndDraggingSectionController:stack0.sectionControllers[0] willDecelerate:NO]; + [[mockScrollDelegate expect] listAdapter:self.adapter didEndDraggingSectionController:stack0.sectionControllers[1] willDecelerate:NO]; + [[mockScrollDelegate expect] listAdapter:self.adapter didEndDraggingSectionController:stack0.sectionControllers[2] willDecelerate:NO]; + [[mockScrollDelegate expect] listAdapter:self.adapter didEndDraggingSectionController:stack1.sectionControllers[0] willDecelerate:NO]; + [[mockScrollDelegate expect] listAdapter:self.adapter didEndDraggingSectionController:stack1.sectionControllers[1] willDecelerate:NO]; + + [self.adapter scrollViewDidEndDragging:self.collectionView willDecelerate:NO]; + + [mockScrollDelegate verify]; +} + +- (void)test_whenUsingSupplementary_withCode_thatSupplementaryViewExists { + // updater that uses reloadData so we can rebuild all views/sizes + IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:[IGListReloadDataUpdater new] viewController:nil workingRangeSize:0]; + + self.dataSource.objects = @[ + [[IGTestObject alloc] initWithKey:@0 value:@[@1, @2, @3]], + [[IGTestObject alloc] initWithKey:@2 value:@[@1, @1]] + ]; + + adapter.collectionView = self.collectionView; + adapter.dataSource = self.dataSource; + [self.collectionView layoutIfNeeded]; + + IGListStackedSectionController *stack = [adapter sectionControllerForObject:self.dataSource.objects[1]]; + IGListTestSection *section = stack.sectionControllers.lastObject; + + IGTestSupplementarySource *supplementarySource = [IGTestSupplementarySource new]; + // the stack acts as the collection context. manually assign it. + supplementarySource.collectionContext = stack; + // however the actual section controller the supplementary serves is a child of the stack + supplementarySource.sectionController = section; + supplementarySource.supportedElementKinds = @[UICollectionElementKindSectionFooter]; + + section.supplementaryViewSource = supplementarySource; + + [adapter performUpdatesAnimated:NO completion:nil]; + + XCTAssertNotNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionFooter + atIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]); + XCTAssertNotNil(supplementarySource); +} + +- (void)test_whenUsingSupplementary_withNib_thatSupplementaryViewExists { + // updater that uses reloadData so we can rebuild all views/sizes + IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:[IGListReloadDataUpdater new] viewController:nil workingRangeSize:0]; + + self.dataSource.objects = @[ + [[IGTestObject alloc] initWithKey:@0 value:@[@1, @2, @3]], + [[IGTestObject alloc] initWithKey:@2 value:@[@1, @1]] + ]; + + adapter.collectionView = self.collectionView; + adapter.dataSource = self.dataSource; + [self.collectionView layoutIfNeeded]; + + IGListStackedSectionController *stack = [adapter sectionControllerForObject:self.dataSource.objects[1]]; + IGListTestSection *section = stack.sectionControllers.lastObject; + + IGTestSupplementarySource *supplementarySource = [IGTestSupplementarySource new]; + // the stack acts as the collection context. manually assign it. + supplementarySource.collectionContext = stack; + // however the actual section controller the supplementary serves is a child of the stack + supplementarySource.sectionController = section; + supplementarySource.supportedElementKinds = @[UICollectionElementKindSectionFooter]; + supplementarySource.dequeueFromNib = YES; + + section.supplementaryViewSource = supplementarySource; + + [adapter performUpdatesAnimated:NO completion:nil]; + + XCTAssertNotNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionFooter + atIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]); + XCTAssertNotNil(supplementarySource); +} + +- (void)test_whenUsingSupplementary_withStoryboard_thatSupplementaryViewExists { + // updater that uses reloadData so we can rebuild all views/sizes + IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:[IGListReloadDataUpdater new] viewController:nil workingRangeSize:0]; + + self.dataSource.objects = @[ + [[IGTestObject alloc] initWithKey:@0 value:@[@1, @2, @3]], + [[IGTestObject alloc] initWithKey:@2 value:@[@1, @1]] + ]; + + adapter.collectionView = self.collectionView; + adapter.dataSource = self.dataSource; + [self.collectionView layoutIfNeeded]; + + IGListStackedSectionController *stack = [adapter sectionControllerForObject:self.dataSource.objects[1]]; + IGListTestSection *section = stack.sectionControllers.lastObject; + + IGTestStoryboardSupplementarySource *supplementarySource = [IGTestStoryboardSupplementarySource new]; + // the stack acts as the collection context. manually assign it. + supplementarySource.collectionContext = stack; + // however the actual section controller the supplementary serves is a child of the stack + supplementarySource.sectionController = section; + + // the "section header" property of the parent collection view must be checked + supplementarySource.supportedElementKinds = @[UICollectionElementKindSectionHeader]; + + section.supplementaryViewSource = supplementarySource; + + [adapter performUpdatesAnimated:NO completion:nil]; + + XCTAssertNotNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionHeader + atIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]); + XCTAssertNotNil(supplementarySource); +} + +- (void)test_whenScrollingFromChildSectionController_thatScrollsToCorrectPosition { + // pad with enough items that we can freely scroll to the middle without accounting for content size + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@4, @5, @6]], + [[IGTestObject alloc] initWithKey:@1 value:@[@1, @2, @3]], + [[IGTestObject alloc] initWithKey:@2 value:@[@4, @5, @6]] + ]]; + + IGListStackedSectionController *stack = [self.adapter sectionControllerForObject:self.dataSource.objects[1]]; + IGListTestSection *section = stack.sectionControllers[1]; + + [section.collectionContext scrollToSectionController:section atIndex:1 scrollPosition:UICollectionViewScrollPositionTop animated:NO]; + + // IGListTestSection cells are 100x10 + XCTAssertEqual(self.collectionView.contentOffset.x, 0); + XCTAssertEqual(self.collectionView.contentOffset.y, 170); +} + @end diff --git a/Tests/Objects/IGTestStackedDataSource.m b/Tests/Objects/IGTestStackedDataSource.m index 99f91624..0643ef1d 100644 --- a/Tests/Objects/IGTestStackedDataSource.m +++ b/Tests/Objects/IGTestStackedDataSource.m @@ -11,6 +11,7 @@ #import +#import "IGTestCell.h" #import "IGListTestSection.h" @implementation IGTestStackedDataSource @@ -21,9 +22,35 @@ - (IGListSectionController *)listAdapter:(IGListAdapter *)listAdapter sectionControllerForObject:(id)object { NSMutableArray *controllers = [[NSMutableArray alloc] init]; - for (NSNumber *num in [(IGTestObject *)object value]) { - IGListTestSection *controller = [[IGListTestSection alloc] init]; - controller.items = [num integerValue]; + for (id value in [(IGTestObject *)object value]) { + id controller; + // use a standard IGListTestSection + if ([value isKindOfClass:[NSNumber class]]) { + IGListTestSection *section = [[IGListTestSection alloc] init]; + section.items = [value integerValue]; + controller = section; + } else if ([value isKindOfClass:[NSString class]]) { + void (^configureBlock)(id, __kindof UICollectionViewCell *) = ^(id obj, IGTestCell *cell) { + // capturing the value in block scope so we use the CHILD OBJECT of the stack + // otherwise the block uses the IGTestObject in the block param + cell.label.text = value; + }; + CGSize (^sizeBlock)(id, id) = ^CGSize(IGTestObject *item, id collectionContext) { + return CGSizeMake([collectionContext containerSize].width, 44); + }; + + // use either nibs or storyboards with NSString depending on the string value + if ([value isEqualToString:@"nib"]) { + controller = [[IGListSingleSectionController alloc] initWithNibName:@"IGTestNibCell" + bundle:[NSBundle bundleForClass:self.class] + configureBlock:configureBlock + sizeBlock:sizeBlock]; + } else { + controller = [[IGListSingleSectionController alloc] initWithStoryboardCellIdentifier:@"IGTestStoryboardCell" + configureBlock:configureBlock + sizeBlock:sizeBlock]; + } + } [controllers addObject:controller]; } return [[IGListStackedSectionController alloc] initWithSectionControllers:controllers];