From fab98b32ef130525497bed45246ecba0260db92a Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 14 Feb 2017 14:10:51 -0800 Subject: [PATCH] Fix Pager Node Issues (#3028) * Fix pager node and deprecate zeroContentInsets flag * Do it with the visible state callback * There we are * Put viewController in node debug description --- AsyncDisplayKit.xcodeproj/project.pbxproj | 16 +++ AsyncDisplayKit/ASCollectionView.h | 10 +- AsyncDisplayKit/ASCollectionView.mm | 25 +++-- AsyncDisplayKit/ASDisplayNode.mm | 15 +++ AsyncDisplayKit/ASPagerFlowLayout.m | 99 ++++++++++--------- AsyncDisplayKit/ASPagerNode.h | 18 ++++ AsyncDisplayKit/ASPagerNode.m | 24 ++++- AsyncDisplayKit/AsyncDisplayKit.h | 1 + .../Private/ASResponderChainEnumerator.h | 28 ++++++ .../Private/ASResponderChainEnumerator.m | 41 ++++++++ AsyncDisplayKit/UIResponder+AsyncDisplayKit.h | 24 +++++ AsyncDisplayKit/UIResponder+AsyncDisplayKit.m | 31 ++++++ examples/PagerNode/Sample/PageNode.m | 4 +- examples/PagerNode/Sample/ViewController.m | 2 +- 14 files changed, 271 insertions(+), 67 deletions(-) create mode 100644 AsyncDisplayKit/Private/ASResponderChainEnumerator.h create mode 100644 AsyncDisplayKit/Private/ASResponderChainEnumerator.m create mode 100644 AsyncDisplayKit/UIResponder+AsyncDisplayKit.h create mode 100644 AsyncDisplayKit/UIResponder+AsyncDisplayKit.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index b8193331e8..d8c57f9e5e 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -327,6 +327,10 @@ CC4C2A791D88E3BF0039ACAB /* ASTraceEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = CC4C2A761D88E3BF0039ACAB /* ASTraceEvent.m */; }; CC54A81C1D70079800296A24 /* ASDispatch.h in Headers */ = {isa = PBXBuildFile; fileRef = CC54A81B1D70077A00296A24 /* ASDispatch.h */; settings = {ATTRIBUTES = (Private, ); }; }; CC54A81E1D7008B300296A24 /* ASDispatchTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC54A81D1D7008B300296A24 /* ASDispatchTests.m */; }; + CC55A70D1E529FA200594372 /* UIResponder+AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = CC55A70B1E529FA200594372 /* UIResponder+AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CC55A70E1E529FA200594372 /* UIResponder+AsyncDisplayKit.m in Sources */ = {isa = PBXBuildFile; fileRef = CC55A70C1E529FA200594372 /* UIResponder+AsyncDisplayKit.m */; }; + CC55A7111E52A0F200594372 /* ASResponderChainEnumerator.h in Headers */ = {isa = PBXBuildFile; fileRef = CC55A70F1E52A0F200594372 /* ASResponderChainEnumerator.h */; }; + CC55A7121E52A0F200594372 /* ASResponderChainEnumerator.m in Sources */ = {isa = PBXBuildFile; fileRef = CC55A7101E52A0F200594372 /* ASResponderChainEnumerator.m */; }; CC57EAF71E3939350034C595 /* ASCollectionView+Undeprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = CC2E317F1DAC353700EEE891 /* ASCollectionView+Undeprecated.h */; settings = {ATTRIBUTES = (Private, ); }; }; CC57EAF81E3939450034C595 /* ASTableView+Undeprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = CC512B841DAC45C60054848E /* ASTableView+Undeprecated.h */; settings = {ATTRIBUTES = (Private, ); }; }; CC58AA4B1E398E1D002C8CB4 /* ASBlockTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = CC58AA4A1E398E1D002C8CB4 /* ASBlockTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -711,6 +715,10 @@ CC512B841DAC45C60054848E /* ASTableView+Undeprecated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASTableView+Undeprecated.h"; sourceTree = ""; }; CC54A81B1D70077A00296A24 /* ASDispatch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASDispatch.h; sourceTree = ""; }; CC54A81D1D7008B300296A24 /* ASDispatchTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASDispatchTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + CC55A70B1E529FA200594372 /* UIResponder+AsyncDisplayKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIResponder+AsyncDisplayKit.h"; sourceTree = ""; }; + CC55A70C1E529FA200594372 /* UIResponder+AsyncDisplayKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIResponder+AsyncDisplayKit.m"; sourceTree = ""; }; + CC55A70F1E52A0F200594372 /* ASResponderChainEnumerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASResponderChainEnumerator.h; sourceTree = ""; }; + CC55A7101E52A0F200594372 /* ASResponderChainEnumerator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASResponderChainEnumerator.m; sourceTree = ""; }; CC57EAF91E394EA40034C595 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CC58AA4A1E398E1D002C8CB4 /* ASBlockTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBlockTypes.h; sourceTree = ""; }; CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPhotosFrameworkImageRequest.h; sourceTree = ""; }; @@ -927,6 +935,8 @@ DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */, 68FC85E71CE29C7D00EDD713 /* ASVisibilityProtocols.h */, 68FC85E81CE29C7D00EDD713 /* ASVisibilityProtocols.m */, + CC55A70B1E529FA200594372 /* UIResponder+AsyncDisplayKit.h */, + CC55A70C1E529FA200594372 /* UIResponder+AsyncDisplayKit.m */, 690ED5911E36D118000627C0 /* tvOS */, DE89C1691DCEB9CC00D49D74 /* Debug */, 058D09E1195D050800B7D73C /* Details */, @@ -1120,6 +1130,8 @@ 058D0A01195D050800B7D73C /* Private */ = { isa = PBXGroup; children = ( + CC55A70F1E52A0F200594372 /* ASResponderChainEnumerator.h */, + CC55A7101E52A0F200594372 /* ASResponderChainEnumerator.m */, 6947B0BB1E36B4E30007C478 /* Layout */, CCE04B2A1E313EDA006AEBBB /* Collection Data Adapter */, 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */, @@ -1398,6 +1410,7 @@ B35062151B010EFD0018CF92 /* ASBatchContext.h in Headers */, B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */, 34EFC7631B701CBF00AD841F /* ASCenterLayoutSpec.h in Headers */, + CC55A7111E52A0F200594372 /* ASResponderChainEnumerator.h in Headers */, 18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */, B35061F51B010EFD0018CF92 /* ASCollectionView.h in Headers */, ACE87A2C1D73696800D7FF06 /* ASSectionContext.h in Headers */, @@ -1441,6 +1454,7 @@ 254C6B771BF94DF4003EC431 /* ASTextKitAttributes.h in Headers */, 254C6B7D1BF94DF4003EC431 /* ASTextKitShadower.h in Headers */, 690ED58E1E36BCA6000627C0 /* ASLayoutElementStylePrivate.h in Headers */, + CC55A70D1E529FA200594372 /* UIResponder+AsyncDisplayKit.h in Headers */, 254C6B731BF94DF4003EC431 /* ASTextKitCoreTextAdditions.h in Headers */, 254C6B7A1BF94DF4003EC431 /* ASTextKitRenderer.h in Headers */, 69CB62AC1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */, @@ -1827,6 +1841,7 @@ 18C2ED831B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */, E55D86331CA8A14000A0C26F /* ASLayoutElement.mm in Sources */, 68FC85EC1CE29C7D00EDD713 /* ASVisibilityProtocols.m in Sources */, + CC55A7121E52A0F200594372 /* ASResponderChainEnumerator.m in Sources */, 68B8A4E41CBDB958007E4543 /* ASWeakProxy.m in Sources */, 9C70F20A1CDBE949007D6C76 /* ASTableNode.mm in Sources */, 69CB62AE1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */, @@ -1905,6 +1920,7 @@ 254C6B871BF94F8A003EC431 /* ASTextKitEntityAttribute.m in Sources */, 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */, 254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */, + CC55A70E1E529FA200594372 /* UIResponder+AsyncDisplayKit.m in Sources */, 697796611D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */, B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */, 044284FD1BAA365100D16268 /* UICollectionViewLayout+ASConvenience.m in Sources */, diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 72a6d73faa..70e16916b5 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -111,6 +111,10 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign) BOOL inverted; +@end + +@interface ASCollectionView (Deprecated) + /** * Forces the .contentInset to be UIEdgeInsetsZero. * @@ -119,11 +123,7 @@ NS_ASSUME_NONNULL_BEGIN * automaticallyAdjustsScrollViewInsets, which may not be accessible. ASPagerNode uses this to ensure * its flow layout behaves predictably and does not log undefined layout warnings. */ -@property (nonatomic) BOOL zeroContentInsets; - -@end - -@interface ASCollectionView (Deprecated) +@property (nonatomic) BOOL zeroContentInsets ASDISPLAYNODE_DEPRECATED_MSG("Set automaticallyAdjustsScrollViewInsets=NO on your view controller instead."); /** * The object that acts as the asynchronous delegate of the collection view diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 2103d7b3f0..56e7103300 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -26,6 +26,7 @@ #import #import #import +#import #import #import #import @@ -93,6 +94,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; NSMutableSet *_registeredSupplementaryKinds; CGPoint _deceleratingVelocity; + + BOOL _zeroContentInsets; ASCollectionViewInvalidationStyle _nextLayoutInvalidationStyle; @@ -584,6 +587,16 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; return [_rangeController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; } +- (void)setZeroContentInsets:(BOOL)zeroContentInsets +{ + _zeroContentInsets = zeroContentInsets; +} + +- (BOOL)zeroContentInsets +{ + return _zeroContentInsets; +} + - (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { return [[self nodeForItemAtIndexPath:indexPath] calculatedSize]; @@ -1911,19 +1924,11 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; BOOL changedInNonScrollingDirection = (fixedHorizontally && newBounds.size.width != lastUsedSize.width) || (fixedVertically && newBounds.size.height != lastUsedSize.height); if (changedInNonScrollingDirection) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - // This actually doesn't perform an animation, but prevents the transaction block from being processed in the - // data controller's prevent animation block that would interrupt an interrupted relayout happening in an animation block - // ie. ASCollectionView bounds change on rotation or multi-tasking split view resize. - [self performBatchAnimated:YES updates:^{ - [_dataController relayoutAllNodes]; - } completion:nil]; + [_dataController relayoutAllNodes]; + [_dataController waitUntilAllUpdatesAreCommitted]; // We need to ensure the size requery is done before we update our layout. - [self waitUntilAllUpdatesAreCommitted]; [self.collectionViewLayout invalidateLayout]; } -#pragma clang diagnostic pop } } diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 87825557d5..9e36173275 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -36,6 +36,7 @@ #import #import #import +#import /** * Assert if the current thread owns a mutex. @@ -3784,6 +3785,20 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) { [result addObject:@{ @"frameInWindow" : [NSValue valueWithCGRect:windowFrame] }]; } + // Attempt to find view controller. + // Note that the convenience method asdk_associatedViewController has an assertion + // that it's run on main. Since this is a debug method, let's bypass the assertion + // and run up the chain ourselves. + if (_view != nil) { + for (UIResponder *responder in [_view asdk_responderChainEnumerator]) { + UIViewController *vc = ASDynamicCast(responder, UIViewController); + if (vc) { + [result addObject:@{ @"viewController" : ASObjectDescriptionMakeTiny(vc) }]; + break; + } + } + } + if (_view != nil) { [result addObject:@{ @"frame" : [NSValue valueWithCGRect:_view.frame] }]; } else if (_layer != nil) { diff --git a/AsyncDisplayKit/ASPagerFlowLayout.m b/AsyncDisplayKit/ASPagerFlowLayout.m index 7c4419b7b0..86892c9033 100644 --- a/AsyncDisplayKit/ASPagerFlowLayout.m +++ b/AsyncDisplayKit/ASPagerFlowLayout.m @@ -11,62 +11,43 @@ // #import +#import +#import @interface ASPagerFlowLayout () { - BOOL _didRotate; - CGRect _cachedCollectionViewBounds; - NSIndexPath *_currentIndexPath; + __weak ASCellNode *_currentCellNode; } @end @implementation ASPagerFlowLayout -- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity +- (ASCollectionView *)asCollectionView { - NSInteger currentPage = ceil(proposedContentOffset.x / self.collectionView.bounds.size.width); - _currentIndexPath = [NSIndexPath indexPathForItem:currentPage inSection:0]; - - return [super targetContentOffsetForProposedContentOffset:proposedContentOffset withScrollingVelocity:velocity]; + // Dynamic cast is too slow and not worth it. + return (ASCollectionView *)self.collectionView; } - -- (void)prepareForAnimatedBoundsChange:(CGRect)oldBounds +- (void)prepareLayout { - // Cache the current page if a rotation did happen. This happens before the rotation animation - // is occuring and the bounds changed so we use this as an opportunity to cache the current index path - if (_cachedCollectionViewBounds.size.width != self.collectionView.bounds.size.width) { - _cachedCollectionViewBounds = self.collectionView.bounds; - - // Figurring out current page based on the old bounds visible space - CGRect visibleRect = oldBounds; - - CGFloat visibleXCenter = CGRectGetMidX(visibleRect); - NSArray *layoutAttributes = [self layoutAttributesForElementsInRect:visibleRect]; - for (UICollectionViewLayoutAttributes *attributes in layoutAttributes) { - if ([attributes representedElementCategory] == UICollectionElementCategoryCell && attributes.center.x == visibleXCenter) { - _currentIndexPath = attributes.indexPath; - break; - } - } - - _didRotate = YES; - } - - [super prepareForAnimatedBoundsChange:oldBounds]; + [super prepareLayout]; + if (_currentCellNode == nil) { + [self _updateCurrentNode]; + } } + - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset { - // Don't mess around if the user is interacting with the page node. Although if just a rotation happened we should - // try to use the current index path to not end up setting the target content offset to something in between pages - if (_didRotate || (!self.collectionView.isDecelerating && !self.collectionView.isTracking)) { - _didRotate = NO; - if (_currentIndexPath) { - return [self _targetContentOffsetForItemAtIndexPath:_currentIndexPath proposedContentOffset:proposedContentOffset]; - } + // Don't mess around if the user is interacting with the page node. Although if just a rotation happened we should + // try to use the current index path to not end up setting the target content offset to something in between pages + if (!self.collectionView.decelerating && !self.collectionView.tracking) { + NSIndexPath *indexPath = [self.asCollectionView indexPathForNode:_currentCellNode]; + if (indexPath) { + return [self _targetContentOffsetForItemAtIndexPath:indexPath proposedContentOffset:proposedContentOffset]; } - - return [super targetContentOffsetForProposedContentOffset:proposedContentOffset]; + } + + return [super targetContentOffsetForProposedContentOffset:proposedContentOffset]; } - (CGPoint)_targetContentOffsetForItemAtIndexPath:(NSIndexPath *)indexPath proposedContentOffset:(CGPoint)proposedContentOffset @@ -75,19 +56,49 @@ return proposedContentOffset; } - UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:_currentIndexPath]; + UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; if (attributes == nil) { return proposedContentOffset; } - + CGFloat xOffset = (CGRectGetWidth(self.collectionView.bounds) - CGRectGetWidth(attributes.frame)) / 2.0; return CGPointMake(attributes.frame.origin.x - xOffset, proposedContentOffset.y); } - (BOOL)_dataSourceIsEmpty { - return ([self.collectionView numberOfSections] == 0 || - [self.collectionView numberOfItemsInSection:0] == 0); + return ([self.collectionView numberOfSections] == 0 || + [self.collectionView numberOfItemsInSection:0] == 0); +} + +- (void)_updateCurrentNode +{ + // Never change node during an animated bounds change (rotation) + // NOTE! Listening for -prepareForAnimatedBoundsChange and -finalizeAnimatedBoundsChange + // isn't sufficient here! It's broken! + NSArray *animKeys = self.collectionView.layer.animationKeys; + for (NSString *key in animKeys) { + if ([key hasPrefix:@"bounds"]) { + return; + } + } + + CGRect bounds = self.collectionView.bounds; + CGRect rect = CGRectMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds), 1, 1); + + NSIndexPath *indexPath = [self layoutAttributesForElementsInRect:rect].firstObject.indexPath; + if (indexPath) { + ASCellNode *node = [self.asCollectionView nodeForItemAtIndexPath:indexPath]; + if (node) { + _currentCellNode = node; + } + } +} + +- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds +{ + [self _updateCurrentNode]; + return [super shouldInvalidateLayoutForBoundsChange:newBounds]; } @end diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index 658b7e1204..c89911c86b 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -120,6 +120,24 @@ NS_ASSUME_NONNULL_BEGIN */ - (NSInteger)indexOfPageWithNode:(ASCellNode *)node; +/** + * Tells the pager node to allow its view controller to automatically adjust its content insets. + * + * @see UIViewController.automaticallyAdjustsScrollViewInsets + * + * @discussion ASPagerNode should usually not have its content insets automatically adjusted + * because it scrolls horizontally, and flow layout will log errors because the pages + * do not fit between the top & bottom insets of the collection view. + * + * The default value is NO, which means that ASPagerNode expects that its view controller will + * have automaticallyAdjustsScrollViewInsets=NO. + * + * If this property is NO, but your view controller has automaticallyAdjustsScrollViewInsets=YES, + * the pager node will set the property to NO and log a warning message. In the future, + * the pager node will just log the warning, and you'll need to configure your view controller on your own. + */ +@property (nonatomic, assign) BOOL allowsAutomaticInsetsAdjustment; + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index ffd2e34bea..b1c42e8e15 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -12,11 +12,13 @@ #import #import +#import #import #import #import #import #import +#import @interface ASPagerNode () { @@ -80,11 +82,6 @@ cv.allowsSelection = NO; cv.showsVerticalScrollIndicator = NO; cv.showsHorizontalScrollIndicator = NO; - - // Zeroing contentInset is important, as UIKit will set the top inset for the navigation bar even though - // our view is only horizontally scrollable. This causes UICollectionViewFlowLayout to log a warning. - // From here we cannot disable this directly (UIViewController's automaticallyAdjustsScrollViewInsets). - cv.zeroContentInsets = YES; ASRangeTuningParameters minimumRenderParams = { .leadingBufferScreenfuls = 0.0, .trailingBufferScreenfuls = 0.0 }; ASRangeTuningParameters minimumPreloadParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; @@ -211,4 +208,21 @@ [self setDelegate:nil]; } +- (void)didEnterVisibleState +{ + [super didEnterVisibleState]; + + // Check that our view controller does not automatically set our content insets + // It would be better to have a -didEnterHierarchy hook to put this in, but + // such a hook doesn't currently exist, and in every use case I can imagine, + // the pager is not hosted inside a range-managed node. + if (_allowsAutomaticInsetsAdjustment == NO) { + UIViewController *vc = [self.view asdk_associatedViewController]; + if (vc.automaticallyAdjustsScrollViewInsets) { + NSLog(@"AsyncDisplayKit: ASPagerNode is setting automaticallyAdjustsScrollViewInsets=NO on its owning view controller %@. This automatic behavior will be disabled in the future. Set allowsAutomaticInsetsAdjustment=YES on the pager node to suppress this behavior.", vc); + vc.automaticallyAdjustsScrollViewInsets = NO; + } + } +} + @end diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 888a09126a..53f251837f 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -106,6 +106,7 @@ #import #import #import +#import #import #import diff --git a/AsyncDisplayKit/Private/ASResponderChainEnumerator.h b/AsyncDisplayKit/Private/ASResponderChainEnumerator.h new file mode 100644 index 0000000000..4e292edab8 --- /dev/null +++ b/AsyncDisplayKit/Private/ASResponderChainEnumerator.h @@ -0,0 +1,28 @@ +// +// ASResponderChainEnumerator.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/13/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +AS_SUBCLASSING_RESTRICTED +@interface ASResponderChainEnumerator : NSEnumerator + +- (instancetype)initWithResponder:(UIResponder *)responder; + +@end + +@interface UIResponder (ASResponderChainEnumerator) + +- (ASResponderChainEnumerator *)asdk_responderChainEnumerator; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Private/ASResponderChainEnumerator.m b/AsyncDisplayKit/Private/ASResponderChainEnumerator.m new file mode 100644 index 0000000000..2310c5c861 --- /dev/null +++ b/AsyncDisplayKit/Private/ASResponderChainEnumerator.m @@ -0,0 +1,41 @@ +// +// ASResponderChainEnumerator.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/13/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import "ASResponderChainEnumerator.h" + +@implementation ASResponderChainEnumerator { + UIResponder *_currentResponder; +} + +- (instancetype)initWithResponder:(UIResponder *)responder +{ + if (self = [super init]) { + _currentResponder = responder; + } + return self; +} + +#pragma mark - NSEnumerator + +- (id)nextObject +{ + id result = [_currentResponder nextResponder]; + _currentResponder = result; + return result; +} + +@end + +@implementation UIResponder (ASResponderChainEnumerator) + +- (NSEnumerator *)asdk_responderChainEnumerator +{ + return [[ASResponderChainEnumerator alloc] initWithResponder:self]; +} + +@end diff --git a/AsyncDisplayKit/UIResponder+AsyncDisplayKit.h b/AsyncDisplayKit/UIResponder+AsyncDisplayKit.h new file mode 100644 index 0000000000..c39b5275a9 --- /dev/null +++ b/AsyncDisplayKit/UIResponder+AsyncDisplayKit.h @@ -0,0 +1,24 @@ +// +// UIResponder+AsyncDisplayKit.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/13/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIResponder (AsyncDisplayKit) + +/** + * The nearest view controller above this responder, if one exists. + * + * This property must be accessed on the main thread. + */ +@property (nonatomic, nullable, readonly) __kindof UIViewController *asdk_associatedViewController; + +@end + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/UIResponder+AsyncDisplayKit.m b/AsyncDisplayKit/UIResponder+AsyncDisplayKit.m new file mode 100644 index 0000000000..55e4c9fbfa --- /dev/null +++ b/AsyncDisplayKit/UIResponder+AsyncDisplayKit.m @@ -0,0 +1,31 @@ +// +// UIResponder+AsyncDisplayKit.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 2/13/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import "UIResponder+AsyncDisplayKit.h" + +#import +#import +#import + +@implementation UIResponder (AsyncDisplayKit) + +- (__kindof UIViewController *)asdk_associatedViewController +{ + ASDisplayNodeAssertMainThread(); + + for (UIResponder *responder in [self asdk_responderChainEnumerator]) { + UIViewController *vc = ASDynamicCast(responder, UIViewController); + if (vc) { + return vc; + } + } + return nil; +} + +@end + diff --git a/examples/PagerNode/Sample/PageNode.m b/examples/PagerNode/Sample/PageNode.m index 75f9942e7d..bda108a1bf 100644 --- a/examples/PagerNode/Sample/PageNode.m +++ b/examples/PagerNode/Sample/PageNode.m @@ -21,9 +21,9 @@ @implementation PageNode -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize +- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { - return [ASLayout layoutWithLayoutElement:self size:constrainedSize.max]; + return constrainedSize; } - (void)fetchData diff --git a/examples/PagerNode/Sample/ViewController.m b/examples/PagerNode/Sample/ViewController.m index 8adf45354c..001c5c069a 100644 --- a/examples/PagerNode/Sample/ViewController.m +++ b/examples/PagerNode/Sample/ViewController.m @@ -46,7 +46,7 @@ static UIColor *randomColor() { self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Next" style:UIBarButtonItemStylePlain target:self action:@selector(scrollToNextPage:)]; self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Previous" style:UIBarButtonItemStylePlain target:self action:@selector(scrollToPreviousPage:)]; - + self.automaticallyAdjustsScrollViewInsets = NO; return self; }