Skip to content

Commit

Permalink
Add cell deselection API
Browse files Browse the repository at this point in the history
Summary:
Adding support for a cell deselection API. Trying to make some headway to move and drag+drop support, but also want better stock `UICollectionView` API support. Will also assist eventual `UITableView` support.

- Added overridable API to `IGListSectionController`
- Support for stacked SC
- Breaking, required protocol for binding SC

Assists Instagram#524 and Instagram#184

- [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.
Closes Instagram#853

Reviewed By: jeremycohen

Differential Revision: D5425414

Pulled By: rnystrom

fbshipit-source-id: 0b25c125b1f171979a15c3095095fc18b4108be6
  • Loading branch information
Ryan Nystrom authored and facebook-github-bot committed Jul 17, 2017
1 parent 61c1524 commit 6540f96
Show file tree
Hide file tree
Showing 15 changed files with 130 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ The changelog for `IGListKit`. Also see the [releases](https://github.com/instag

- Prevent a crash when update queued immediately after item batch update. [Ryan Nystrom](https://github.com/rnystrom) (tbd)

### Enhancements

- Added `-[IGListSectionController didDeselectItemAtIndex:]` API to support default `UICollectionView` cell deselection. [Ryan Nystrom](https://github.com/rnystrom) (tbd)

3.0.0
-----

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,6 @@ final class MonthSectionController: ListBindingSectionController<ListDiffable>,
update(animated: true)
}

func sectionController(_ sectionController: ListBindingSectionController<ListDiffable>, didDeselectItemAt index: Int, viewModel: Any) {}

}
4 changes: 4 additions & 0 deletions Source/IGListBindingSectionController.m
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,8 @@ - (void)didSelectItemAtIndex:(NSInteger)index {
[self.selectionDelegate sectionController:self didSelectItemAtIndex:index viewModel:self.viewModels[index]];
}

- (void)didDeselectItemAtIndex:(NSInteger)index {
[self.selectionDelegate sectionController:self didDeselectItemAtIndex:index viewModel:self.viewModels[index]];
}

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

/**
Tells the delegate that a cell at a given index was deselected.
@param sectionController The section controller the deselection occurred in.
@param index The index of the deselected cell.
@param viewModel The view model that was bound to the cell.
@note Method is `@optional` until the 4.0.0 release where it will become required.
*/
@optional
- (void)sectionController:(IGListBindingSectionController *)sectionController
didDeselectItemAtIndex:(NSInteger)index
viewModel:(id)viewModel;

@end

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

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

/**
The view controller housing the adapter that created this section controller.
Expand Down
2 changes: 2 additions & 0 deletions Source/IGListSectionController.m
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,6 @@ - (void)didUpdateToObject:(id)object {}

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

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

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

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

#pragma mark - IGListCollectionContext

- (CGSize)containerSize {
Expand Down
11 changes: 11 additions & 0 deletions Source/Internal/IGListAdapter+UICollectionView.m
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPa
[sectionController didSelectItemAtIndex:indexPath.item];
}

- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(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:didDeselectItemAtIndexPath:)]) {
[collectionViewDelegate collectionView:collectionView didDeselectItemAtIndexPath:indexPath];
}

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

- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
// forward this method to the delegate b/c this implementation will steal the message from the proxy
id<UICollectionViewDelegate> collectionViewDelegate = self.collectionViewDelegate;
Expand Down
34 changes: 34 additions & 0 deletions Tests/IGListAdapterTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,40 @@ - (void)test_whenSelectingCell_thatSectionControllerReceivesMethod {
XCTAssertFalse(s2.wasSelected);
}

- (void)test_whenDeselectingCell_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 didDeselectItemAtIndexPath:indexPath];

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

[mockDelegate verify];
}

- (void)test_whenDeselectingCell_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 tapped
[self.adapter collectionView:self.collectionView didDeselectItemAtIndexPath:indexPath];

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

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

- (void)test_whenDisplayingCell_thatCollectionViewDelegateReceivesMethod {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];
Expand Down
9 changes: 9 additions & 0 deletions Tests/IGListBindingSectionControllerTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@ - (void)test_whenSelectingCell_thatCorrectViewModelSelected {
XCTAssertEqualObjects(section.selectedViewModel, @"seven");
}

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

- (void)test_whenAdapterReloadsObjects_thatSectionUpdated {
[self setupWithObjects:@[
[[IGTestDiffingObject alloc] initWithKey:@1 objects:@[@7, @"seven"]],
Expand Down
25 changes: 25 additions & 0 deletions Tests/IGListStackSectionControllerTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,31 @@ - (void)test_whenSelectingItems_thatChildSectionControllersSelected {
XCTAssertTrue([stack2.sectionControllers[1] wasSelected]);
}

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

- (void)test_whenUsingNibs_withStoryboards_thatCellsAreConfigured {
[self setupWithObjects:@[
[[IGTestObject alloc] initWithKey:@0 value:@[@1, @"nib", @"storyboard"]],
Expand Down
3 changes: 1 addition & 2 deletions Tests/Objects/IGListTestSection.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
@interface IGListTestSection : IGListSectionController

@property (nonatomic, assign) NSInteger items;

@property (nonatomic, assign) CGSize size;

@property (nonatomic, assign) BOOL wasSelected;
@property (nonatomic, assign) BOOL wasDeselected;

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

- (void)didDeselectItemAtIndex:(NSInteger)index {
self.wasDeselected = YES;
}

@end
1 change: 1 addition & 0 deletions Tests/Objects/IGTestDiffingSectionController.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
@interface IGTestDiffingSectionController : IGListBindingSectionController <IGListBindingSectionControllerDataSource, IGListBindingSectionControllerSelectionDelegate>

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

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

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

@end

0 comments on commit 6540f96

Please sign in to comment.