Skip to content

Commit

Permalink
Add cell (un)highlight APIs
Browse files Browse the repository at this point in the history
Summary:
This implements the `collectionView:didHighlightItemAtIndexPath:` and `collectionView:didUnhighlightItemAtIndexPath:` `UICollectionViewDelegate` APIs and forward the calls to the appropriate `IGListSectionController`.
For the most part, it's doing the same thing as the `collectionView:didDeselectItemAtIndexPath:` calls and tests are also based on that feature.

- Implements `collectionView:didHighlightItemAtIndexPath:` and `collectionView:didUnhighlightItemAtIndexPath:` in IGListAdapter
- Catch those selectors in `IGListAdapterProxy`
- Adds `didHighlightItemAtIndex:` and `didUnhighlightItemAtIndex:` methods to IGListSectionController and friends (supports `IGListBindingSectionController` via its delegate and `IGListStackedSectionController`)
- Tests for changes

Issue fixed: I didn't open one, so none I guess.

- [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 added an entry to the `CHANGELOG.md` for any breaking changes, enhancements, or bug fixes.
- [x] I have reviewed the [contributing guide](https://github.com/Instagram/IGListKit/blob/master/.github/CONTRIBUTING.md)
Closes #933

Differential Revision: D5872090

Pulled By: rnystrom

fbshipit-source-id: adc93b68aced3e995f32c291bf607a263cd58edf
  • Loading branch information
delannoyk authored and facebook-github-bot committed Sep 20, 2017
1 parent d322c2e commit 9ddc64f
Show file tree
Hide file tree
Showing 15 changed files with 255 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ The changelog for `IGListKit`. Also see the [releases](https://github.com/instag

- Weakly reference the `UICollectionView` in coalescence so that it can be released if the rest of system is destroyed. [Ryan Nystrom](https://github.com/rnystrom) [(#tbd)](https://github.com/Instagram/IGListKit/pull/tbd)

### Enhancements

- Added `-[IGListSectionController didHighlightItemAtIndex:]` and `-[IGListSectionController didUnhighlightItemAtIndex:]` APIs to support `UICollectionView` cell highlighting. [Kevin Delannoy](https://github.com/delannoyk) [(#933)](https://github.com/Instagram/IGListKit/pull/933)

3.1.1
-----
Expand Down
14 changes: 14 additions & 0 deletions Source/IGListBindingSectionController.m
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,18 @@ - (void)didDeselectItemAtIndex:(NSInteger)index {
}
}

- (void)didHighlightItemAtIndex:(NSInteger)index {
id<IGListBindingSectionControllerSelectionDelegate> selectionDelegate = self.selectionDelegate;
if ([selectionDelegate respondsToSelector:@selector(sectionController:didHighlightItemAtIndex:viewModel:)]) {
[selectionDelegate sectionController:self didHighlightItemAtIndex:index viewModel:self.viewModels[index]];
}
}

- (void)didUnhighlightItemAtIndex:(NSInteger)index {
id<IGListBindingSectionControllerSelectionDelegate> selectionDelegate = self.selectionDelegate;
if ([selectionDelegate respondsToSelector:@selector(sectionController:didUnhighlightItemAtIndex:viewModel:)]) {
[selectionDelegate sectionController:self didUnhighlightItemAtIndex:index viewModel:self.viewModels[index]];
}
}

@end
24 changes: 24 additions & 0 deletions Source/IGListBindingSectionControllerSelectionDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,30 @@ NS_SWIFT_NAME(ListBindingSectionControllerSelectionDelegate)
didDeselectItemAtIndex:(NSInteger)index
viewModel:(id)viewModel;

/**
Tells the delegate that a cell at a given index was highlighted.
@param sectionController The section controller the highlight occurred in.
@param index The index of the highlighted cell.
@param viewModel The view model that was bound to the cell.
*/
@optional
- (void)sectionController:(IGListBindingSectionController *)sectionController
didHighlightItemAtIndex:(NSInteger)index
viewModel:(id)viewModel;

/**
Tells the delegate that a cell at a given index was unhighlighted.
@param sectionController The section controller the unhighlight occurred in.
@param index The index of the unhighlighted cell.
@param viewModel The view model that was bound to the cell.
*/
@optional
- (void)sectionController:(IGListBindingSectionController *)sectionController
didUnhighlightItemAtIndex:(NSInteger)index
viewModel:(id)viewModel;

@end

NS_ASSUME_NONNULL_END
18 changes: 18 additions & 0 deletions Source/IGListSectionController.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,24 @@ NS_SWIFT_NAME(ListSectionController)
*/
- (void)didDeselectItemAtIndex:(NSInteger)index;

/**
Tells the section controller that the cell at the specified index path was highlighted.
@param index The index of the highlighted cell.
@note The default implementation does nothing. **Calling super is not required.**
*/
- (void)didHighlightItemAtIndex:(NSInteger)index;

/**
Tells the section controller that the cell at the specified index path was unhighlighted.
@param index The index of the unhighlighted cell.
@note The default implementation does nothing. **Calling super is not required.**
*/
- (void)didUnhighlightItemAtIndex:(NSInteger)index;

/**
The view controller housing the adapter that created this section controller.
Expand Down
4 changes: 4 additions & 0 deletions Source/IGListSectionController.m
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,8 @@ - (void)didSelectItemAtIndex:(NSInteger)index {}

- (void)didDeselectItemAtIndex:(NSInteger)index {}

- (void)didHighlightItemAtIndex:(NSInteger)index {}

- (void)didUnhighlightItemAtIndex:(NSInteger)index {}

@end
12 changes: 12 additions & 0 deletions Source/IGListStackedSectionController.m
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,18 @@ - (void)didDeselectItemAtIndex:(NSInteger)index {
[sectionController didDeselectItemAtIndex:localIndex];
}

- (void)didHighlightItemAtIndex:(NSInteger)index {
IGListSectionController *sectionController = [self sectionControllerForObjectIndex:index];
const NSInteger localIndex = [self localIndexForSectionController:sectionController index:index];
[sectionController didHighlightItemAtIndex:localIndex];
}

- (void)didUnhighlightItemAtIndex:(NSInteger)index {
IGListSectionController *sectionController = [self sectionControllerForObjectIndex:index];
const NSInteger localIndex = [self localIndexForSectionController:sectionController index:index];
[sectionController didUnhighlightItemAtIndex:localIndex];
}

#pragma mark - IGListCollectionContext

- (CGSize)containerSize {
Expand Down
22 changes: 22 additions & 0 deletions Source/Internal/IGListAdapter+UICollectionView.m
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,28 @@ - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupple
[self removeMapForView:view];
}

- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath {
// forward this method to the delegate b/c this implementation will steal the message from the proxy
id<UICollectionViewDelegate> collectionViewDelegate = self.collectionViewDelegate;
if ([collectionViewDelegate respondsToSelector:@selector(collectionView:didHighlightItemAtIndexPath:)]) {
[collectionViewDelegate collectionView:collectionView didHighlightItemAtIndexPath:indexPath];
}

IGListSectionController * sectionController = [self sectionControllerForSection:indexPath.section];
[sectionController didHighlightItemAtIndex:indexPath.item];
}

- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath {
// forward this method to the delegate b/c this implementation will steal the message from the proxy
id<UICollectionViewDelegate> collectionViewDelegate = self.collectionViewDelegate;
if ([collectionViewDelegate respondsToSelector:@selector(collectionView:didUnhighlightItemAtIndexPath:)]) {
[collectionViewDelegate collectionView:collectionView didUnhighlightItemAtIndexPath:indexPath];
}

IGListSectionController * sectionController = [self sectionControllerForSection:indexPath.section];
[sectionController didUnhighlightItemAtIndex:indexPath.item];
}

#pragma mark - UICollectionViewDelegateFlowLayout

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
Expand Down
2 changes: 2 additions & 0 deletions Source/Internal/IGListAdapterProxy.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ static BOOL isInterceptedSelector(SEL sel) {
sel == @selector(collectionView:didSelectItemAtIndexPath:) ||
sel == @selector(collectionView:willDisplayCell:forItemAtIndexPath:) ||
sel == @selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:) ||
sel == @selector(collectionView:didHighlightItemAtIndexPath:) ||
sel == @selector(collectionView:didUnhighlightItemAtIndexPath:) ||
// UICollectionViewDelegateFlowLayout
sel == @selector(collectionView:layout:sizeForItemAtIndexPath:) ||
sel == @selector(collectionView:layout:insetForSectionAtIndex:) ||
Expand Down
68 changes: 68 additions & 0 deletions Tests/IGListAdapterTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,74 @@ - (void)test_whenEndDisplayingSupplementaryView_thatCollectionViewDelegateReceiv
[mockDelegate verify];
}

- (void)test_whenHighlightingCell_thatCollectionViewDelegateReceivesMethod {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];

id mockDelegate = [OCMockObject mockForProtocol:@protocol(UICollectionViewDelegate)];
self.adapter.collectionViewDelegate = mockDelegate;

NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
[[mockDelegate expect] collectionView:self.collectionView didHighlightItemAtIndexPath:indexPath];

// simulates the collectionview telling its delegate that it was highlighted
[self.adapter collectionView:self.collectionView didHighlightItemAtIndexPath:indexPath];

[mockDelegate verify];
}

- (void)test_whenHighlightingCell_thatSectionControllerReceivesMethod {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];

NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];

// simulates the collectionview telling its delegate that it was highlighted
[self.adapter collectionView:self.collectionView didHighlightItemAtIndexPath:indexPath];

IGListTestSection *s0 = [self.adapter sectionControllerForObject:@0];
IGListTestSection *s1 = [self.adapter sectionControllerForObject:@1];
IGListTestSection *s2 = [self.adapter sectionControllerForObject:@2];

XCTAssertTrue(s0.wasHighlighted);
XCTAssertFalse(s1.wasHighlighted);
XCTAssertFalse(s2.wasHighlighted);
}

- (void)test_whenUnhighlightingCell_thatCollectionViewDelegateReceivesMethod {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];

id mockDelegate = [OCMockObject mockForProtocol:@protocol(UICollectionViewDelegate)];
self.adapter.collectionViewDelegate = mockDelegate;

NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
[[mockDelegate expect] collectionView:self.collectionView didUnhighlightItemAtIndexPath:indexPath];

// simulates the collectionview telling its delegate that it was unhighlighted
[self.adapter collectionView:self.collectionView didUnhighlightItemAtIndexPath:indexPath];

[mockDelegate verify];
}

- (void)test_whenUnlighlightingCell_thatSectionControllerReceivesMethod {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];

NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];

// simulates the collectionview telling its delegate that it was unhighlighted
[self.adapter collectionView:self.collectionView didUnhighlightItemAtIndexPath:indexPath];

IGListTestSection *s0 = [self.adapter sectionControllerForObject:@0];
IGListTestSection *s1 = [self.adapter sectionControllerForObject:@1];
IGListTestSection *s2 = [self.adapter sectionControllerForObject:@2];

XCTAssertTrue(s0.wasUnhighlighted);
XCTAssertFalse(s1.wasUnhighlighted);
XCTAssertFalse(s2.wasUnhighlighted);
}

- (void)test_whenDataSourceDoesntHandleObject_thatObjectIsDropped {
// IGListTestAdapterDataSource does not handle NSStrings
self.dataSource.objects = @[@1, @"dog", @2];
Expand Down
18 changes: 18 additions & 0 deletions Tests/IGListBindingSectionControllerTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,24 @@ - (void)test_whenDeselectingCell_thatCorrectViewModelSelected {
XCTAssertEqualObjects(section.deselectedViewModel, @"seven");
}

- (void)test_whenHighlightingCell_thatCorrectViewModelHighlighted {
[self setupWithObjects:@[
[[IGTestDiffingObject alloc] initWithKey:@1 objects:@[@7, @"seven"]],
]];
[self.adapter collectionView:self.collectionView didHighlightItemAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:0]];
IGTestDiffingSectionController *section = [self.adapter sectionControllerForObject:self.dataSource.objects.firstObject];
XCTAssertEqualObjects(section.highlightedViewModel, @"seven");
}

- (void)test_whenUnhighlightingCell_thatCorrectViewModelUnhighlighted {
[self setupWithObjects:@[
[[IGTestDiffingObject alloc] initWithKey:@1 objects:@[@7, @"seven"]],
]];
[self.adapter collectionView:self.collectionView didUnhighlightItemAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:0]];
IGTestDiffingSectionController *section = [self.adapter sectionControllerForObject:self.dataSource.objects.firstObject];
XCTAssertEqualObjects(section.unhighlightedViewModel, @"seven");
}

- (void)test_whenDeselectingCell_withoutImplementation_thatNoOps {
[self setupWithObjects:@[
[[IGTestDiffingObject alloc] initWithKey:@1 objects:@[@7, @"seven"]],
Expand Down
50 changes: 50 additions & 0 deletions Tests/IGListStackSectionControllerTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,56 @@ - (void)test_whenDeselectingItems_thatChildSectionControllersSelected {
XCTAssertTrue([stack2.sectionControllers[1] wasDeselected]);
}

- (void)test_whenHighlightingItems_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 didHighlightItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
[self.adapter collectionView:self.collectionView didHighlightItemAtIndexPath:[NSIndexPath indexPathForItem:2 inSection:1]];
[self.adapter collectionView:self.collectionView didHighlightItemAtIndexPath:[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] wasHighlighted]);
XCTAssertFalse([stack0.sectionControllers[1] wasHighlighted]);
XCTAssertFalse([stack0.sectionControllers[2] wasHighlighted]);
XCTAssertFalse([stack1.sectionControllers[0] wasHighlighted]);
XCTAssertTrue([stack1.sectionControllers[1] wasHighlighted]);
XCTAssertFalse([stack1.sectionControllers[2] wasHighlighted]);
XCTAssertFalse([stack2.sectionControllers[0] wasHighlighted]);
XCTAssertTrue([stack2.sectionControllers[1] wasHighlighted]);
}

- (void)test_whenUnhighlightingItems_thatChildSectionControllersUnhighlighted {
[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 didUnhighlightItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
[self.adapter collectionView:self.collectionView didUnhighlightItemAtIndexPath:[NSIndexPath indexPathForItem:2 inSection:1]];
[self.adapter collectionView:self.collectionView didUnhighlightItemAtIndexPath:[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] wasUnhighlighted]);
XCTAssertFalse([stack0.sectionControllers[1] wasUnhighlighted]);
XCTAssertFalse([stack0.sectionControllers[2] wasUnhighlighted]);
XCTAssertFalse([stack1.sectionControllers[0] wasUnhighlighted]);
XCTAssertTrue([stack1.sectionControllers[1] wasUnhighlighted]);
XCTAssertFalse([stack1.sectionControllers[2] wasUnhighlighted]);
XCTAssertFalse([stack2.sectionControllers[0] wasUnhighlighted]);
XCTAssertTrue([stack2.sectionControllers[1] wasUnhighlighted]);
}

- (void)test_whenUsingNibs_withStoryboards_thatCellsAreConfigured {
[self setupWithObjects:@[
[[IGTestObject alloc] initWithKey:@0 value:@[@1, @"nib", @"storyboard"]],
Expand Down
2 changes: 2 additions & 0 deletions Tests/Objects/IGListTestSection.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@
@property (nonatomic, assign) CGSize size;
@property (nonatomic, assign) BOOL wasSelected;
@property (nonatomic, assign) BOOL wasDeselected;
@property (nonatomic, assign) BOOL wasHighlighted;
@property (nonatomic, assign) BOOL wasUnhighlighted;

@end
8 changes: 8 additions & 0 deletions Tests/Objects/IGListTestSection.m
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,12 @@ - (void)didDeselectItemAtIndex:(NSInteger)index {
self.wasDeselected = YES;
}

- (void)didHighlightItemAtIndex:(NSInteger)index {
self.wasHighlighted = YES;
}

- (void)didUnhighlightItemAtIndex:(NSInteger)index {
self.wasUnhighlighted = YES;
}

@end
2 changes: 2 additions & 0 deletions Tests/Objects/IGTestDiffingSectionController.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@

@property (nonatomic, strong) id selectedViewModel;
@property (nonatomic, strong) id deselectedViewModel;
@property (nonatomic, strong) id highlightedViewModel;
@property (nonatomic, strong) id unhighlightedViewModel;

@end
8 changes: 8 additions & 0 deletions Tests/Objects/IGTestDiffingSectionController.m
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,12 @@ - (void)sectionController:(IGListBindingSectionController *)sectionController di
self.deselectedViewModel = viewModel;
}

- (void)sectionController:(IGListBindingSectionController *)sectionController didHighlightItemAtIndex:(NSInteger)index viewModel:(id)viewModel {
self.highlightedViewModel = viewModel;
}

- (void)sectionController:(IGListBindingSectionController *)sectionController didUnhighlightItemAtIndex:(NSInteger)index viewModel:(id)viewModel {
self.unhighlightedViewModel = viewModel;
}

@end

0 comments on commit 9ddc64f

Please sign in to comment.