diff --git a/IGListKit.xcodeproj/project.pbxproj b/IGListKit.xcodeproj/project.pbxproj index c66c3a75..5dd9b025 100644 --- a/IGListKit.xcodeproj/project.pbxproj +++ b/IGListKit.xcodeproj/project.pbxproj @@ -236,6 +236,8 @@ 8285404D1DE40C6E00118B94 /* IGListTestHorizontalSection.m in Sources */ = {isa = PBXBuildFile; fileRef = 8285404B1DE40C6E00118B94 /* IGListTestHorizontalSection.m */; }; 828540501DE40D2D00118B94 /* IGListTestAdapterHorizontalDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 8285404F1DE40D2D00118B94 /* IGListTestAdapterHorizontalDataSource.m */; }; 828540511DE40D2D00118B94 /* IGListTestAdapterHorizontalDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 8285404F1DE40D2D00118B94 /* IGListTestAdapterHorizontalDataSource.m */; }; + 82914C5B1E6E2DEC0066C2F8 /* IGListTestContainerSizeSection.m in Sources */ = {isa = PBXBuildFile; fileRef = 82914C5A1E6E2DEC0066C2F8 /* IGListTestContainerSizeSection.m */; }; + 82914C5C1E6E2DEC0066C2F8 /* IGListTestContainerSizeSection.m in Sources */ = {isa = PBXBuildFile; fileRef = 82914C5A1E6E2DEC0066C2F8 /* IGListTestContainerSizeSection.m */; }; 829D7BAA1DD1819000549816 /* IGListSectionMapTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 829D7BA81DD1816400549816 /* IGListSectionMapTests.m */; }; 88144F071D870EDC007C7F66 /* IGListAdapterE2ETests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EE21D870EDC007C7F66 /* IGListAdapterE2ETests.m */; }; 88144F081D870EDC007C7F66 /* IGListAdapterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88144EE31D870EDC007C7F66 /* IGListAdapterTests.m */; }; @@ -451,6 +453,8 @@ 8285404B1DE40C6E00118B94 /* IGListTestHorizontalSection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListTestHorizontalSection.m; sourceTree = ""; }; 8285404E1DE40D2D00118B94 /* IGListTestAdapterHorizontalDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListTestAdapterHorizontalDataSource.h; sourceTree = ""; }; 8285404F1DE40D2D00118B94 /* IGListTestAdapterHorizontalDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListTestAdapterHorizontalDataSource.m; sourceTree = ""; }; + 82914C591E6E2DEC0066C2F8 /* IGListTestContainerSizeSection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IGListTestContainerSizeSection.h; sourceTree = ""; }; + 82914C5A1E6E2DEC0066C2F8 /* IGListTestContainerSizeSection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListTestContainerSizeSection.m; sourceTree = ""; }; 829D7BA81DD1816400549816 /* IGListSectionMapTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListSectionMapTests.m; sourceTree = ""; }; 841726B542A3E9A4BD48946F /* Pods-IGListKit-tvOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IGListKit-tvOSTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-IGListKit-tvOSTests/Pods-IGListKit-tvOSTests.debug.xcconfig"; sourceTree = ""; }; 88144EE21D870EDC007C7F66 /* IGListAdapterE2ETests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IGListAdapterE2ETests.m; sourceTree = ""; }; @@ -739,6 +743,8 @@ 298DD9D91E3ADE3300F76F50 /* IGTestStringBindableCell.m */, 88144F051D870EDC007C7F66 /* IGTestSupplementarySource.h */, 88144F061D870EDC007C7F66 /* IGTestSupplementarySource.m */, + 82914C591E6E2DEC0066C2F8 /* IGListTestContainerSizeSection.h */, + 82914C5A1E6E2DEC0066C2F8 /* IGListTestContainerSizeSection.m */, ); path = Objects; sourceTree = ""; @@ -1344,6 +1350,7 @@ 298DDA3C1E3B170300F76F50 /* IGLayoutTestSection.m in Sources */, 298DDA0A1E3AE31E00F76F50 /* IGTestDiffingSectionController.m in Sources */, 29C4748D1DDF45F900AE68CE /* IGListAdapterProxyTests.m in Sources */, + 82914C5C1E6E2DEC0066C2F8 /* IGListTestContainerSizeSection.m in Sources */, 885FE22C1DC51B76009CE2B4 /* IGListAdapterTests.m in Sources */, 298DDA051E3AE2B000F76F50 /* IGTestStringBindableCell.m in Sources */, 885FE22D1DC51B76009CE2B4 /* IGListAdapterUpdaterTests.m in Sources */, @@ -1431,6 +1438,7 @@ 298DDA3D1E3B170400F76F50 /* IGLayoutTestSection.m in Sources */, 298DDA091E3AE31D00F76F50 /* IGTestDiffingSectionController.m in Sources */, 88144F151D870EDC007C7F66 /* IGListTestSection.m in Sources */, + 82914C5B1E6E2DEC0066C2F8 /* IGListTestContainerSizeSection.m in Sources */, 88144F1D1D870EDC007C7F66 /* IGTestSupplementarySource.m in Sources */, 298DDA071E3AE2B100F76F50 /* IGTestStringBindableCell.m in Sources */, 88144F081D870EDC007C7F66 /* IGListAdapterTests.m in Sources */, diff --git a/Source/IGListAdapter.m b/Source/IGListAdapter.m index 8398f3e5..6147d237 100644 --- a/Source/IGListAdapter.m +++ b/Source/IGListAdapter.m @@ -818,6 +818,12 @@ return UIEdgeInsetsInsetRect(self.collectionView.bounds, self.collectionView.contentInset).size; } +- (CGSize)containerSizeForSectionController:(IGListSectionController *)sectionController { + const UIEdgeInsets inset = sectionController.inset; + return CGSizeMake(self.containerSize.width - inset.left - inset.right, + self.containerSize.height - inset.top - inset.bottom); +} + - (NSInteger)indexForCell:(UICollectionViewCell *)cell sectionController:(nonnull IGListSectionController *)sectionController { IGAssertMainThread(); IGParameterAssert(cell != nil); diff --git a/Source/IGListCollectionContext.h b/Source/IGListCollectionContext.h index ea357b5e..2b40809c 100644 --- a/Source/IGListCollectionContext.h +++ b/Source/IGListCollectionContext.h @@ -25,6 +25,15 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, readonly) CGSize containerSize; +/** + Returns size of the collection view relative to the section controller. + + @param sectionController The section controller requesting this information. + + @return The size of the collection view minus the given section controller's insets. + */ +- (CGSize)containerSizeForSectionController:(IGListSectionController *)sectionController; + /** Returns the index of the specified cell in the collection relative to the section controller. diff --git a/Source/IGListStackedSectionController.m b/Source/IGListStackedSectionController.m index 1a5f6efa..f6666d4b 100644 --- a/Source/IGListStackedSectionController.m +++ b/Source/IGListStackedSectionController.m @@ -165,6 +165,12 @@ static void * kStackedSectionControllerIndexKey = &kStackedSectionControllerInde return [self.collectionContext containerSize]; } +- (CGSize)containerSizeForSectionController:(IGListSectionController *)sectionController { + const UIEdgeInsets inset = sectionController.inset; + return CGSizeMake(self.containerSize.width - inset.left - inset.right, + self.containerSize.height - inset.top - inset.bottom); +} + - (NSInteger)indexForCell:(UICollectionViewCell *)cell sectionController:(IGListSectionController *)sectionController { const NSInteger index = [self.collectionContext indexForCell:cell sectionController:self]; return [self localIndexForSectionController:sectionController index:index]; diff --git a/Tests/IGListAdapterTests.m b/Tests/IGListAdapterTests.m index d34fc348..4a518361 100644 --- a/Tests/IGListAdapterTests.m +++ b/Tests/IGListAdapterTests.m @@ -27,6 +27,12 @@ CGPoint p = CGPointMake(x, y); \ XCTAssertEqual(CGPointEqualToPoint(point, p), YES); \ } while(0) +#define IGAssertEqualSize(size, w, h, ...) \ +do { \ +CGSize s = CGSizeMake(w, h); \ +XCTAssertEqual(CGSizeEqualToSize(size, s), YES); \ +} while(0) + @interface IGListAdapterTests : XCTestCase // infra does not hold a strong ref to collection view @@ -1083,4 +1089,12 @@ XCTAssertEqual(CGPointEqualToPoint(point, p), YES); \ XCTAssertEqualObjects(self.adapter.objects, expected); } +- (void)test_whenSectionEdgeInsetIsNotZero { + // IGListTestAdapterDataSource does not handle NSStrings + self.dataSource.objects = @[@42]; + [self.adapter reloadDataWithCompletion:nil]; + IGListSectionController *controller = [self.adapter sectionControllerForObject:@42]; + IGAssertEqualSize([self.adapter containerSizeForSectionController:controller], 98, 98); +} + @end diff --git a/Tests/IGListStackSectionControllerTests.m b/Tests/IGListStackSectionControllerTests.m index 3251a065..9f3227aa 100644 --- a/Tests/IGListStackSectionControllerTests.m +++ b/Tests/IGListStackSectionControllerTests.m @@ -17,6 +17,7 @@ #import "IGListDisplayHandler.h" #import "IGListStackedSectionControllerInternal.h" #import "IGListTestSection.h" +#import "IGListTestContainerSizeSection.h" #import "IGTestCell.h" #import "IGTestStackedDataSource.h" #import "IGTestStoryboardCell.h" @@ -25,6 +26,12 @@ #import "IGTestSupplementarySource.h" #import "IGTestStoryboardSupplementarySource.h" +#define IGAssertEqualSize(size, w, h, ...) \ +do { \ +CGSize s = CGSizeMake(w, h); \ +XCTAssertEqual(CGSizeEqualToSize(size, s), YES); \ +} while(0) + static const CGRect kStackTestFrame = (CGRect){{0.0, 0.0}, {100.0, 100.0}}; @interface IGListStackSectionControllerTests : XCTestCase @@ -142,6 +149,15 @@ static const CGRect kStackTestFrame = (CGRect){{0.0, 0.0}, {100.0, 100.0}}; XCTAssertTrue(CGSizeEqualToSize([section1.collectionContext containerSize], kStackTestFrame.size)); } +- (void)test_whenSectionEdgeInsetIsNotZero { + [self setupWithObjects:@[ + [[IGTestObject alloc] initWithKey:@0 value:@[@42]] + ]]; + IGListStackedSectionController *stack = [self.adapter sectionControllerForObject:self.dataSource.objects[0]]; + IGListTestContainerSizeSection *section1 = stack.sectionControllers[0]; + IGAssertEqualSize([stack containerSizeForSectionController:section1], 98, 98); +} + - (void)test_whenQueryingCellIndex_thatIndexIsRelativeToSectionController { [self setupWithObjects:@[ [[IGTestObject alloc] initWithKey:@0 value:@[@1, @1, @2]] diff --git a/Tests/Objects/IGListTestAdapterDataSource.m b/Tests/Objects/IGListTestAdapterDataSource.m index e7a92db8..e1edd1c4 100644 --- a/Tests/Objects/IGListTestAdapterDataSource.m +++ b/Tests/Objects/IGListTestAdapterDataSource.m @@ -12,6 +12,7 @@ #import #import "IGListTestSection.h" +#import "IGListTestContainerSizeSection.h" @implementation IGListTestAdapterDataSource @@ -21,6 +22,9 @@ - (IGListSectionController *)listAdapter:(IGListAdapter *)listAdapter sectionControllerForObject:(id)object { if ([object isKindOfClass:[NSNumber class]]) { + if ([(NSNumber*)object isEqual: @42]) { + return [IGListTestContainerSizeSection new]; + } return [IGListTestSection new]; } return nil; diff --git a/Tests/Objects/IGListTestContainerSizeSection.h b/Tests/Objects/IGListTestContainerSizeSection.h new file mode 100644 index 00000000..e5b6777c --- /dev/null +++ b/Tests/Objects/IGListTestContainerSizeSection.h @@ -0,0 +1,19 @@ +/** + * 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 + +@interface IGListTestContainerSizeSection : IGListSectionController + +@property (nonatomic, assign) NSInteger items; + +@end diff --git a/Tests/Objects/IGListTestContainerSizeSection.m b/Tests/Objects/IGListTestContainerSizeSection.m new file mode 100644 index 00000000..f55b2804 --- /dev/null +++ b/Tests/Objects/IGListTestContainerSizeSection.m @@ -0,0 +1,50 @@ +/** + * 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 "IGListTestContainerSizeSection.h" + +@implementation IGListTestContainerSizeSection + +- (instancetype)init +{ + self = [super init]; + if (self) { + self.inset = UIEdgeInsetsMake(1.0, 1.0, 1.0, 1.0); + } + return self; +} + +- (NSArray *)cellClasses { + return @[UICollectionViewCell.class]; +} + +- (NSInteger)numberOfItems { + return self.items; +} + +- (CGSize)sizeForItemAtIndex:(NSInteger)index { + return CGSizeMake(100, 10); +} + +- (UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index { + return [self.collectionContext dequeueReusableCellOfClass:UICollectionViewCell.class + forSectionController:self + atIndex:index]; +} + +- (void)didUpdateToObject:(id)object { + if ([object isKindOfClass:[NSNumber class]]) { + self.items = [object integerValue]; + } +} + +- (void)didSelectItemAtIndex:(NSInteger)index { +} + +@end diff --git a/Tests/Objects/IGTestStackedDataSource.m b/Tests/Objects/IGTestStackedDataSource.m index 0643ef1d..4d7987b4 100644 --- a/Tests/Objects/IGTestStackedDataSource.m +++ b/Tests/Objects/IGTestStackedDataSource.m @@ -13,6 +13,7 @@ #import "IGTestCell.h" #import "IGListTestSection.h" +#import "IGListTestContainerSizeSection.h" @implementation IGTestStackedDataSource @@ -26,9 +27,15 @@ id controller; // use a standard IGListTestSection if ([value isKindOfClass:[NSNumber class]]) { - IGListTestSection *section = [[IGListTestSection alloc] init]; - section.items = [value integerValue]; - controller = section; + if ([(NSNumber*)value isEqual: @42]) { + IGListTestContainerSizeSection *section = [[IGListTestContainerSizeSection alloc] init]; + section.items = [value integerValue]; + controller = section; + } else { + 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