diff --git a/CHANGELOG.md b/CHANGELOG.md index 829cd8801..616d56fe0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 ----- diff --git a/Examples/Examples-iOS/IGListKitExamples/SectionControllers/MonthSectionController.swift b/Examples/Examples-iOS/IGListKitExamples/SectionControllers/MonthSectionController.swift index e1db4a61c..54fb05fa7 100644 --- a/Examples/Examples-iOS/IGListKitExamples/SectionControllers/MonthSectionController.swift +++ b/Examples/Examples-iOS/IGListKitExamples/SectionControllers/MonthSectionController.swift @@ -95,4 +95,6 @@ final class MonthSectionController: ListBindingSectionController, update(animated: true) } + func sectionController(_ sectionController: ListBindingSectionController, didDeselectItemAt index: Int, viewModel: Any) {} + } diff --git a/Source/IGListBindingSectionController.m b/Source/IGListBindingSectionController.m index bc2fc5561..7fb06d05f 100644 --- a/Source/IGListBindingSectionController.m +++ b/Source/IGListBindingSectionController.m @@ -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 diff --git a/Source/IGListBindingSectionControllerSelectionDelegate.h b/Source/IGListBindingSectionControllerSelectionDelegate.h index 4c2a9c7c2..936e221a5 100644 --- a/Source/IGListBindingSectionControllerSelectionDelegate.h +++ b/Source/IGListBindingSectionControllerSelectionDelegate.h @@ -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 diff --git a/Source/IGListSectionController.h b/Source/IGListSectionController.h index eba888dff..8d02fbf68 100644 --- a/Source/IGListSectionController.h +++ b/Source/IGListSectionController.h @@ -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. diff --git a/Source/IGListSectionController.m b/Source/IGListSectionController.m index 84accbc49..ccef02eae 100644 --- a/Source/IGListSectionController.m +++ b/Source/IGListSectionController.m @@ -84,4 +84,6 @@ - (void)didUpdateToObject:(id)object {} - (void)didSelectItemAtIndex:(NSInteger)index {} +- (void)didDeselectItemAtIndex:(NSInteger)index {} + @end diff --git a/Source/IGListStackedSectionController.m b/Source/IGListStackedSectionController.m index 35880559c..fcf720d03 100644 --- a/Source/IGListStackedSectionController.m +++ b/Source/IGListStackedSectionController.m @@ -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 { diff --git a/Source/Internal/IGListAdapter+UICollectionView.m b/Source/Internal/IGListAdapter+UICollectionView.m index c2a6019c0..c3c60d141 100644 --- a/Source/Internal/IGListAdapter+UICollectionView.m +++ b/Source/Internal/IGListAdapter+UICollectionView.m @@ -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 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 collectionViewDelegate = self.collectionViewDelegate; diff --git a/Tests/IGListAdapterTests.m b/Tests/IGListAdapterTests.m index 71b6e73fd..efbefc77d 100644 --- a/Tests/IGListAdapterTests.m +++ b/Tests/IGListAdapterTests.m @@ -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]; diff --git a/Tests/IGListBindingSectionControllerTests.m b/Tests/IGListBindingSectionControllerTests.m index 8ecc27d87..5e9fb3f66 100644 --- a/Tests/IGListBindingSectionControllerTests.m +++ b/Tests/IGListBindingSectionControllerTests.m @@ -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"]], diff --git a/Tests/IGListStackSectionControllerTests.m b/Tests/IGListStackSectionControllerTests.m index 670167836..759d98663 100644 --- a/Tests/IGListStackSectionControllerTests.m +++ b/Tests/IGListStackSectionControllerTests.m @@ -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"]], diff --git a/Tests/Objects/IGListTestSection.h b/Tests/Objects/IGListTestSection.h index 00d2230ca..f2650875c 100644 --- a/Tests/Objects/IGListTestSection.h +++ b/Tests/Objects/IGListTestSection.h @@ -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 diff --git a/Tests/Objects/IGListTestSection.m b/Tests/Objects/IGListTestSection.m index 98b8a3edf..bb3e3a2d5 100644 --- a/Tests/Objects/IGListTestSection.m +++ b/Tests/Objects/IGListTestSection.m @@ -46,4 +46,8 @@ - (void)didSelectItemAtIndex:(NSInteger)index { self.wasSelected = YES; } +- (void)didDeselectItemAtIndex:(NSInteger)index { + self.wasDeselected = YES; +} + @end diff --git a/Tests/Objects/IGTestDiffingSectionController.h b/Tests/Objects/IGTestDiffingSectionController.h index 51759c10a..a798bd788 100644 --- a/Tests/Objects/IGTestDiffingSectionController.h +++ b/Tests/Objects/IGTestDiffingSectionController.h @@ -12,5 +12,6 @@ @interface IGTestDiffingSectionController : IGListBindingSectionController @property (nonatomic, strong) id selectedViewModel; +@property (nonatomic, strong) id deselectedViewModel; @end diff --git a/Tests/Objects/IGTestDiffingSectionController.m b/Tests/Objects/IGTestDiffingSectionController.m index 0346a1b04..589a5637f 100644 --- a/Tests/Objects/IGTestDiffingSectionController.m +++ b/Tests/Objects/IGTestDiffingSectionController.m @@ -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