From 777b48cc334f752a2f7c0305b6e7d653e4d6a234 Mon Sep 17 00:00:00 2001 From: Rene Cacheaux Date: Sun, 21 Jun 2015 18:37:00 -0500 Subject: [PATCH] Adds layout controller for collection views. This new layout controller leverages UICollectionViewLayout's layoutAttributesForElementsInRect in order to calculate index paths for items in AsyncDisplayKit ranges. Flow layout is still a requirement in order to test the waters. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 16 ++ AsyncDisplayKit/ASCollectionView.mm | 14 +- .../Details/ASAbstractLayoutController.mm | 2 - .../ASCollectionViewLayoutController.h | 18 +++ .../ASCollectionViewLayoutController.mm | 153 ++++++++++++++++++ AsyncDisplayKit/Details/ASScrollDirection.h | 5 + AsyncDisplayKit/Details/ASScrollDirection.m | 16 ++ .../Details/CGRect+ASConvenience.h | 18 +++ .../Details/CGRect+ASConvenience.m | 31 ++++ 9 files changed, 263 insertions(+), 10 deletions(-) create mode 100644 AsyncDisplayKit/Details/ASCollectionViewLayoutController.h create mode 100644 AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm create mode 100644 AsyncDisplayKit/Details/CGRect+ASConvenience.h create mode 100644 AsyncDisplayKit/Details/CGRect+ASConvenience.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index ca4d3c60ee..b0de0a95c2 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -147,6 +147,10 @@ 205F0E121B371BD7007741D0 /* ASScrollDirection.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E111B371BD7007741D0 /* ASScrollDirection.m */; }; 205F0E191B37339C007741D0 /* ASAbstractLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 205F0E1A1B37339C007741D0 /* ASAbstractLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */; }; + 205F0E1D1B373A2C007741D0 /* ASCollectionViewLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 205F0E1E1B373A2C007741D0 /* ASCollectionViewLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */; }; + 205F0E211B376416007741D0 /* CGRect+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; }; + 205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; }; 242995D31B29743C00090100 /* ASBasicImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */; }; 2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2911485B1A77147A005D0878 /* ASControlNodeTests.m */; }; 291B63FB1AA53A7A000A71B3 /* ASScrollDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 296A0A311A951715005ACEAA /* ASScrollDirection.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -329,6 +333,10 @@ 205F0E111B371BD7007741D0 /* ASScrollDirection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollDirection.m; sourceTree = ""; }; 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAbstractLayoutController.h; sourceTree = ""; }; 205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASAbstractLayoutController.mm; sourceTree = ""; }; + 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewLayoutController.h; sourceTree = ""; }; + 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionViewLayoutController.mm; sourceTree = ""; }; + 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CGRect+ASConvenience.h"; sourceTree = ""; }; + 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CGRect+ASConvenience.m"; sourceTree = ""; }; 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = ""; }; 2911485B1A77147A005D0878 /* ASControlNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASControlNodeTests.m; sourceTree = ""; }; 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutRangeType.h; sourceTree = ""; }; @@ -554,6 +562,8 @@ 054963481A1EA066000F8E56 /* ASBasicImageDownloader.mm */, 299DA1A71A828D2900162D41 /* ASBatchContext.h */, 299DA1A81A828D2900162D41 /* ASBatchContext.mm */, + 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */, + 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */, 464052191A3F83C40061C0BA /* ASDataController.h */, 4640521A1A3F83C40061C0BA /* ASDataController.mm */, 05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */, @@ -590,6 +600,8 @@ 058D09F3195D050800B7D73C /* ASTextNodeWordKerner.h */, 058D09F4195D050800B7D73C /* ASTextNodeWordKerner.m */, 058D0A12195D050800B7D73C /* ASThread.h */, + 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */, + 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */, 058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */, 058D09F6195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m */, 058D09F7195D050800B7D73C /* Transactions */, @@ -728,6 +740,7 @@ 6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */, 058D0A6D195D05EC00B7D73C /* _ASAsyncTransactionGroup.h in Headers */, 058D0A6E195D05EC00B7D73C /* _ASAsyncTransactionGroup.m in Headers */, + 205F0E1D1B373A2C007741D0 /* ASCollectionViewLayoutController.h in Headers */, 058D0A6F195D05EC00B7D73C /* UIView+ASConvenience.h in Headers */, 058D0A70195D05EC00B7D73C /* UIView+ASConvenience.m in Headers */, 058D0A82195D060300B7D73C /* ASAssert.h in Headers */, @@ -754,6 +767,7 @@ 058D0A79195D05F900B7D73C /* ASDisplayNode+DebugTiming.mm in Headers */, 058D0A7A195D05F900B7D73C /* ASDisplayNode+UIViewBridge.mm in Headers */, 2967F9E21AB0A5190072E4AB /* ASBasicImageDownloaderInternal.h in Headers */, + 205F0E211B376416007741D0 /* CGRect+ASConvenience.h in Headers */, 058D0A7B195D05F900B7D73C /* ASDisplayNodeInternal.h in Headers */, 058D0A7C195D05F900B7D73C /* ASImageNode+CGExtras.h in Headers */, 058D0A7D195D05F900B7D73C /* ASImageNode+CGExtras.m in Headers */, @@ -932,6 +946,7 @@ 058D0A1E195D050800B7D73C /* ASTextNodeShadower.m in Sources */, 058D0A18195D050800B7D73C /* _ASDisplayLayer.mm in Sources */, 058D0A2C195D050800B7D73C /* ASSentinel.m in Sources */, + 205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */, 205F0E1A1B37339C007741D0 /* ASAbstractLayoutController.mm in Sources */, 464052211A3F83C40061C0BA /* ASDataController.mm in Sources */, 299DA1AA1A828D2900162D41 /* ASBatchContext.mm in Sources */, @@ -969,6 +984,7 @@ 0516FA411A1563D200B4EBED /* ASMultiplexImageNode.mm in Sources */, 058D0A16195D050800B7D73C /* ASImageNode.mm in Sources */, 058D0A29195D050800B7D73C /* ASDisplayNode+DebugTiming.mm in Sources */, + 205F0E1E1B373A2C007741D0 /* ASCollectionViewLayoutController.mm in Sources */, 058D0A22195D050800B7D73C /* _ASAsyncTransaction.m in Sources */, 055F1A3919ABD413004DAFF1 /* ASRangeController.mm in Sources */, 296A0A2F1A9516B2005ACEAA /* ASBatchFetching.m in Sources */, diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index ae5f799566..0ffd65f224 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -9,7 +9,7 @@ #import "ASCollectionView.h" #import "ASAssert.h" -#import "ASFlowLayoutController.h" +#import "ASCollectionViewLayoutController.h" #import "ASRangeController.h" #import "ASDataController.h" #import "ASDisplayNodeInternal.h" @@ -109,7 +109,7 @@ static BOOL _isInterceptedSelector(SEL sel) ASDataController *_dataController; ASRangeController *_rangeController; - ASFlowLayoutController *_layoutController; + ASCollectionViewLayoutController *_layoutController; BOOL _performingBatchUpdates; NSMutableArray *_batchUpdateBlocks; @@ -143,9 +143,7 @@ static BOOL _isInterceptedSelector(SEL sel) asyncDataFetchingEnabled = NO; ASDisplayNodeAssert([layout asdk_isFlowLayout], @"only flow layouts are currently supported"); - - ASFlowLayoutDirection direction = (((UICollectionViewFlowLayout *)layout).scrollDirection == UICollectionViewScrollDirectionHorizontal) ? ASFlowLayoutDirectionHorizontal : ASFlowLayoutDirectionVertical; - _layoutController = [[ASFlowLayoutController alloc] initWithScrollOption:direction]; + _layoutController = [[ASCollectionViewLayoutController alloc] initWithCollectionView:self]; _rangeController = [[ASRangeController alloc] init]; _rangeController.delegate = self; @@ -379,14 +377,14 @@ static BOOL _isInterceptedSelector(SEL sel) ASScrollDirection scrollableDirections = [self scrollableDirections]; if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { // Can scroll horizontally. - if (scrollVelocity.x > 0) { + if (scrollVelocity.x >= 0) { direction |= ASScrollDirectionRight; } else { direction |= ASScrollDirectionLeft; } } if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { // Can scroll vertically. - if (scrollVelocity.y > 0) { + if (scrollVelocity.y >= 0) { direction |= ASScrollDirectionDown; } else { direction |= ASScrollDirectionUp; @@ -493,7 +491,7 @@ static BOOL _isInterceptedSelector(SEL sel) { CGSize restrainedSize = self.bounds.size; - if (_layoutController.layoutDirection == ASFlowLayoutDirectionHorizontal) { + if (ASScrollDirectionContainsHorizontalDirection([self scrollableDirections])) { restrainedSize.width = FLT_MAX; } else { restrainedSize.height = FLT_MAX; diff --git a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm index f18b9b5a95..60bd234b3d 100644 --- a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm +++ b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm @@ -8,9 +8,7 @@ #import "ASAbstractLayoutController.h" -#include #include -#include #import "ASAssert.h" diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h new file mode 100644 index 0000000000..9aa25db0a9 --- /dev/null +++ b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2015-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 + +@class ASCollectionView; + +@interface ASCollectionViewLayoutController : ASAbstractLayoutController + +- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView; + +@end diff --git a/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm new file mode 100644 index 0000000000..15240b3c87 --- /dev/null +++ b/AsyncDisplayKit/Details/ASCollectionViewLayoutController.mm @@ -0,0 +1,153 @@ +/* Copyright (c) 2015-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 "ASCollectionViewLayoutController.h" + +#include + +#import "ASAssert.h" +#import "ASCollectionView.h" +#import "CGRect+ASConvenience.h" + +struct ASDirectionalScreenfulBuffer { + CGFloat positiveDirection; // Positive relative to iOS Core Animation layer coordinate space. + CGFloat negativeDirection; +}; +typedef struct ASDirectionalScreenfulBuffer ASDirectionalScreenfulBuffer; + +ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferHorizontal(ASScrollDirection scrollDirection, + ASRangeTuningParameters rangeTuningParameters) { + ASDirectionalScreenfulBuffer horizontalBuffer = {0, 0}; + BOOL movingRight = ASScrollDirectionContainsRight(scrollDirection); + horizontalBuffer.positiveDirection = movingRight ? rangeTuningParameters.leadingBufferScreenfuls : + rangeTuningParameters.trailingBufferScreenfuls; + horizontalBuffer.negativeDirection = movingRight ? rangeTuningParameters.trailingBufferScreenfuls : + rangeTuningParameters.leadingBufferScreenfuls; + return horizontalBuffer; +} + +ASDirectionalScreenfulBuffer ASDirectionalScreenfulBufferVertical(ASScrollDirection scrollDirection, + ASRangeTuningParameters rangeTuningParameters) { + ASDirectionalScreenfulBuffer verticalBuffer = {0, 0}; + BOOL movingDown = ASScrollDirectionContainsDown(scrollDirection); + verticalBuffer.positiveDirection = movingDown ? rangeTuningParameters.leadingBufferScreenfuls : + rangeTuningParameters.trailingBufferScreenfuls; + verticalBuffer.negativeDirection = movingDown ? rangeTuningParameters.trailingBufferScreenfuls : + rangeTuningParameters.leadingBufferScreenfuls; + return verticalBuffer; +} + +struct ASRangeGeometry { + CGRect rangeBounds; + CGRect updateBounds; +}; +typedef struct ASRangeGeometry ASRangeGeometry; + + +#pragma mark - +#pragma mark ASCollectionViewLayoutController + +@interface ASCollectionViewLayoutController () { + ASCollectionView * __weak _collectionView; + std::vector _updateRangeBoundsIndexedByRangeType; +} +@end + +@implementation ASCollectionViewLayoutController + +- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView { + if (!(self = [super init])) { + return nil; + } + _collectionView = collectionView; + _updateRangeBoundsIndexedByRangeType = std::vector(ASLayoutRangeTypeCount); + return self; +} + +#pragma mark - +#pragma mark Index Paths in Range + +- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection + viewportSize:(CGSize)viewportSize + rangeType:(ASLayoutRangeType)rangeType { + ASRangeGeometry rangeGeometry = [self rangeGeometryWithScrollDirection:scrollDirection + collectionView:_collectionView + rangeTuningParameters:[self tuningParametersForRangeType:rangeType]]; + _updateRangeBoundsIndexedByRangeType[rangeType] = rangeGeometry.updateBounds; + return [self indexPathsForItemsWithinRangeBounds:rangeGeometry.rangeBounds collectionView:_collectionView]; +} + +- (ASRangeGeometry)rangeGeometryWithScrollDirection:(ASScrollDirection)scrollDirection + collectionView:(ASCollectionView *)collectionView + rangeTuningParameters:(ASRangeTuningParameters)rangeTuningParameters { + CGRect rangeBounds = collectionView.bounds; + CGRect updateBounds = collectionView.bounds; + ASScrollDirection scrollableDirections = [collectionView scrollableDirections]; + + BOOL canScrollHorizontally = ASScrollDirectionContainsHorizontalDirection(scrollableDirections); + if (canScrollHorizontally) { + ASDirectionalScreenfulBuffer horizontalBuffer = ASDirectionalScreenfulBufferHorizontal(scrollDirection, + rangeTuningParameters); + rangeBounds = asdk_CGRectExpandHorizontally(rangeBounds, + horizontalBuffer.negativeDirection, + horizontalBuffer.positiveDirection); + // Update bounds is at most 95% of the next/previous screenful and at least half of tuning parameter value. + updateBounds = asdk_CGRectExpandHorizontally(updateBounds, + MIN(horizontalBuffer.negativeDirection * 0.5, 0.95), + MIN(horizontalBuffer.positiveDirection * 0.5, 0.95)); + } + + BOOL canScrollVertically = ASScrollDirectionContainsVerticalDirection(scrollableDirections); + if (canScrollVertically) { + ASDirectionalScreenfulBuffer verticalBuffer = ASDirectionalScreenfulBufferVertical(scrollDirection, + rangeTuningParameters); + rangeBounds = asdk_CGRectExpandVertically(rangeBounds, + verticalBuffer.negativeDirection, + verticalBuffer.positiveDirection); + // Update bounds is at most 95% of the next/previous screenful and at least half of tuning parameter value. + updateBounds = asdk_CGRectExpandVertically(updateBounds, + MIN(verticalBuffer.negativeDirection * 0.5, 0.95), + MIN(verticalBuffer.positiveDirection * 0.5, 0.95)); + } + + return {rangeBounds, updateBounds}; +} + +- (NSSet *)indexPathsForItemsWithinRangeBounds:(CGRect)rangeBounds collectionView:(ASCollectionView *)collectionView { + NSMutableSet *indexPathSet = [[NSMutableSet alloc] init]; + NSArray *layoutAttributes = [collectionView.collectionViewLayout layoutAttributesForElementsInRect:rangeBounds]; + for (UICollectionViewLayoutAttributes *la in layoutAttributes) { + [indexPathSet addObject:la.indexPath]; + } + return indexPathSet; +} + +#pragma mark - +#pragma mark Should Update Range + +- (BOOL)shouldUpdateForVisibleIndexPaths:(NSArray *)indexPaths + viewportSize:(CGSize)viewportSize + rangeType:(ASLayoutRangeType)rangeType { + CGRect updateRangeBounds = _updateRangeBoundsIndexedByRangeType[rangeType]; + if (CGRectIsEmpty(updateRangeBounds)) { + return YES; + } + + CGRect currentBounds = _collectionView.bounds; + if (CGRectIsEmpty(currentBounds)) { + currentBounds = CGRectMake(0, 0, viewportSize.width, viewportSize.height); + } + + if (CGRectContainsRect(updateRangeBounds, currentBounds)) { + return NO; + } else { + return YES; + } +} + +@end diff --git a/AsyncDisplayKit/Details/ASScrollDirection.h b/AsyncDisplayKit/Details/ASScrollDirection.h index 66aebfe001..a46295cf9e 100644 --- a/AsyncDisplayKit/Details/ASScrollDirection.h +++ b/AsyncDisplayKit/Details/ASScrollDirection.h @@ -26,4 +26,9 @@ ASDISPLAYNODE_EXTERN_C_BEGIN BOOL ASScrollDirectionContainsVerticalDirection(ASScrollDirection scrollDirection); BOOL ASScrollDirectionContainsHorizontalDirection(ASScrollDirection scrollDirection); +BOOL ASScrollDirectionContainsRight(ASScrollDirection scrollDirection); +BOOL ASScrollDirectionContainsLeft(ASScrollDirection scrollDirection); +BOOL ASScrollDirectionContainsUp(ASScrollDirection scrollDirection); +BOOL ASScrollDirectionContainsDown(ASScrollDirection scrollDirection); + ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Details/ASScrollDirection.m b/AsyncDisplayKit/Details/ASScrollDirection.m index a228e2cb36..40698d4279 100644 --- a/AsyncDisplayKit/Details/ASScrollDirection.m +++ b/AsyncDisplayKit/Details/ASScrollDirection.m @@ -18,3 +18,19 @@ BOOL ASScrollDirectionContainsVerticalDirection(ASScrollDirection scrollDirectio BOOL ASScrollDirectionContainsHorizontalDirection(ASScrollDirection scrollDirection) { return (scrollDirection & ASScrollDirectionHorizontalDirections) != 0; } + +BOOL ASScrollDirectionContainsRight(ASScrollDirection scrollDirection) { + return (scrollDirection & ASScrollDirectionRight) != 0; +} + +BOOL ASScrollDirectionContainsLeft(ASScrollDirection scrollDirection) { + return (scrollDirection & ASScrollDirectionLeft) != 0; +} + +BOOL ASScrollDirectionContainsUp(ASScrollDirection scrollDirection) { + return (scrollDirection & ASScrollDirectionUp) != 0; +} + +BOOL ASScrollDirectionContainsDown(ASScrollDirection scrollDirection) { + return (scrollDirection & ASScrollDirectionDown) != 0; +} diff --git a/AsyncDisplayKit/Details/CGRect+ASConvenience.h b/AsyncDisplayKit/Details/CGRect+ASConvenience.h new file mode 100644 index 0000000000..a9cc714a31 --- /dev/null +++ b/AsyncDisplayKit/Details/CGRect+ASConvenience.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2015-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 "ASBaseDefines.h" + +ASDISPLAYNODE_EXTERN_C_BEGIN + +CGRect asdk_CGRectExpandHorizontally(CGRect rect, CGFloat negativeMultiplier, CGFloat positiveMultiplier); +CGRect asdk_CGRectExpandVertically(CGRect rect, CGFloat negativeMultiplier, CGFloat positiveMultiplier); + +ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Details/CGRect+ASConvenience.m b/AsyncDisplayKit/Details/CGRect+ASConvenience.m new file mode 100644 index 0000000000..171f3d3986 --- /dev/null +++ b/AsyncDisplayKit/Details/CGRect+ASConvenience.m @@ -0,0 +1,31 @@ +/* Copyright (c) 2015-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 "CGRect+ASConvenience.h" + +CGRect asdk_CGRectExpandHorizontally(CGRect rect, CGFloat negativeMultiplier, CGFloat positiveMultiplier) { + CGFloat negativeDirectionWidth = negativeMultiplier * rect.size.width; + CGFloat positiveDirectionWidth = positiveMultiplier * rect.size.width; + CGFloat width = negativeDirectionWidth + rect.size.width + positiveDirectionWidth; + CGFloat originX = rect.origin.x - negativeDirectionWidth; + return CGRectMake(originX, + rect.origin.y, + width, + rect.size.height); +} + +CGRect asdk_CGRectExpandVertically(CGRect rect, CGFloat negativeMultiplier, CGFloat positiveMultiplier) { + CGFloat negativeDirectionHeight = negativeMultiplier * rect.size.height; + CGFloat positiveDirectionHeight = positiveMultiplier * rect.size.height; + CGFloat height = negativeDirectionHeight + rect.size.height + positiveDirectionHeight; + CGFloat originY = rect.origin.y - negativeDirectionHeight; + return CGRectMake(rect.origin.x, + originY, + rect.size.width, + height); +}