mirror of
https://github.com/HackPlan/AsyncDisplayKit.git
synced 2026-03-28 23:58:50 +08:00
Merge remote-tracking branch 'facebook/master' into 500pxgram
This commit is contained in:
@@ -264,6 +264,10 @@
|
||||
698548661CA9E025008A345F /* ASEnvironment.m in Sources */ = {isa = PBXBuildFile; fileRef = 698548621CA9E025008A345F /* ASEnvironment.m */; };
|
||||
698C8B611CAB49FC0052DC3F /* ASLayoutableExtensibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 698C8B601CAB49FC0052DC3F /* ASLayoutableExtensibility.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
698C8B621CAB49FC0052DC3F /* ASLayoutableExtensibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 698C8B601CAB49FC0052DC3F /* ASLayoutableExtensibility.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
69CB62AB1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */ = {isa = PBXBuildFile; fileRef = 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */; };
|
||||
69CB62AC1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */ = {isa = PBXBuildFile; fileRef = 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */; };
|
||||
69CB62AD1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */; };
|
||||
69CB62AE1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */; };
|
||||
69E1006D1CA89CB600D88C1B /* ASEnvironmentInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */; };
|
||||
69E1006E1CA89CB600D88C1B /* ASEnvironmentInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */; };
|
||||
69E1006F1CA89CB600D88C1B /* ASEnvironmentInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */; };
|
||||
@@ -745,6 +749,8 @@
|
||||
698548611CA9E025008A345F /* ASEnvironment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEnvironment.h; sourceTree = "<group>"; };
|
||||
698548621CA9E025008A345F /* ASEnvironment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASEnvironment.m; sourceTree = "<group>"; };
|
||||
698C8B601CAB49FC0052DC3F /* ASLayoutableExtensibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutableExtensibility.h; path = AsyncDisplayKit/Layout/ASLayoutableExtensibility.h; sourceTree = "<group>"; };
|
||||
69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASDisplayViewAccessiblity.h; sourceTree = "<group>"; };
|
||||
69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASDisplayViewAccessiblity.mm; sourceTree = "<group>"; };
|
||||
69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEnvironmentInternal.h; sourceTree = "<group>"; };
|
||||
69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEnvironmentInternal.mm; sourceTree = "<group>"; };
|
||||
69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASRangeControllerUpdateRangeProtocol+Beta.h"; sourceTree = "<group>"; };
|
||||
@@ -1123,6 +1129,8 @@
|
||||
058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */,
|
||||
058D09E4195D050800B7D73C /* _ASDisplayView.h */,
|
||||
058D09E5195D050800B7D73C /* _ASDisplayView.mm */,
|
||||
69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */,
|
||||
69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */,
|
||||
205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */,
|
||||
205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */,
|
||||
054963471A1EA066000F8E56 /* ASBasicImageDownloader.h */,
|
||||
@@ -1510,6 +1518,7 @@
|
||||
0574D5E219C110940097DC25 /* ASTableViewProtocols.h in Headers */,
|
||||
81EE384F1C8E94F000456208 /* ASRunLoopQueue.h in Headers */,
|
||||
CC3B20831C3F76D600798563 /* ASPendingStateController.h in Headers */,
|
||||
69CB62AB1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */,
|
||||
058D0A51195D05CB00B7D73C /* ASTextNode.h in Headers */,
|
||||
058D0A81195D05F900B7D73C /* ASThread.h in Headers */,
|
||||
ACC945A91BA9E7A0005E1FB8 /* ASViewController.h in Headers */,
|
||||
@@ -1553,6 +1562,7 @@
|
||||
DE84918D1C8FFF2B003D89E9 /* ASRunLoopQueue.h in Headers */,
|
||||
254C6B731BF94DF4003EC431 /* ASTextKitCoreTextAdditions.h in Headers */,
|
||||
254C6B7A1BF94DF4003EC431 /* ASTextKitRenderer.h in Headers */,
|
||||
69CB62AC1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */,
|
||||
254C6B7C1BF94DF4003EC431 /* ASTextKitRenderer+TextChecking.h in Headers */,
|
||||
34EFC7611B701C9C00AD841F /* ASBackgroundLayoutSpec.h in Headers */,
|
||||
B35062591B010F070018CF92 /* ASBaseDefines.h in Headers */,
|
||||
@@ -1940,6 +1950,7 @@
|
||||
7A06A73A1C35F08800FE8DAA /* ASRelativeLayoutSpec.mm in Sources */,
|
||||
257754921BED28F300737CA5 /* ASEqualityHashHelpers.mm in Sources */,
|
||||
E52405B31C8FEF03004DC8E7 /* ASDisplayNodeLayoutContext.mm in Sources */,
|
||||
69CB62AD1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */,
|
||||
257754AB1BEE44CD00737CA5 /* ASTextKitEntityAttribute.m in Sources */,
|
||||
055F1A3919ABD413004DAFF1 /* ASRangeController.mm in Sources */,
|
||||
044285091BAA63FE00D16268 /* ASBatchFetching.m in Sources */,
|
||||
@@ -2051,6 +2062,7 @@
|
||||
34EFC7641B701CC600AD841F /* ASCenterLayoutSpec.mm in Sources */,
|
||||
18C2ED831B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */,
|
||||
E55D86331CA8A14000A0C26F /* ASLayoutable.mm in Sources */,
|
||||
69CB62AE1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */,
|
||||
B35061F61B010EFD0018CF92 /* ASCollectionView.mm in Sources */,
|
||||
509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */,
|
||||
B35061F91B010EFD0018CF92 /* ASControlNode.mm in Sources */,
|
||||
|
||||
@@ -91,7 +91,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
#pragma mark -
|
||||
#pragma mark ASCollectionView.
|
||||
|
||||
@interface ASCollectionView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASDataControllerSource, ASCellNodeLayoutDelegate, ASDelegateProxyInterceptor> {
|
||||
@interface ASCollectionView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASDataControllerSource, ASCellNodeLayoutDelegate, ASDelegateProxyInterceptor, ASBatchFetchingScrollView> {
|
||||
ASCollectionViewProxy *_proxyDataSource;
|
||||
ASCollectionViewProxy *_proxyDelegate;
|
||||
|
||||
@@ -605,8 +605,45 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
cellNode.scrollView = nil;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Scroll Direction.
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
{
|
||||
// If a scroll happenes the current range mode needs to go to full
|
||||
ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController];
|
||||
if (ASInterfaceStateIncludesVisible(interfaceState)) {
|
||||
[_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull];
|
||||
}
|
||||
|
||||
for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) {
|
||||
// Only nodes that respond to the selector are added to _cellsForVisibilityUpdates
|
||||
[[collectionCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged
|
||||
inScrollView:scrollView
|
||||
withCellFrame:collectionCell.frame];
|
||||
}
|
||||
if (_asyncDelegateImplementsScrollviewDidScroll) {
|
||||
[_asyncDelegate scrollViewDidScroll:scrollView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
|
||||
{
|
||||
_deceleratingVelocity = CGPointMake(
|
||||
scrollView.contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0),
|
||||
scrollView.contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 0)
|
||||
);
|
||||
|
||||
if (targetContentOffset != NULL) {
|
||||
ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist");
|
||||
[self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset];
|
||||
}
|
||||
|
||||
if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
|
||||
[_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Scroll Direction.
|
||||
|
||||
- (ASScrollDirection)scrollDirection
|
||||
{
|
||||
@@ -700,45 +737,14 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Batch Fetching
|
||||
#pragma mark - Batch Fetching
|
||||
|
||||
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
|
||||
- (ASBatchContext *)batchContext
|
||||
{
|
||||
_deceleratingVelocity = CGPointMake(
|
||||
scrollView.contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0),
|
||||
scrollView.contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 0)
|
||||
);
|
||||
|
||||
if (targetContentOffset != NULL) {
|
||||
[self handleBatchFetchScrollingToOffset:*targetContentOffset];
|
||||
}
|
||||
|
||||
if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
|
||||
[_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
|
||||
}
|
||||
return _batchContext;
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
{
|
||||
// If a scroll happenes the current range mode needs to go to full
|
||||
ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController];
|
||||
if (ASInterfaceStateIncludesVisible(interfaceState)) {
|
||||
[_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull];
|
||||
}
|
||||
|
||||
for (_ASCollectionViewCell *collectionCell in _cellsForVisibilityUpdates) {
|
||||
// Only nodes that respond to the selector are added to _cellsForVisibilityUpdates
|
||||
[[collectionCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged
|
||||
inScrollView:scrollView
|
||||
withCellFrame:collectionCell.frame];
|
||||
}
|
||||
if (_asyncDelegateImplementsScrollviewDidScroll) {
|
||||
[_asyncDelegate scrollViewDidScroll:scrollView];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)shouldBatchFetch
|
||||
- (BOOL)canBatchFetch
|
||||
{
|
||||
// if the delegate does not respond to this method, there is no point in starting to fetch
|
||||
BOOL canFetch = [_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)];
|
||||
@@ -749,16 +755,35 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleBatchFetchScrollingToOffset:(CGPoint)targetOffset
|
||||
- (void)_scheduleCheckForBatchFetching
|
||||
{
|
||||
ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist");
|
||||
|
||||
if (![self shouldBatchFetch]) {
|
||||
// Push this to the next runloop to be sure the scroll view has the right content size
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self _checkForBatchFetching];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)_checkForBatchFetching
|
||||
{
|
||||
// Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset:
|
||||
if (self.isDragging || self.isTracking) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollDirection], self.bounds, self.contentSize, targetOffset, _leadingScreensForBatching)) {
|
||||
[_batchContext beginBatchFetching];
|
||||
[self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollableDirections] contentOffset:self.contentOffset];
|
||||
}
|
||||
|
||||
- (void)_beginBatchFetchingIfNeededWithScrollView:(UIScrollView<ASBatchFetchingScrollView> *)scrollView forScrollDirection:(ASScrollDirection)scrollDirection contentOffset:(CGPoint)contentOffset
|
||||
{
|
||||
if (ASDisplayShouldFetchBatchForScrollView(self, scrollDirection, contentOffset)) {
|
||||
[self _beginBatchFetching];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_beginBatchFetching
|
||||
{
|
||||
[_batchContext beginBatchFetching];
|
||||
if ([_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext];
|
||||
});
|
||||
@@ -954,7 +979,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
for (dispatch_block_t block in _batchUpdateBlocks) {
|
||||
block();
|
||||
}
|
||||
} completion:completion];
|
||||
} completion:^(BOOL finished){
|
||||
[self _scheduleCheckForBatchFetching];
|
||||
if (completion) { completion(finished); }
|
||||
}];
|
||||
});
|
||||
|
||||
[_batchUpdateBlocks removeAllObjects];
|
||||
@@ -977,6 +1005,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO];
|
||||
[UIView performWithoutAnimation:^{
|
||||
[super insertItemsAtIndexPaths:indexPaths];
|
||||
[self _scheduleCheckForBatchFetching];
|
||||
}];
|
||||
}
|
||||
}
|
||||
@@ -997,6 +1026,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO];
|
||||
[UIView performWithoutAnimation:^{
|
||||
[super deleteItemsAtIndexPaths:indexPaths];
|
||||
[self _scheduleCheckForBatchFetching];
|
||||
}];
|
||||
}
|
||||
}
|
||||
@@ -1017,6 +1047,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO];
|
||||
[UIView performWithoutAnimation:^{
|
||||
[super insertSections:indexSet];
|
||||
[self _scheduleCheckForBatchFetching];
|
||||
}];
|
||||
}
|
||||
}
|
||||
@@ -1037,6 +1068,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
[_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO];
|
||||
[UIView performWithoutAnimation:^{
|
||||
[super deleteSections:indexSet];
|
||||
[self _scheduleCheckForBatchFetching];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +256,6 @@ static BOOL _enableHitTestDebug = NO;
|
||||
_debugHighlightOverlay = [[ASImageNode alloc] init];
|
||||
_debugHighlightOverlay.zPosition = 1000; // CALayer doesn't have -moveSublayerToFront, but this will ensure we're over the top of any siblings.
|
||||
_debugHighlightOverlay.layerBacked = YES;
|
||||
_debugHighlightOverlay.backgroundColor = [[UIColor greenColor] colorWithAlphaComponent:0.4];
|
||||
[self addSubnode:_debugHighlightOverlay];
|
||||
}
|
||||
}
|
||||
@@ -277,16 +276,17 @@ static BOOL _enableHitTestDebug = NO;
|
||||
}
|
||||
|
||||
// Have we seen this target before for this event?
|
||||
NSMutableArray *targetActions = [eventDispatchTable objectForKey:target];
|
||||
NSMutableSet *targetActions = [eventDispatchTable objectForKey:target];
|
||||
if (!targetActions)
|
||||
{
|
||||
// Nope. Create an actions array for it.
|
||||
targetActions = [[NSMutableArray alloc] initWithCapacity:kASControlNodeActionDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries.
|
||||
// Nope. Create an action set for it.
|
||||
targetActions = [[NSMutableSet alloc] initWithCapacity:kASControlNodeActionDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries.
|
||||
[eventDispatchTable setObject:targetActions forKey:target];
|
||||
}
|
||||
|
||||
// Add the action message.
|
||||
// Note that bizarrely enough UIControl (at least according to the docs) supports duplicate target-action pairs for a particular control event, so we replicate that behavior.
|
||||
// UIControl does not support duplicate target-action-events entries, so we replicate that behavior.
|
||||
// See: https://github.com/facebook/AsyncDisplayKit/files/205466/DuplicateActionsTest.playground.zip
|
||||
[targetActions addObject:NSStringFromSelector(action)];
|
||||
});
|
||||
|
||||
@@ -371,7 +371,7 @@ static BOOL _enableHitTestDebug = NO;
|
||||
if (!target)
|
||||
{
|
||||
// Look at every target, removing target-pairs that have action (or all of its actions).
|
||||
for (id aTarget in eventDispatchTable)
|
||||
for (id aTarget in [eventDispatchTable copy])
|
||||
removeActionFromTarget(aTarget, action);
|
||||
}
|
||||
else
|
||||
@@ -468,6 +468,8 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v
|
||||
// will not search sub-hierarchies if one of our parents does not return YES for pointInside:. In such a scenario, hitTestSlop
|
||||
// may not be able to expand the tap target as much as desired without also setting some hitTestSlop on the limiting parents.
|
||||
CGRect intersectRect = UIEdgeInsetsInsetRect(self.bounds, [self hitTestSlop]);
|
||||
UIRectEdge clippedEdges = UIRectEdgeNone;
|
||||
UIRectEdge clipsToBoundsClippedEdges = UIRectEdgeNone;
|
||||
CALayer *layer = self.layer;
|
||||
CALayer *intersectLayer = layer;
|
||||
CALayer *intersectSuperlayer = layer.superlayer;
|
||||
@@ -477,21 +479,106 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v
|
||||
while (intersectSuperlayer && ![intersectSuperlayer.delegate respondsToSelector:@selector(contentOffset)]) {
|
||||
// Get our parent's tappable bounds. If the parent has an associated node, consider hitTestSlop, as it will extend its pointInside:.
|
||||
CGRect parentHitRect = intersectSuperlayer.bounds;
|
||||
BOOL parentClipsToBounds = NO;
|
||||
|
||||
ASDisplayNode *parentNode = ASLayerToDisplayNode(intersectSuperlayer);
|
||||
if (parentNode) {
|
||||
parentHitRect = UIEdgeInsetsInsetRect(parentHitRect, [parentNode hitTestSlop]);
|
||||
UIEdgeInsets parentSlop = [parentNode hitTestSlop];
|
||||
|
||||
// if parent has a hitTestSlop as well, we need to account for the fact that events will be routed towards us in that area too.
|
||||
if (!UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, parentSlop)) {
|
||||
parentClipsToBounds = parentNode.clipsToBounds;
|
||||
// if the parent is clipping, this will prevent us from showing the overlay outside that area.
|
||||
// in this case, we will make the overlay smaller so that the special highlight to indicate the overlay
|
||||
// cannot accurately display the true tappable area is shown.
|
||||
if (!parentClipsToBounds) {
|
||||
parentHitRect = UIEdgeInsetsInsetRect(parentHitRect, [parentNode hitTestSlop]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert our current rectangle to parent coordinates, and intersect with the parent's hit rect.
|
||||
CGRect intersectRectInParentCoordinates = [intersectSuperlayer convertRect:intersectRect fromLayer:intersectLayer];
|
||||
intersectRect = CGRectIntersection(parentHitRect, intersectRectInParentCoordinates);
|
||||
if (!CGSizeEqualToSize(parentHitRect.size, intersectRectInParentCoordinates.size)) {
|
||||
clippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates
|
||||
parentRect:parentHitRect rectEdge:clippedEdges];
|
||||
if (parentClipsToBounds) {
|
||||
clipsToBoundsClippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates
|
||||
parentRect:parentHitRect rectEdge:clipsToBoundsClippedEdges];
|
||||
}
|
||||
}
|
||||
|
||||
// Advance up the tree.
|
||||
intersectLayer = intersectSuperlayer;
|
||||
intersectSuperlayer = intersectLayer.superlayer;
|
||||
}
|
||||
|
||||
_debugHighlightOverlay.frame = [intersectLayer convertRect:intersectRect toLayer:layer];
|
||||
CGRect finalRect = [intersectLayer convertRect:intersectRect toLayer:layer];
|
||||
UIColor *fillColor = [[UIColor greenColor] colorWithAlphaComponent:0.4];
|
||||
|
||||
// determine if edges are clipped
|
||||
if (clippedEdges == UIRectEdgeNone) {
|
||||
_debugHighlightOverlay.backgroundColor = fillColor;
|
||||
} else {
|
||||
const CGFloat borderWidth = 2.0;
|
||||
UIColor *borderColor = [[UIColor orangeColor] colorWithAlphaComponent:0.8];
|
||||
UIColor *clipsBorderColor = [UIColor colorWithRed:30/255.0 green:90/255.0 blue:50/255.0 alpha:0.7];
|
||||
CGRect imgRect = CGRectMake(0, 0, 2.0 * borderWidth + 1.0, 2.0 * borderWidth + 1.0);
|
||||
UIGraphicsBeginImageContext(imgRect.size);
|
||||
|
||||
[fillColor setFill];
|
||||
UIRectFill(imgRect);
|
||||
|
||||
[self drawEdgeIfClippedWithEdges:clippedEdges color:clipsBorderColor borderWidth:borderWidth imgRect:imgRect];
|
||||
[self drawEdgeIfClippedWithEdges:clipsToBoundsClippedEdges color:borderColor borderWidth:borderWidth imgRect:imgRect];
|
||||
|
||||
UIImage *debugHighlightImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
UIEdgeInsets edgeInsets = UIEdgeInsetsMake(borderWidth, borderWidth, borderWidth, borderWidth);
|
||||
_debugHighlightOverlay.image = [debugHighlightImage resizableImageWithCapInsets:edgeInsets
|
||||
resizingMode:UIImageResizingModeStretch];
|
||||
_debugHighlightOverlay.backgroundColor = nil;
|
||||
}
|
||||
|
||||
_debugHighlightOverlay.frame = finalRect;
|
||||
}
|
||||
}
|
||||
|
||||
- (UIRectEdge)setEdgesOfIntersectionForChildRect:(CGRect)childRect parentRect:(CGRect)parentRect rectEdge:(UIRectEdge)rectEdge
|
||||
{
|
||||
if (childRect.origin.y < parentRect.origin.y) {
|
||||
rectEdge |= UIRectEdgeTop;
|
||||
}
|
||||
if (childRect.origin.x < parentRect.origin.x) {
|
||||
rectEdge |= UIRectEdgeLeft;
|
||||
}
|
||||
if (CGRectGetMaxY(childRect) > CGRectGetMaxY(parentRect)) {
|
||||
rectEdge |= UIRectEdgeBottom;
|
||||
}
|
||||
if (CGRectGetMaxX(childRect) > CGRectGetMaxX(parentRect)) {
|
||||
rectEdge |= UIRectEdgeRight;
|
||||
}
|
||||
|
||||
return rectEdge;
|
||||
}
|
||||
|
||||
- (void)drawEdgeIfClippedWithEdges:(UIRectEdge)rectEdge color:(UIColor *)color borderWidth:(CGFloat)borderWidth imgRect:(CGRect)imgRect
|
||||
{
|
||||
[color setFill];
|
||||
|
||||
if (rectEdge & UIRectEdgeTop) {
|
||||
UIRectFill(CGRectMake(0.0, 0.0, imgRect.size.width, borderWidth));
|
||||
}
|
||||
if (rectEdge & UIRectEdgeLeft) {
|
||||
UIRectFill(CGRectMake(0.0, 0.0, borderWidth, imgRect.size.height));
|
||||
}
|
||||
if (rectEdge & UIRectEdgeBottom) {
|
||||
UIRectFill(CGRectMake(0.0, imgRect.size.height - borderWidth, imgRect.size.width, borderWidth));
|
||||
}
|
||||
if (rectEdge & UIRectEdgeRight) {
|
||||
UIRectFill(CGRectMake(imgRect.size.width - borderWidth, 0.0, borderWidth, imgRect.size.height));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -695,20 +695,30 @@ NS_ASSUME_NONNULL_END
|
||||
- (nullable UIView *)preferredFocusedView;
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@interface ASDisplayNode (UIViewBridgeAccessibility)
|
||||
|
||||
// Accessibility support
|
||||
@property (atomic, assign) BOOL isAccessibilityElement;
|
||||
@property (nullable, atomic, copy) NSString *accessibilityLabel;
|
||||
@property (nullable, atomic, copy) NSString *accessibilityHint;
|
||||
@property (nullable, atomic, copy) NSString *accessibilityValue;
|
||||
@property (atomic, assign) UIAccessibilityTraits accessibilityTraits;
|
||||
@property (atomic, assign) CGRect accessibilityFrame;
|
||||
@property (nullable, atomic, strong) NSString *accessibilityLanguage;
|
||||
@property (atomic, assign) BOOL accessibilityElementsHidden;
|
||||
@property (atomic, assign) BOOL accessibilityViewIsModal;
|
||||
@property (atomic, assign) BOOL shouldGroupAccessibilityChildren;
|
||||
@property (nonatomic, assign) BOOL isAccessibilityElement;
|
||||
@property (nonatomic, copy, nullable) NSString *accessibilityLabel;
|
||||
@property (nonatomic, copy, nullable) NSString *accessibilityHint;
|
||||
@property (nonatomic, copy, nullable) NSString *accessibilityValue;
|
||||
@property (nonatomic, assign) UIAccessibilityTraits accessibilityTraits;
|
||||
@property (nonatomic, assign) CGRect accessibilityFrame;
|
||||
@property (nonatomic, copy, nullable) UIBezierPath *accessibilityPath;
|
||||
@property (nonatomic, assign) CGPoint accessibilityActivationPoint;
|
||||
@property (nonatomic, copy, nullable) NSString *accessibilityLanguage;
|
||||
@property (nonatomic, assign) BOOL accessibilityElementsHidden;
|
||||
@property (nonatomic, assign) BOOL accessibilityViewIsModal;
|
||||
@property (nonatomic, assign) BOOL shouldGroupAccessibilityChildren;
|
||||
@property (nonatomic, assign) UIAccessibilityNavigationStyle accessibilityNavigationStyle;
|
||||
#if TARGET_OS_TV
|
||||
@property(nonatomic, copy, nullable) NSArray *accessibilityHeaderElements;
|
||||
#endif
|
||||
|
||||
// Accessibility identification support
|
||||
@property (nullable, nonatomic, copy) NSString *accessibilityIdentifier;
|
||||
@property (nonatomic, copy, nullable) NSString *accessibilityIdentifier;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -1604,22 +1604,6 @@ static NSInteger incrementIfFound(NSInteger i) {
|
||||
[self didExitHierarchy];
|
||||
}
|
||||
|
||||
// This case is important when tearing down hierarchies. We must deliver a visibilityDidChange:NO callback, as part our API guarantee that this method can be used for
|
||||
// things like data analytics about user content viewing. We cannot call the method in the dealloc as any incidental retain operations in client code would fail.
|
||||
// Additionally, it may be that a Standard UIView which is containing us is moving between hierarchies, and we should not send the call if we will be re-added in the
|
||||
// same runloop. Strategy: strong reference (might be the last!), wait one runloop, and confirm we are still outside the hierarchy (both layer-backed and view-backed).
|
||||
// TODO: This approach could be optimized by only performing the dispatch for root elements + recursively apply the interface state change. This would require a closer
|
||||
// integration with _ASDisplayLayer to ensure that the superlayer pointer has been cleared by this stage (to check if we are root or not), or a different delegate call.
|
||||
|
||||
if (ASInterfaceStateIncludesVisible(_interfaceState)) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
if (!_flags.isInHierarchy && ASInterfaceStateIncludesVisible(_interfaceState)) {
|
||||
self.interfaceState = (_interfaceState & ~ASInterfaceStateVisible);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_flags.isExitingHierarchy = NO;
|
||||
}
|
||||
}
|
||||
@@ -1990,6 +1974,23 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
|
||||
|
||||
if (![self supportsRangeManagedInterfaceState]) {
|
||||
self.interfaceState = ASInterfaceStateNone;
|
||||
} else {
|
||||
// This case is important when tearing down hierarchies. We must deliver a visibilityDidChange:NO callback, as part our API guarantee that this method can be used for
|
||||
// things like data analytics about user content viewing. We cannot call the method in the dealloc as any incidental retain operations in client code would fail.
|
||||
// Additionally, it may be that a Standard UIView which is containing us is moving between hierarchies, and we should not send the call if we will be re-added in the
|
||||
// same runloop. Strategy: strong reference (might be the last!), wait one runloop, and confirm we are still outside the hierarchy (both layer-backed and view-backed).
|
||||
// TODO: This approach could be optimized by only performing the dispatch for root elements + recursively apply the interface state change. This would require a closer
|
||||
// integration with _ASDisplayLayer to ensure that the superlayer pointer has been cleared by this stage (to check if we are root or not), or a different delegate call.
|
||||
|
||||
if (ASInterfaceStateIncludesVisible(_interfaceState)) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// This block intentionally retains self.
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
if (!_flags.isInHierarchy && ASInterfaceStateIncludesVisible(_interfaceState)) {
|
||||
self.interfaceState = (_interfaceState & ~ASInterfaceStateVisible);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,12 @@ extern ASDisplayNode *ASDisplayNodeUltimateParentOfNode(ASDisplayNode *node);
|
||||
*/
|
||||
extern void ASDisplayNodePerformBlockOnEveryNode(CALayer * _Nullable layer, ASDisplayNode * _Nullable node, void(^block)(ASDisplayNode *node));
|
||||
|
||||
/**
|
||||
This function will walk the node hierarchy in a breadth first fashion. It does run the block on the node provided
|
||||
directly to the function call.
|
||||
*/
|
||||
extern void ASDisplayNodePerformBlockOnEveryNodeBFS(ASDisplayNode *node, void(^block)(ASDisplayNode *node));
|
||||
|
||||
/**
|
||||
Identical to ASDisplayNodePerformBlockOnEveryNode, except it does not run the block on the
|
||||
node provided directly to the function call - only on all descendants.
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#import "ASDisplayNodeInternal.h"
|
||||
#import "ASDisplayNode+FrameworkPrivate.h"
|
||||
|
||||
#import <queue>
|
||||
|
||||
extern ASInterfaceState ASInterfaceStateForDisplayNode(ASDisplayNode *displayNode, UIWindow *window)
|
||||
{
|
||||
ASDisplayNodeCAssert(![displayNode isLayerBacked], @"displayNode must not be layer backed as it may have a nil window");
|
||||
@@ -63,6 +65,24 @@ extern void ASDisplayNodePerformBlockOnEveryNode(CALayer *layer, ASDisplayNode *
|
||||
}
|
||||
}
|
||||
|
||||
extern void ASDisplayNodePerformBlockOnEveryNodeBFS(ASDisplayNode *node, void(^block)(ASDisplayNode *node))
|
||||
{
|
||||
// Queue used to keep track of subnodes while traversing this layout in a BFS fashion.
|
||||
std::queue<ASDisplayNode *> queue;
|
||||
queue.push(node);
|
||||
|
||||
while (!queue.empty()) {
|
||||
node = queue.front();
|
||||
queue.pop();
|
||||
|
||||
block(node);
|
||||
|
||||
// Add all subnodes to process in next step
|
||||
for (int i = 0; i < node.subnodes.count; i++)
|
||||
queue.push(node.subnodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
extern void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, void(^block)(ASDisplayNode *node))
|
||||
{
|
||||
for (ASDisplayNode *subnode in node.subnodes) {
|
||||
|
||||
@@ -279,7 +279,7 @@
|
||||
#pragma mark - Layout
|
||||
- (void)setSnapshotSizeWithReloadIfNeeded:(CGSize)snapshotSize
|
||||
{
|
||||
if (!CGSizeEqualToSize(self.options.size, snapshotSize)) {
|
||||
if (snapshotSize.height > 0 && snapshotSize.width > 0 && !CGSizeEqualToSize(self.options.size, snapshotSize)) {
|
||||
_options.size = snapshotSize;
|
||||
if (_snapshotter) {
|
||||
[self destroySnapshotter];
|
||||
|
||||
@@ -88,9 +88,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
- (instancetype)_initWithTableView:(ASTableView *)tableView;
|
||||
@end
|
||||
|
||||
@interface ASTableView () <ASRangeControllerDataSource, ASRangeControllerDelegate,
|
||||
ASDataControllerSource, _ASTableViewCellDelegate,
|
||||
ASCellNodeLayoutDelegate, ASDelegateProxyInterceptor>
|
||||
@interface ASTableView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASDataControllerSource, _ASTableViewCellDelegate, ASCellNodeLayoutDelegate, ASDelegateProxyInterceptor, ASBatchFetchingScrollView>
|
||||
{
|
||||
ASTableViewProxy *_proxyDataSource;
|
||||
ASTableViewProxy *_proxyDelegate;
|
||||
@@ -524,8 +522,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Intercepted selectors
|
||||
|
||||
#pragma mark - Intercepted selectors
|
||||
|
||||
- (void)setTableHeaderView:(UIView *)tableHeaderView
|
||||
{
|
||||
@@ -579,47 +577,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
return [_dataController numberOfRowsInSection:section];
|
||||
}
|
||||
|
||||
- (ASScrollDirection)scrollDirection
|
||||
{
|
||||
CGPoint scrollVelocity;
|
||||
if (self.isTracking) {
|
||||
scrollVelocity = [self.panGestureRecognizer velocityInView:self.superview];
|
||||
} else {
|
||||
scrollVelocity = _deceleratingVelocity;
|
||||
}
|
||||
ASScrollDirection scrollDirection = [self _scrollDirectionForVelocity:scrollVelocity];
|
||||
return ASScrollDirectionApplyTransform(scrollDirection, self.transform);
|
||||
}
|
||||
|
||||
- (ASScrollDirection)_scrollDirectionForVelocity:(CGPoint)velocity
|
||||
{
|
||||
ASScrollDirection direction = ASScrollDirectionNone;
|
||||
if (velocity.y < 0.0) {
|
||||
direction = ASScrollDirectionDown;
|
||||
} else if (velocity.y > 0.0) {
|
||||
direction = ASScrollDirectionUp;
|
||||
}
|
||||
return direction;
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
{
|
||||
// If a scroll happenes the current range mode needs to go to full
|
||||
ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController];
|
||||
if (ASInterfaceStateIncludesVisible(interfaceState)) {
|
||||
[_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull];
|
||||
}
|
||||
|
||||
for (_ASTableViewCell *tableCell in _cellsForVisibilityUpdates) {
|
||||
[[tableCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged
|
||||
inScrollView:scrollView
|
||||
withCellFrame:tableCell.frame];
|
||||
}
|
||||
if (_asyncDelegateImplementsScrollviewDidScroll) {
|
||||
[_asyncDelegate scrollViewDidScroll:scrollView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView willDisplayCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
_pendingVisibleIndexPath = indexPath;
|
||||
@@ -631,7 +588,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
[_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
|
||||
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]];
|
||||
|
||||
if (cellNode.neverShowPlaceholders) {
|
||||
[cellNode recursivelyEnsureDisplaySynchronously:YES];
|
||||
@@ -650,7 +607,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
ASCellNode *cellNode = [cell node];
|
||||
|
||||
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
|
||||
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]];
|
||||
|
||||
if ([_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNode:forRowAtIndexPath:)]) {
|
||||
ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil.");
|
||||
@@ -672,8 +629,23 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Batch Fetching
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
{
|
||||
// If a scroll happenes the current range mode needs to go to full
|
||||
ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController];
|
||||
if (ASInterfaceStateIncludesVisible(interfaceState)) {
|
||||
[_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull];
|
||||
}
|
||||
|
||||
for (_ASTableViewCell *tableCell in _cellsForVisibilityUpdates) {
|
||||
[[tableCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged
|
||||
inScrollView:scrollView
|
||||
withCellFrame:tableCell.frame];
|
||||
}
|
||||
if (_asyncDelegateImplementsScrollviewDidScroll) {
|
||||
[_asyncDelegate scrollViewDidScroll:scrollView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
|
||||
{
|
||||
@@ -683,7 +655,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
);
|
||||
|
||||
if (targetContentOffset != NULL) {
|
||||
[self handleBatchFetchScrollingToOffset:*targetContentOffset];
|
||||
ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist");
|
||||
[self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset];
|
||||
}
|
||||
|
||||
if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
|
||||
@@ -691,7 +664,62 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)shouldBatchFetch
|
||||
|
||||
#pragma mark - Scroll Direction
|
||||
|
||||
- (ASScrollDirection)scrollDirection
|
||||
{
|
||||
CGPoint scrollVelocity;
|
||||
if (self.isTracking) {
|
||||
scrollVelocity = [self.panGestureRecognizer velocityInView:self.superview];
|
||||
} else {
|
||||
scrollVelocity = _deceleratingVelocity;
|
||||
}
|
||||
|
||||
ASScrollDirection scrollDirection = [self _scrollDirectionForVelocity:scrollVelocity];
|
||||
return ASScrollDirectionApplyTransform(scrollDirection, self.transform);
|
||||
}
|
||||
|
||||
- (ASScrollDirection)_scrollDirectionForVelocity:(CGPoint)scrollVelocity
|
||||
{
|
||||
ASScrollDirection direction = ASScrollDirectionNone;
|
||||
ASScrollDirection scrollableDirections = [self scrollableDirections];
|
||||
|
||||
if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { // Can scroll vertically.
|
||||
if (scrollVelocity.y < 0.0) {
|
||||
direction |= ASScrollDirectionDown;
|
||||
} else if (scrollVelocity.y > 0.0) {
|
||||
direction |= ASScrollDirectionUp;
|
||||
}
|
||||
}
|
||||
|
||||
return direction;
|
||||
}
|
||||
|
||||
- (ASScrollDirection)scrollableDirections
|
||||
{
|
||||
ASScrollDirection scrollableDirection = ASScrollDirectionNone;
|
||||
CGFloat totalContentWidth = self.contentSize.width + self.contentInset.left + self.contentInset.right;
|
||||
CGFloat totalContentHeight = self.contentSize.height + self.contentInset.top + self.contentInset.bottom;
|
||||
|
||||
if (self.alwaysBounceHorizontal || totalContentWidth > self.bounds.size.width) { // Can scroll horizontally.
|
||||
scrollableDirection |= ASScrollDirectionHorizontalDirections;
|
||||
}
|
||||
if (self.alwaysBounceVertical || totalContentHeight > self.bounds.size.height) { // Can scroll vertically.
|
||||
scrollableDirection |= ASScrollDirectionVerticalDirections;
|
||||
}
|
||||
return scrollableDirection;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Batch Fetching
|
||||
|
||||
- (ASBatchContext *)batchContext
|
||||
{
|
||||
return _batchContext;
|
||||
}
|
||||
|
||||
- (BOOL)canBatchFetch
|
||||
{
|
||||
// if the delegate does not respond to this method, there is no point in starting to fetch
|
||||
BOOL canFetch = [_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)];
|
||||
@@ -702,16 +730,35 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleBatchFetchScrollingToOffset:(CGPoint)targetOffset
|
||||
- (void)_scheduleCheckForBatchFetching
|
||||
{
|
||||
ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist");
|
||||
// Push this to the next runloop to be sure the scroll view has the right content size
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self _checkForBatchFetching];
|
||||
});
|
||||
}
|
||||
|
||||
if (![self shouldBatchFetch]) {
|
||||
- (void)_checkForBatchFetching
|
||||
{
|
||||
// Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset:
|
||||
if (self.isDragging || self.isTracking) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollableDirections] contentOffset:self.contentOffset];
|
||||
}
|
||||
|
||||
if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollDirection], self.bounds, self.contentSize, targetOffset, _leadingScreensForBatching)) {
|
||||
[_batchContext beginBatchFetching];
|
||||
- (void)_beginBatchFetchingIfNeededWithScrollView:(UIScrollView<ASBatchFetchingScrollView> *)scrollView forScrollDirection:(ASScrollDirection)scrollDirection contentOffset:(CGPoint)contentOffset
|
||||
{
|
||||
if (ASDisplayShouldFetchBatchForScrollView(self, scrollDirection, contentOffset)) {
|
||||
[self _beginBatchFetching];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_beginBatchFetching
|
||||
{
|
||||
[_batchContext beginBatchFetching];
|
||||
if ([_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext];
|
||||
});
|
||||
@@ -720,6 +767,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
#pragma mark - ASRangeControllerDataSource
|
||||
|
||||
- (ASRangeController *)rangeController
|
||||
{
|
||||
return _rangeController;
|
||||
}
|
||||
|
||||
- (NSArray *)visibleNodeIndexPathsForRangeController:(ASRangeController *)rangeController
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
@@ -852,6 +904,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone;
|
||||
ASPerformBlockWithoutAnimation(preventAnimation, ^{
|
||||
[super insertRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions];
|
||||
[self _scheduleCheckForBatchFetching];
|
||||
});
|
||||
|
||||
if (_automaticallyAdjustsContentOffset) {
|
||||
@@ -871,6 +924,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone;
|
||||
ASPerformBlockWithoutAnimation(preventAnimation, ^{
|
||||
[super deleteRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions];
|
||||
[self _scheduleCheckForBatchFetching];
|
||||
});
|
||||
|
||||
if (_automaticallyAdjustsContentOffset) {
|
||||
@@ -891,6 +945,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone;
|
||||
ASPerformBlockWithoutAnimation(preventAnimation, ^{
|
||||
[super insertSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions];
|
||||
[self _scheduleCheckForBatchFetching];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -906,6 +961,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone;
|
||||
ASPerformBlockWithoutAnimation(preventAnimation, ^{
|
||||
[super deleteSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions];
|
||||
[self _scheduleCheckForBatchFetching];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1072,7 +1128,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
// Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their
|
||||
// their update in the layout pass
|
||||
if (![node supportsRangeManagedInterfaceState]) {
|
||||
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection];
|
||||
[_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:[self scrollDirection]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -334,7 +334,8 @@
|
||||
- (void)setMuted:(BOOL)muted
|
||||
{
|
||||
ASDN::MutexLocker l(_videoLock);
|
||||
|
||||
|
||||
_player.muted = muted;
|
||||
_muted = muted;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,12 @@
|
||||
@interface ASControlNode (Debugging)
|
||||
|
||||
/**
|
||||
Class method to enable a visualization overlay of the tapable area on the ASControlNode. For app debugging purposes only.
|
||||
Class method to enable a visualization overlay of the tappable area on the ASControlNode. For app debugging purposes only.
|
||||
NOTE: GESTURE RECOGNIZERS, (including tap gesture recognizers on a control node) WILL NOT BE VISUALIZED!!!
|
||||
Overlay = translucent GREEN color,
|
||||
edges that are clipped by the tappable area of any parent (their bounds + hitTestSlop) in the hierarchy = DARK GREEN BORDERED EDGE,
|
||||
edges that are clipped by clipToBounds = YES of any parent in the hierarchy = ORANGE BORDERED EDGE (may still receive touches beyond
|
||||
overlay rect, but can't be visualized).
|
||||
@param enable Specify YES to make this debug feature enabled when messaging the ASControlNode class.
|
||||
*/
|
||||
+ (void)setEnableHitTestDebug:(BOOL)enable;
|
||||
|
||||
@@ -45,6 +45,12 @@ typedef NS_ENUM(NSInteger, ASBatchContextState) {
|
||||
return _state == ASBatchContextStateCancelled;
|
||||
}
|
||||
|
||||
- (void)beginBatchFetching
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
_state = ASBatchContextStateFetching;
|
||||
}
|
||||
|
||||
- (void)completeBatchFetching:(BOOL)didComplete
|
||||
{
|
||||
if (didComplete) {
|
||||
@@ -53,12 +59,6 @@ typedef NS_ENUM(NSInteger, ASBatchContextState) {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)beginBatchFetching
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
_state = ASBatchContextStateFetching;
|
||||
}
|
||||
|
||||
- (void)cancelBatchFetching
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
|
||||
@@ -132,7 +132,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
NSAssert(ASDisplayNodeThreadIsMain(), @"Layout of loaded nodes must happen on the main thread.");
|
||||
ASDisplayNodeAssertTrue(nodes.count == contexts.count);
|
||||
|
||||
[self _layoutNodes:nodes fromContexts:contexts inIndexesOfRange:NSMakeRange(0, nodes.count) ofKind:kind];
|
||||
[self _layoutNodes:nodes fromContexts:contexts atIndexesOfRange:NSMakeRange(0, nodes.count) ofKind:kind];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,7 +158,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
/**
|
||||
* Perform measurement and layout of loaded or unloaded nodes based if they will be layed out on main thread or not
|
||||
*/
|
||||
- (void)_layoutNodes:(NSArray<ASCellNode *> *)nodes fromContexts:(NSArray<ASIndexedNodeContext *> *)contexts inIndexesOfRange:(NSRange)range ofKind:(NSString *)kind
|
||||
- (void)_layoutNodes:(NSArray<ASCellNode *> *)nodes fromContexts:(NSArray<ASIndexedNodeContext *> *)contexts atIndexesOfRange:(NSRange)range ofKind:(NSString *)kind
|
||||
{
|
||||
if (_dataSource == nil) {
|
||||
return;
|
||||
@@ -198,7 +198,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
dispatch_apply(batchCount, queue, ^(size_t i) {
|
||||
unsigned long k = j + i;
|
||||
ASCellNode *node = [contexts[k] allocateNode];
|
||||
ASDisplayNodeAssertNotNil(node, @"Node block created nil node");
|
||||
if (node == nil) {
|
||||
ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, self.dataSource);
|
||||
node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps.
|
||||
}
|
||||
allocatedNodeBuffer[i] = node;
|
||||
});
|
||||
subarray = [[NSArray alloc] initWithObjects:allocatedNodeBuffer count:batchCount];
|
||||
@@ -209,7 +212,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
}
|
||||
free(allocatedNodeBuffer);
|
||||
};
|
||||
|
||||
|
||||
// Run the allocation block to concurrently create the cell nodes. Then, handle layout for nodes that are already loaded
|
||||
// (e.g. the dataSource may have provided cells that have been used before), which must do layout on the main thread.
|
||||
NSRange batchRange = NSMakeRange(0, batchCount);
|
||||
if (ASDisplayNodeThreadIsMain()) {
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
@@ -218,29 +224,20 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
});
|
||||
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
|
||||
|
||||
[self _layoutNodes:subarray
|
||||
fromContexts:contexts
|
||||
inIndexesOfRange:NSMakeRange(0, batchCount)
|
||||
ofKind:kind];
|
||||
|
||||
[self _layoutNodes:subarray fromContexts:contexts atIndexesOfRange:batchRange ofKind:kind];
|
||||
} else {
|
||||
allocationBlock();
|
||||
[_mainSerialQueue performBlockOnMainThread:^{
|
||||
[self _layoutNodes:subarray
|
||||
fromContexts:contexts
|
||||
inIndexesOfRange:NSMakeRange(0, batchCount)
|
||||
ofKind:kind];
|
||||
[self _layoutNodes:subarray fromContexts:contexts atIndexesOfRange:batchRange ofKind:kind];
|
||||
}];
|
||||
}
|
||||
|
||||
[allocatedNodes addObjectsFromArray:subarray];
|
||||
|
||||
dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
// We should already have measured loaded nodes before we left the main thread. Layout the remaining once on a background thread.
|
||||
[self _layoutNodes:allocatedNodes
|
||||
fromContexts:contexts
|
||||
inIndexesOfRange:NSMakeRange(j, batchCount)
|
||||
ofKind:kind];
|
||||
// We should already have measured loaded nodes before we left the main thread. Layout the remaining ones on a background thread.
|
||||
NSRange asyncBatchRange = NSMakeRange(j, batchCount);
|
||||
[self _layoutNodes:allocatedNodes fromContexts:contexts atIndexesOfRange:asyncBatchRange ofKind:kind];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ typedef struct ASEnvironmentLayoutOptionsState {
|
||||
ASRelativeSizeRange sizeRange;// = ASRelativeSizeRangeMake(ASRelativeSizeMakeWithCGSize(CGSizeZero), ASRelativeSizeMakeWithCGSize(CGSizeZero));;
|
||||
CGPoint layoutPosition;// = CGPointZero;
|
||||
|
||||
ASEnvironmentStateExtensions _extensions;
|
||||
struct ASEnvironmentStateExtensions _extensions;
|
||||
} ASEnvironmentLayoutOptionsState;
|
||||
|
||||
|
||||
|
||||
@@ -8,12 +8,7 @@
|
||||
|
||||
#import "_ASDisplayView.h"
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import "_ASCoreAnimationExtras.h"
|
||||
#import "_ASAsyncTransactionContainer.h"
|
||||
#import "ASAssert.h"
|
||||
#import "ASDisplayNodeExtras.h"
|
||||
#import "ASDisplayNodeInternal.h"
|
||||
#import "ASDisplayNode+FrameworkPrivate.h"
|
||||
#import "ASDisplayNode+Subclasses.h"
|
||||
@@ -31,6 +26,7 @@
|
||||
__unsafe_unretained ASDisplayNode *_node; // Though UIView has a .node property added via category, since we can add an ivar to a subclass, use that for performance.
|
||||
BOOL _inHitTest;
|
||||
BOOL _inPointInside;
|
||||
NSMutableArray *_accessibleElements;
|
||||
}
|
||||
|
||||
@synthesize asyncdisplaykit_node = _node;
|
||||
|
||||
9
AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h
Normal file
9
AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h
Normal file
@@ -0,0 +1,9 @@
|
||||
/* Copyright (c) 2014-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 <Foundation/Foundation.h>
|
||||
122
AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm
Normal file
122
AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm
Normal file
@@ -0,0 +1,122 @@
|
||||
/* Copyright (c) 2014-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 "_ASDisplayViewAccessiblity.h"
|
||||
#import "_ASDisplayView.h"
|
||||
#import "ASDisplayNodeExtras.h"
|
||||
#import "ASDisplayNode+FrameworkPrivate.h"
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
|
||||
#pragma mark - UIAccessibilityElement
|
||||
|
||||
@implementation UIAccessibilityElement (_ASDisplayView)
|
||||
|
||||
+ (UIAccessibilityElement *)accessibilityElementWithContainer:(id)container node:(ASDisplayNode *)node
|
||||
{
|
||||
UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:container];
|
||||
accessibilityElement.accessibilityIdentifier = node.accessibilityIdentifier;
|
||||
accessibilityElement.accessibilityLabel = node.accessibilityLabel;
|
||||
accessibilityElement.accessibilityHint = node.accessibilityHint;
|
||||
accessibilityElement.accessibilityValue = node.accessibilityValue;
|
||||
accessibilityElement.accessibilityTraits = node.accessibilityTraits;
|
||||
return accessibilityElement;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark - _ASDisplayView / UIAccessibilityContainer
|
||||
|
||||
@interface _ASDisplayView () {
|
||||
NSMutableArray *_accessibleElements;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _ASDisplayView (UIAccessibilityContainer)
|
||||
|
||||
#pragma mark - UIAccessibility
|
||||
|
||||
- (NSArray *)accessibleElements
|
||||
{
|
||||
ASDisplayNode *selfNode = self.asyncdisplaykit_node;
|
||||
if (selfNode == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_accessibleElements = [[NSMutableArray alloc] init];
|
||||
|
||||
// Handle rasterize case
|
||||
if (selfNode.shouldRasterizeDescendants) {
|
||||
// If the node has shouldRasterizeDescendants enabled it's necessaty to go through the whole subnodes
|
||||
// tree of the node in BFS fashion and create for all subnodes UIAccessibilityElement objects ourselves
|
||||
// as the view hierarchy is flattened
|
||||
ASDisplayNodePerformBlockOnEveryNodeBFS(selfNode, ^(ASDisplayNode * _Nonnull node) {
|
||||
// For every subnode we have to create a UIAccessibilityElement as we cannot just add the view to the
|
||||
// accessibleElements as for a subnode of a node with shouldRasterizeDescendants enabled no view exists
|
||||
if (node != selfNode && node.isAccessibilityElement) {
|
||||
UIAccessibilityElement *accessibilityElement = [UIAccessibilityElement accessibilityElementWithContainer:self node:node];
|
||||
// As the node hierarchy is flattened it's necessary to convert the frame for each subnode in the tree to the
|
||||
// coordinate system of the node with shouldRasterizeDescendants enabled
|
||||
CGRect frame = [selfNode convertRect:node.bounds fromNode:node];
|
||||
accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(frame, self);
|
||||
[_accessibleElements addObject:accessibilityElement];
|
||||
}
|
||||
});
|
||||
return _accessibleElements;
|
||||
}
|
||||
|
||||
// Handle not rasterize case
|
||||
// Create UI accessiblity elements for each subnode that represent an elment within the accessibility container
|
||||
for (ASDisplayNode *subnode in selfNode.subnodes) {
|
||||
if (subnode.isAccessibilityElement) {
|
||||
id accessiblityElement = nil;
|
||||
if (subnode.isLayerBacked) {
|
||||
// The same comment for layer backed nodes is true as for subnodes within a shouldRasterizeDescendants node.
|
||||
// See details above
|
||||
accessiblityElement = [UIAccessibilityElement accessibilityElementWithContainer:self node:subnode];
|
||||
} else {
|
||||
accessiblityElement = subnode.view;
|
||||
}
|
||||
[accessiblityElement setAccessibilityFrame:UIAccessibilityConvertFrameToScreenCoordinates(subnode.frame, self)];
|
||||
[_accessibleElements addObject:accessiblityElement];
|
||||
} else if ([subnode accessibilityElementCount] > 0) { // Check if it's an UIAccessibilityContainer
|
||||
[_accessibleElements addObject:subnode.view];
|
||||
}
|
||||
}
|
||||
|
||||
return _accessibleElements;
|
||||
}
|
||||
|
||||
- (NSInteger)accessibilityElementCount
|
||||
{
|
||||
return [self accessibleElements].count;
|
||||
}
|
||||
|
||||
- (id)accessibilityElementAtIndex:(NSInteger)index
|
||||
{
|
||||
ASDisplayNodeAssertNotNil(_accessibleElements, @"At this point _accessibleElements should be created.");
|
||||
if (_accessibleElements == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return _accessibleElements[index];
|
||||
}
|
||||
|
||||
- (NSInteger)indexOfAccessibilityElement:(id)element
|
||||
{
|
||||
if (_accessibleElements == nil) {
|
||||
return NSNotFound;
|
||||
}
|
||||
|
||||
return [_accessibleElements indexOfObject:element];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -88,6 +88,12 @@ static CGFloat centerInset(CGFloat outer, CGFloat inner)
|
||||
MAX(0, constrainedSize.max.height - insetsY),
|
||||
}
|
||||
};
|
||||
|
||||
if (self.child == nil) {
|
||||
ASDisplayNodeAssert(NO, @"Inset spec measured without a child. The spec will do nothing.");
|
||||
return [ASLayout layoutWithLayoutableObject:self size:CGSizeZero];
|
||||
}
|
||||
|
||||
ASLayout *sublayout = [self.child measureWithSizeRange:insetConstrainedSize];
|
||||
|
||||
const CGSize computedSize = ASSizeRangeClamp(constrainedSize, {
|
||||
|
||||
@@ -10,15 +10,15 @@
|
||||
|
||||
@protocol ASLayoutableExtensibility <NSObject>
|
||||
|
||||
/// Currently up to 4 BOOL values
|
||||
// The maximum number of extended values per type are defined in ASEnvironment.h above the ASEnvironmentStateExtensions
|
||||
// struct definition. If you try to set a value at an index after the maximum it will throw an assertion.
|
||||
|
||||
- (void)setLayoutOptionExtensionBool:(BOOL)value atIndex:(int)idx;
|
||||
- (BOOL)layoutOptionExtensionBoolAtIndex:(int)idx;
|
||||
|
||||
/// Currently up to 1 NSInteger value
|
||||
- (void)setLayoutOptionExtensionInteger:(NSInteger)value atIndex:(int)idx;
|
||||
- (NSInteger)layoutOptionExtensionIntegerAtIndex:(int)idx;
|
||||
|
||||
/// Currently up to 1 UIEdgeInsets value
|
||||
- (void)setLayoutOptionExtensionEdgeInsets:(UIEdgeInsets)value atIndex:(int)idx;
|
||||
- (UIEdgeInsets)layoutOptionExtensionEdgeInsetsAtIndex:(int)idx;
|
||||
|
||||
|
||||
@@ -10,10 +10,29 @@
|
||||
|
||||
#import "ASBatchContext.h"
|
||||
#import "ASScrollDirection.h"
|
||||
#import "ASBaseDefines.h"
|
||||
|
||||
ASDISPLAYNODE_EXTERN_C_BEGIN
|
||||
|
||||
@protocol ASBatchFetchingScrollView <NSObject>
|
||||
|
||||
- (BOOL)canBatchFetch;
|
||||
- (ASBatchContext *)batchContext;
|
||||
- (CGFloat)leadingScreensForBatching;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
@abstract Determine if batch fetching should begin based on the state of the parameters.
|
||||
@discussion This method is broken into a category for unit testing purposes and should be used with the ASTableView and
|
||||
* ASCollectionView batch fetching API.
|
||||
@param context The scroll view that in-flight fetches are happening.
|
||||
@param scrollDirection The current scrolling direction of the scroll view.
|
||||
@param targetOffset The offset that the scrollview will scroll to.
|
||||
@return Whether or not the current state should proceed with batch fetching.
|
||||
*/
|
||||
BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView<ASBatchFetchingScrollView> *scrollView, ASScrollDirection scrollDirection, CGPoint contentOffset);
|
||||
|
||||
|
||||
/**
|
||||
@abstract Determine if batch fetching should begin based on the state of the parameters.
|
||||
@param context The batch fetching context that contains knowledge about in-flight fetches.
|
||||
|
||||
@@ -8,34 +8,50 @@
|
||||
|
||||
#import "ASBatchFetching.h"
|
||||
|
||||
BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView<ASBatchFetchingScrollView> *scrollView, ASScrollDirection scrollDirection, CGPoint contentOffset)
|
||||
{
|
||||
// Don't fetch if the scroll view does not allow
|
||||
if (![scrollView canBatchFetch]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Check if we should batch fetch
|
||||
ASBatchContext *context = scrollView.batchContext;
|
||||
CGRect bounds = scrollView.bounds;
|
||||
CGSize contentSize = scrollView.contentSize;
|
||||
CGFloat leadingScreens = scrollView.leadingScreensForBatching;
|
||||
return ASDisplayShouldFetchBatchForContext(context, scrollDirection, bounds, contentSize, contentOffset, leadingScreens);
|
||||
}
|
||||
|
||||
BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context,
|
||||
ASScrollDirection scrollDirection,
|
||||
CGRect bounds,
|
||||
CGSize contentSize,
|
||||
CGPoint targetOffset,
|
||||
CGFloat leadingScreens) {
|
||||
// do not allow fetching if a batch is already in-flight and hasn't been completed or cancelled
|
||||
ASScrollDirection scrollDirection,
|
||||
CGRect bounds,
|
||||
CGSize contentSize,
|
||||
CGPoint targetOffset,
|
||||
CGFloat leadingScreens)
|
||||
{
|
||||
// Do not allow fetching if a batch is already in-flight and hasn't been completed or cancelled
|
||||
if ([context isFetching]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// only Down and Right scrolls are currently supported (tail loading)
|
||||
if (scrollDirection != ASScrollDirectionDown && scrollDirection != ASScrollDirectionRight) {
|
||||
// Only Down and Right scrolls are currently supported (tail loading)
|
||||
if (!ASScrollDirectionContainsDown(scrollDirection) && !ASScrollDirectionContainsRight(scrollDirection)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// no fetching for null states
|
||||
// No fetching for null states
|
||||
if (leadingScreens <= 0.0 || CGRectEqualToRect(bounds, CGRectZero)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
CGFloat viewLength, offset, contentLength;
|
||||
|
||||
if (scrollDirection == ASScrollDirectionDown) {
|
||||
if (ASScrollDirectionContainsDown(scrollDirection)) {
|
||||
viewLength = bounds.size.height;
|
||||
offset = targetOffset.y;
|
||||
contentLength = contentSize.height;
|
||||
} else { // horizontal
|
||||
} else { // horizontal / right
|
||||
viewLength = bounds.size.width;
|
||||
offset = targetOffset.x;
|
||||
contentLength = contentSize.width;
|
||||
|
||||
@@ -709,141 +709,223 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo
|
||||
_setToLayer(edgeAntialiasingMask, edgeAntialiasingMask);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark - UIViewBridgeAccessibility
|
||||
|
||||
// ASDK supports accessibility for view or layer backed nodes. To be able to provide support for layer backed
|
||||
// nodes, properties for all of the UIAccessibility protocol defined properties need to be provided an held in sync
|
||||
// between node and view
|
||||
|
||||
// Helper function with following logic:
|
||||
// - If the node is not loaded yet use the property from the pending state
|
||||
// - In case the node is loaded
|
||||
// - Check if the node has a view and get the
|
||||
// - If view is not available, e.g. the node is layer backed return the property value
|
||||
#define _getAccessibilityFromViewOrProperty(nodeProperty, viewAndPendingViewStateProperty) __loaded(self) ? \
|
||||
(_view ? _view.viewAndPendingViewStateProperty : nodeProperty )\
|
||||
: ASDisplayNodeGetPendingState(self).viewAndPendingViewStateProperty
|
||||
|
||||
// Helper function to set property values on pending state or view and property if loaded
|
||||
#define _setAccessibilityToViewAndProperty(nodeProperty, nodeValueExpr, viewAndPendingViewStateProperty, viewAndPendingViewStateExpr) \
|
||||
nodeProperty = nodeValueExpr; _setToViewOnly(viewAndPendingViewStateProperty, viewAndPendingViewStateExpr)
|
||||
|
||||
@implementation ASDisplayNode (UIViewBridgeAccessibility)
|
||||
|
||||
- (BOOL)isAccessibilityElement
|
||||
{
|
||||
_bridge_prologue_read;
|
||||
return _getFromViewOnly(isAccessibilityElement);
|
||||
return _getAccessibilityFromViewOrProperty(_isAccessibilityElement, isAccessibilityElement);
|
||||
}
|
||||
|
||||
- (void)setIsAccessibilityElement:(BOOL)isAccessibilityElement
|
||||
{
|
||||
_bridge_prologue_write;
|
||||
_setToViewOnly(isAccessibilityElement, isAccessibilityElement);
|
||||
_setAccessibilityToViewAndProperty(_isAccessibilityElement, isAccessibilityElement, isAccessibilityElement, isAccessibilityElement);
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityLabel
|
||||
{
|
||||
_bridge_prologue_read;
|
||||
return _getFromViewOnly(accessibilityLabel);
|
||||
return _getAccessibilityFromViewOrProperty(_accessibilityLabel, accessibilityLabel);
|
||||
}
|
||||
|
||||
- (void)setAccessibilityLabel:(NSString *)accessibilityLabel
|
||||
{
|
||||
_bridge_prologue_write;
|
||||
_setToViewOnly(accessibilityLabel, accessibilityLabel);
|
||||
_setAccessibilityToViewAndProperty(_accessibilityLabel, accessibilityLabel, accessibilityLabel, accessibilityLabel);
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityHint
|
||||
{
|
||||
_bridge_prologue_read;
|
||||
return _getFromViewOnly(accessibilityHint);
|
||||
return _getAccessibilityFromViewOrProperty(_accessibilityHint, accessibilityHint);
|
||||
}
|
||||
|
||||
- (void)setAccessibilityHint:(NSString *)accessibilityHint
|
||||
{
|
||||
_bridge_prologue_write;
|
||||
_setToViewOnly(accessibilityHint, accessibilityHint);
|
||||
_setAccessibilityToViewAndProperty(_accessibilityHint, accessibilityHint, accessibilityHint, accessibilityHint);
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityValue
|
||||
{
|
||||
_bridge_prologue_read;
|
||||
return _getFromViewOnly(accessibilityValue);
|
||||
return _getAccessibilityFromViewOrProperty(_accessibilityValue, accessibilityValue);
|
||||
}
|
||||
|
||||
- (void)setAccessibilityValue:(NSString *)accessibilityValue
|
||||
{
|
||||
_bridge_prologue_write;
|
||||
_setToViewOnly(accessibilityValue, accessibilityValue);
|
||||
_setAccessibilityToViewAndProperty(_accessibilityValue, accessibilityValue, accessibilityValue, accessibilityValue);
|
||||
}
|
||||
|
||||
- (UIAccessibilityTraits)accessibilityTraits
|
||||
{
|
||||
_bridge_prologue_read;
|
||||
return _getFromViewOnly(accessibilityTraits);
|
||||
return _getAccessibilityFromViewOrProperty(_accessibilityTraits, accessibilityTraits);
|
||||
}
|
||||
|
||||
- (void)setAccessibilityTraits:(UIAccessibilityTraits)accessibilityTraits
|
||||
{
|
||||
_bridge_prologue_write;
|
||||
_setToViewOnly(accessibilityTraits, accessibilityTraits);
|
||||
_setAccessibilityToViewAndProperty(_accessibilityTraits, accessibilityTraits, accessibilityTraits, accessibilityTraits);
|
||||
}
|
||||
|
||||
- (CGRect)accessibilityFrame
|
||||
{
|
||||
_bridge_prologue_read;
|
||||
return _getFromViewOnly(accessibilityFrame);
|
||||
return _getAccessibilityFromViewOrProperty(_accessibilityFrame, accessibilityFrame);
|
||||
}
|
||||
|
||||
- (void)setAccessibilityFrame:(CGRect)accessibilityFrame
|
||||
{
|
||||
_bridge_prologue_write;
|
||||
_setToViewOnly(accessibilityFrame, accessibilityFrame);
|
||||
_setAccessibilityToViewAndProperty(_accessibilityFrame, accessibilityFrame, accessibilityFrame, accessibilityFrame);
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityLanguage
|
||||
{
|
||||
_bridge_prologue_read;
|
||||
return _getFromViewOnly(accessibilityLanguage);
|
||||
return _getAccessibilityFromViewOrProperty(_accessibilityLanguage, accessibilityLanguage);
|
||||
}
|
||||
|
||||
- (void)setAccessibilityLanguage:(NSString *)accessibilityLanguage
|
||||
{
|
||||
_bridge_prologue_write;
|
||||
_setToViewOnly(accessibilityLanguage, accessibilityLanguage);
|
||||
_setAccessibilityToViewAndProperty(_accessibilityLanguage, accessibilityLanguage, accessibilityLanguage, accessibilityLanguage);
|
||||
}
|
||||
|
||||
- (BOOL)accessibilityElementsHidden
|
||||
{
|
||||
_bridge_prologue_read;
|
||||
return _getFromViewOnly(accessibilityElementsHidden);
|
||||
return _getAccessibilityFromViewOrProperty(_accessibilityElementsHidden, accessibilityElementsHidden);
|
||||
}
|
||||
|
||||
- (void)setAccessibilityElementsHidden:(BOOL)accessibilityElementsHidden
|
||||
{
|
||||
_bridge_prologue_write;
|
||||
_setToViewOnly(accessibilityElementsHidden, accessibilityElementsHidden);
|
||||
_setAccessibilityToViewAndProperty(_accessibilityElementsHidden, accessibilityElementsHidden, accessibilityElementsHidden, accessibilityElementsHidden);
|
||||
}
|
||||
|
||||
- (BOOL)accessibilityViewIsModal
|
||||
{
|
||||
_bridge_prologue_read;
|
||||
return _getFromViewOnly(accessibilityViewIsModal);
|
||||
return _getAccessibilityFromViewOrProperty(_accessibilityViewIsModal, accessibilityViewIsModal);
|
||||
}
|
||||
|
||||
- (void)setAccessibilityViewIsModal:(BOOL)accessibilityViewIsModal
|
||||
{
|
||||
_bridge_prologue_write;
|
||||
_setToViewOnly(accessibilityViewIsModal, accessibilityViewIsModal);
|
||||
_setAccessibilityToViewAndProperty(_accessibilityViewIsModal, accessibilityViewIsModal, accessibilityViewIsModal, accessibilityViewIsModal);
|
||||
}
|
||||
|
||||
- (BOOL)shouldGroupAccessibilityChildren
|
||||
{
|
||||
_bridge_prologue_read;
|
||||
return _getFromViewOnly(shouldGroupAccessibilityChildren);
|
||||
return _getAccessibilityFromViewOrProperty(_shouldGroupAccessibilityChildren, shouldGroupAccessibilityChildren);
|
||||
}
|
||||
|
||||
- (void)setShouldGroupAccessibilityChildren:(BOOL)shouldGroupAccessibilityChildren
|
||||
{
|
||||
_bridge_prologue_write;
|
||||
_setToViewOnly(shouldGroupAccessibilityChildren, shouldGroupAccessibilityChildren);
|
||||
_setAccessibilityToViewAndProperty(_shouldGroupAccessibilityChildren, shouldGroupAccessibilityChildren, shouldGroupAccessibilityChildren, shouldGroupAccessibilityChildren);
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityIdentifier
|
||||
{
|
||||
_bridge_prologue_read;
|
||||
return _getFromViewOnly(accessibilityIdentifier);
|
||||
return _getAccessibilityFromViewOrProperty(_accessibilityIdentifier, accessibilityIdentifier);
|
||||
}
|
||||
|
||||
- (void)setAccessibilityIdentifier:(NSString *)accessibilityIdentifier
|
||||
{
|
||||
_bridge_prologue_write;
|
||||
_setToViewOnly(accessibilityIdentifier, accessibilityIdentifier);
|
||||
_setAccessibilityToViewAndProperty(_accessibilityIdentifier, accessibilityIdentifier, accessibilityIdentifier, accessibilityIdentifier);
|
||||
}
|
||||
|
||||
- (void)setAccessibilityNavigationStyle:(UIAccessibilityNavigationStyle)accessibilityNavigationStyle
|
||||
{
|
||||
_bridge_prologue_write;
|
||||
_setAccessibilityToViewAndProperty(_accessibilityNavigationStyle, accessibilityNavigationStyle, accessibilityNavigationStyle, accessibilityNavigationStyle);
|
||||
}
|
||||
|
||||
- (UIAccessibilityNavigationStyle)accessibilityNavigationStyle
|
||||
{
|
||||
_bridge_prologue_read;
|
||||
return _getAccessibilityFromViewOrProperty(_accessibilityNavigationStyle, accessibilityNavigationStyle);
|
||||
}
|
||||
|
||||
#if TARGET_OS_TV
|
||||
- (void)setAccessibilityHeaderElements:(NSArray *)accessibilityHeaderElements
|
||||
{
|
||||
_bridge_prologue_write;
|
||||
_setAccessibilityToViewAndProperty(_accessibilityHeaderElements, accessibilityHeaderElements, accessibilityHeaderElements, accessibilityHeaderElements);
|
||||
}
|
||||
|
||||
- (NSArray *)accessibilityHeaderElements
|
||||
{
|
||||
_bridge_prologue_read;
|
||||
return _getAccessibilityFromViewOrProperty(_accessibilityHeaderElements, accessibilityHeaderElements);
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)setAccessibilityActivationPoint:(CGPoint)accessibilityActivationPoint
|
||||
{
|
||||
_bridge_prologue_write;
|
||||
_setAccessibilityToViewAndProperty(_accessibilityActivationPoint, accessibilityActivationPoint, accessibilityActivationPoint, accessibilityActivationPoint);
|
||||
}
|
||||
|
||||
- (CGPoint)accessibilityActivationPoint
|
||||
{
|
||||
_bridge_prologue_read;
|
||||
return _getAccessibilityFromViewOrProperty(_accessibilityActivationPoint, accessibilityActivationPoint);
|
||||
}
|
||||
|
||||
- (void)setAccessibilityPath:(UIBezierPath *)accessibilityPath
|
||||
{
|
||||
_bridge_prologue_write;
|
||||
_setAccessibilityToViewAndProperty(_accessibilityPath, accessibilityPath, accessibilityPath, accessibilityPath);
|
||||
}
|
||||
|
||||
- (UIBezierPath *)accessibilityPath
|
||||
{
|
||||
_bridge_prologue_read;
|
||||
return _getAccessibilityFromViewOrProperty(_accessibilityPath, accessibilityPath);
|
||||
}
|
||||
|
||||
- (NSInteger)accessibilityElementCount
|
||||
{
|
||||
_bridge_prologue_read;
|
||||
return _getFromViewOnly(accessibilityElementCount);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark - ASAsyncTransactionContainer
|
||||
|
||||
@implementation ASDisplayNode (ASAsyncTransactionContainer)
|
||||
|
||||
- (BOOL)asyncdisplaykit_isAsyncTransactionContainer
|
||||
|
||||
@@ -129,6 +129,23 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
|
||||
ASDisplayNodeContextModifier _willDisplayNodeContentWithRenderingContext;
|
||||
ASDisplayNodeContextModifier _didDisplayNodeContentWithRenderingContext;
|
||||
|
||||
// Accessibility support
|
||||
BOOL _isAccessibilityElement;
|
||||
NSString *_accessibilityLabel;
|
||||
NSString *_accessibilityHint;
|
||||
NSString *_accessibilityValue;
|
||||
UIAccessibilityTraits _accessibilityTraits;
|
||||
CGRect _accessibilityFrame;
|
||||
NSString *_accessibilityLanguage;
|
||||
BOOL _accessibilityElementsHidden;
|
||||
BOOL _accessibilityViewIsModal;
|
||||
BOOL _shouldGroupAccessibilityChildren;
|
||||
NSString *_accessibilityIdentifier;
|
||||
UIAccessibilityNavigationStyle _accessibilityNavigationStyle;
|
||||
NSArray *_accessibilityHeaderElements;
|
||||
CGPoint _accessibilityActivationPoint;
|
||||
UIBezierPath *_accessibilityPath;
|
||||
|
||||
#if TIME_DISPLAYNODE_OPS
|
||||
@public
|
||||
NSTimeInterval _debugTimeToCreateView;
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
BOOL _calculatedSubnodeOperations;
|
||||
NSArray<ASDisplayNode *> *_insertedSubnodes;
|
||||
NSArray<ASDisplayNode *> *_removedSubnodes;
|
||||
std::vector<NSInteger> _insertedSubnodePositions;
|
||||
std::vector<NSInteger> _removedSubnodePositions;
|
||||
std::vector<NSUInteger> _insertedSubnodePositions;
|
||||
std::vector<NSUInteger> _removedSubnodePositions;
|
||||
}
|
||||
|
||||
- (instancetype)initWithNode:(ASDisplayNode *)node
|
||||
@@ -48,8 +48,8 @@
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
[self calculateSubnodeOperationsIfNeeded];
|
||||
for (NSInteger i = 0; i < [_insertedSubnodes count]; i++) {
|
||||
NSInteger p = _insertedSubnodePositions[i];
|
||||
for (NSUInteger i = 0; i < [_insertedSubnodes count]; i++) {
|
||||
NSUInteger p = _insertedSubnodePositions[i];
|
||||
[_node insertSubnode:_insertedSubnodes[i] atIndex:p];
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
[self calculateSubnodeOperationsIfNeeded];
|
||||
for (NSInteger i = 0; i < [_removedSubnodes count]; i++) {
|
||||
for (NSUInteger i = 0; i < [_removedSubnodes count]; i++) {
|
||||
[_removedSubnodes[i] removeFromSupernode];
|
||||
}
|
||||
}
|
||||
@@ -77,15 +77,15 @@
|
||||
compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) {
|
||||
return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject);
|
||||
}];
|
||||
filterNodesInLayoutAtIndexes(_pendingLayout, insertions, &_insertedSubnodes, &_insertedSubnodePositions);
|
||||
filterNodesInLayoutAtIndexesWithIntersectingNodes(_previousLayout,
|
||||
findNodesInLayoutAtIndexes(_pendingLayout, insertions, &_insertedSubnodes, &_insertedSubnodePositions);
|
||||
findNodesInLayoutAtIndexesWithFilteredNodes(_previousLayout,
|
||||
deletions,
|
||||
_insertedSubnodes,
|
||||
&_removedSubnodes,
|
||||
&_removedSubnodePositions);
|
||||
} else {
|
||||
NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [_pendingLayout.immediateSublayouts count])];
|
||||
filterNodesInLayoutAtIndexes(_pendingLayout, indexes, &_insertedSubnodes, &_insertedSubnodePositions);
|
||||
findNodesInLayoutAtIndexes(_pendingLayout, indexes, &_insertedSubnodes, &_insertedSubnodePositions);
|
||||
_removedSubnodes = nil;
|
||||
}
|
||||
_calculatedSubnodeOperations = YES;
|
||||
@@ -142,45 +142,38 @@
|
||||
/**
|
||||
* @abstract Stores the nodes at the given indexes in the `storedNodes` array, storing indexes in a `storedPositions` c++ vector.
|
||||
*/
|
||||
static inline void filterNodesInLayoutAtIndexes(
|
||||
ASLayout *layout,
|
||||
NSIndexSet *indexes,
|
||||
NSArray<ASDisplayNode *> * __strong *storedNodes,
|
||||
std::vector<NSInteger> *storedPositions
|
||||
)
|
||||
static inline void findNodesInLayoutAtIndexes(ASLayout *layout,
|
||||
NSIndexSet *indexes,
|
||||
NSArray<ASDisplayNode *> * __strong *storedNodes,
|
||||
std::vector<NSUInteger> *storedPositions)
|
||||
{
|
||||
filterNodesInLayoutAtIndexesWithIntersectingNodes(layout, indexes, nil, storedNodes, storedPositions);
|
||||
findNodesInLayoutAtIndexesWithFilteredNodes(layout, indexes, nil, storedNodes, storedPositions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @abstract Stores the nodes at the given indexes in the `storedNodes` array, storing indexes in a `storedPositions` c++ vector.
|
||||
* @discussion If the node exists in the `intersectingNodes` array, the node is not added to `storedNodes`.
|
||||
* @discussion If the node exists in the `filteredNodes` array, the node is not added to `storedNodes`.
|
||||
*/
|
||||
static inline void filterNodesInLayoutAtIndexesWithIntersectingNodes(
|
||||
ASLayout *layout,
|
||||
NSIndexSet *indexes,
|
||||
NSArray<ASDisplayNode *> *intersectingNodes,
|
||||
NSArray<ASDisplayNode *> * __strong *storedNodes,
|
||||
std::vector<NSInteger> *storedPositions
|
||||
)
|
||||
static inline void findNodesInLayoutAtIndexesWithFilteredNodes(ASLayout *layout,
|
||||
NSIndexSet *indexes,
|
||||
NSArray<ASDisplayNode *> *filteredNodes,
|
||||
NSArray<ASDisplayNode *> * __strong *storedNodes,
|
||||
std::vector<NSUInteger> *storedPositions)
|
||||
{
|
||||
NSMutableArray<ASDisplayNode *> *nodes = [NSMutableArray array];
|
||||
std::vector<NSInteger> positions = std::vector<NSInteger>();
|
||||
NSInteger idx = [indexes firstIndex];
|
||||
std::vector<NSUInteger> positions = std::vector<NSUInteger>();
|
||||
NSUInteger idx = [indexes firstIndex];
|
||||
while (idx != NSNotFound) {
|
||||
BOOL skip = NO;
|
||||
ASDisplayNode *node = (ASDisplayNode *)layout.immediateSublayouts[idx].layoutableObject;
|
||||
ASDisplayNodeCAssert(node, @"A flattened layout must consist exclusively of node sublayouts");
|
||||
for (ASDisplayNode *i in intersectingNodes) {
|
||||
if (node == i) {
|
||||
skip = YES;
|
||||
break;
|
||||
// Ignore the odd case in which a non-node sublayout is accessed and the type cast fails
|
||||
if (node != nil) {
|
||||
BOOL notFiltered = (filteredNodes == nil || [filteredNodes indexOfObjectIdenticalTo:node] == NSNotFound);
|
||||
if (notFiltered) {
|
||||
[nodes addObject:node];
|
||||
positions.push_back(idx);
|
||||
}
|
||||
}
|
||||
if (!skip) {
|
||||
[nodes addObject:node];
|
||||
positions.push_back(idx);
|
||||
}
|
||||
idx = [indexes indexGreaterThanIndex:idx];
|
||||
}
|
||||
*storedNodes = nodes;
|
||||
|
||||
@@ -35,6 +35,8 @@ void ASEnvironmentPerformBlockOnObjectAndParents(id<ASEnvironment> object, void(
|
||||
|
||||
#pragma mark - Merging
|
||||
|
||||
static const struct ASEnvironmentStateExtensions ASEnvironmentDefaultStateExtensions = {};
|
||||
|
||||
static const struct ASEnvironmentLayoutOptionsState ASEnvironmentDefaultLayoutOptionsState = {};
|
||||
ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environmentState, ASEnvironmentLayoutOptionsState state, ASEnvironmentStatePropagation propagation);
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
//#define LOG(...) NSLog(__VA_ARGS__)
|
||||
#define LOG(...)
|
||||
|
||||
#define AS_SUPPORT_PROPAGATION NO
|
||||
|
||||
|
||||
#pragma mark - Traversing an ASEnvironment Tree
|
||||
|
||||
void ASEnvironmentPerformBlockOnObjectAndChildren(id<ASEnvironment> object, void(^block)(id<ASEnvironment> node))
|
||||
@@ -52,9 +55,9 @@ void _ASEnvironmentLayoutOptionsExtensionSetBoolAtIndex(id<ASEnvironment> object
|
||||
{
|
||||
NSCAssert(idx < kMaxEnvironmentStateBoolExtensions, @"Setting index outside of max bool extensions space");
|
||||
|
||||
ASEnvironmentStateExtensions extension = object.environmentState.layoutOptionsState._extensions;
|
||||
extension.boolExtensions[idx] = value;
|
||||
object.environmentState.layoutOptionsState._extensions = extension;
|
||||
ASEnvironmentState state = object.environmentState;
|
||||
state.layoutOptionsState._extensions.boolExtensions[idx] = value;
|
||||
object.environmentState = state;
|
||||
}
|
||||
|
||||
BOOL _ASEnvironmentLayoutOptionsExtensionGetBoolAtIndex(id<ASEnvironment> object, int idx)
|
||||
@@ -67,9 +70,9 @@ void _ASEnvironmentLayoutOptionsExtensionSetIntegerAtIndex(id<ASEnvironment> obj
|
||||
{
|
||||
NSCAssert(idx < kMaxEnvironmentStateIntegerExtensions, @"Setting index outside of max integer extensions space");
|
||||
|
||||
ASEnvironmentStateExtensions extension = object.environmentState.layoutOptionsState._extensions;
|
||||
extension.integerExtensions[idx] = value;
|
||||
object.environmentState.layoutOptionsState._extensions = extension;
|
||||
ASEnvironmentState state = object.environmentState;
|
||||
state.layoutOptionsState._extensions.integerExtensions[idx] = value;
|
||||
object.environmentState = state;
|
||||
}
|
||||
|
||||
NSInteger _ASEnvironmentLayoutOptionsExtensionGetIntegerAtIndex(id<ASEnvironment> object, int idx)
|
||||
@@ -82,9 +85,9 @@ void _ASEnvironmentLayoutOptionsExtensionSetEdgeInsetsAtIndex(id<ASEnvironment>
|
||||
{
|
||||
NSCAssert(idx < kMaxEnvironmentStateEdgeInsetExtensions, @"Setting index outside of max edge insets extensions space");
|
||||
|
||||
ASEnvironmentStateExtensions extension = object.environmentState.layoutOptionsState._extensions;
|
||||
extension.edgeInsetsExtensions[idx] = value;
|
||||
object.environmentState.layoutOptionsState._extensions = extension;
|
||||
ASEnvironmentState state = object.environmentState;
|
||||
state.layoutOptionsState._extensions.edgeInsetsExtensions[idx] = value;
|
||||
object.environmentState = state;
|
||||
}
|
||||
|
||||
UIEdgeInsets _ASEnvironmentLayoutOptionsExtensionGetEdgeInsetsAtIndex(id<ASEnvironment> object, int idx)
|
||||
@@ -96,55 +99,86 @@ UIEdgeInsets _ASEnvironmentLayoutOptionsExtensionGetEdgeInsetsAtIndex(id<ASEnvir
|
||||
|
||||
#pragma mark - Merging functions for states
|
||||
|
||||
ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environmentState, ASEnvironmentHierarchyState state, ASEnvironmentStatePropagation propagation) {
|
||||
ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environmentState, ASEnvironmentHierarchyState hierarchyState, ASEnvironmentStatePropagation propagation) {
|
||||
// Merge object and hierarchy state
|
||||
LOG(@"Merge object and state: %@ - ASEnvironmentHierarchyState", object);
|
||||
return environmentState;
|
||||
}
|
||||
|
||||
ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environmentState, ASEnvironmentLayoutOptionsState state, ASEnvironmentStatePropagation propagation) {
|
||||
ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environmentState, ASEnvironmentLayoutOptionsState layoutOptionsState, ASEnvironmentStatePropagation propagation) {
|
||||
// Merge object and layout options state
|
||||
LOG(@"Merge object and state: %@ - ASEnvironmentLayoutOptionsState", object);
|
||||
|
||||
if (!AS_SUPPORT_PROPAGATION) {
|
||||
return environmentState;
|
||||
}
|
||||
|
||||
// Support propagate up
|
||||
if (propagation == ASEnvironmentStatePropagation::UP) {
|
||||
|
||||
// Object is the parent and the state is the state of the child
|
||||
const ASEnvironmentLayoutOptionsState defaultState = ASEnvironmentDefaultLayoutOptionsState;
|
||||
ASEnvironmentLayoutOptionsState parentState = environmentState.layoutOptionsState;
|
||||
ASEnvironmentLayoutOptionsState parentLayoutOptionsState = environmentState.layoutOptionsState;
|
||||
|
||||
// For every field check if the parent value is equal to the default than propegate up the child value to
|
||||
// the parent
|
||||
if (parentState.spacingBefore == defaultState.spacingBefore) {
|
||||
parentState.spacingBefore = state.spacingBefore;
|
||||
// For every field check if the parent value is equal to the default and if so propegate up the value of the passed
|
||||
// in layout options state
|
||||
if (parentLayoutOptionsState.spacingBefore == defaultState.spacingBefore) {
|
||||
parentLayoutOptionsState.spacingBefore = layoutOptionsState.spacingBefore;
|
||||
}
|
||||
if (parentState.spacingAfter == defaultState.spacingAfter) {
|
||||
parentState.spacingAfter = state.spacingAfter;
|
||||
if (parentLayoutOptionsState.spacingAfter == defaultState.spacingAfter) {
|
||||
parentLayoutOptionsState.spacingAfter = layoutOptionsState.spacingAfter;
|
||||
}
|
||||
if (parentState.alignSelf == defaultState.alignSelf) {
|
||||
parentState.alignSelf = state.alignSelf;
|
||||
if (parentLayoutOptionsState.alignSelf == defaultState.alignSelf) {
|
||||
parentLayoutOptionsState.alignSelf = layoutOptionsState.alignSelf;
|
||||
}
|
||||
if (parentState.flexGrow == defaultState.flexGrow) {
|
||||
parentState.flexGrow = state.flexGrow;
|
||||
if (parentLayoutOptionsState.flexGrow == defaultState.flexGrow) {
|
||||
parentLayoutOptionsState.flexGrow = layoutOptionsState.flexGrow;
|
||||
}
|
||||
if (ASRelativeDimensionEqualToRelativeDimension(parentState.flexBasis, defaultState.flexBasis)) {
|
||||
parentState.flexBasis = state.flexBasis;
|
||||
if (ASRelativeDimensionEqualToRelativeDimension(parentLayoutOptionsState.flexBasis, defaultState.flexBasis)) {
|
||||
parentLayoutOptionsState.flexBasis = layoutOptionsState.flexBasis;
|
||||
}
|
||||
if (parentState.alignSelf == defaultState.alignSelf) {
|
||||
parentState.alignSelf = state.alignSelf;
|
||||
if (parentLayoutOptionsState.alignSelf == defaultState.alignSelf) {
|
||||
parentLayoutOptionsState.alignSelf = layoutOptionsState.alignSelf;
|
||||
}
|
||||
if (parentState.ascender == defaultState.ascender) {
|
||||
parentState.ascender = state.ascender;
|
||||
if (parentLayoutOptionsState.ascender == defaultState.ascender) {
|
||||
parentLayoutOptionsState.ascender = layoutOptionsState.ascender;
|
||||
}
|
||||
|
||||
if (ASRelativeSizeRangeEqualToRelativeSizeRange(parentState.sizeRange, defaultState.sizeRange)) {
|
||||
parentState.sizeRange = state.sizeRange;
|
||||
if (ASRelativeSizeRangeEqualToRelativeSizeRange(parentLayoutOptionsState.sizeRange, defaultState.sizeRange)) {
|
||||
// For now it is unclear if we should be up-propagating sizeRange or layoutPosition.
|
||||
// parentLayoutOptionsState.sizeRange = layoutOptionsState.sizeRange;
|
||||
}
|
||||
if (CGPointEqualToPoint(parentState.layoutPosition, defaultState.layoutPosition)) {
|
||||
parentState.layoutPosition = state.layoutPosition;
|
||||
if (CGPointEqualToPoint(parentLayoutOptionsState.layoutPosition, defaultState.layoutPosition)) {
|
||||
// For now it is unclear if we should be up-propagating sizeRange or layoutPosition.
|
||||
// parentLayoutOptionsState.layoutPosition = layoutOptionsState.layoutPosition;
|
||||
}
|
||||
|
||||
environmentState.layoutOptionsState = parentState;
|
||||
// Merge extended values if necessary
|
||||
const ASEnvironmentStateExtensions defaultExtensions = ASEnvironmentDefaultStateExtensions;
|
||||
const ASEnvironmentStateExtensions layoutOptionsStateExtensions = layoutOptionsState._extensions;
|
||||
ASEnvironmentStateExtensions parentLayoutOptionsExtensions = parentLayoutOptionsState._extensions;
|
||||
|
||||
for (int i = 0; i < kMaxEnvironmentStateBoolExtensions; i++) {
|
||||
if (parentLayoutOptionsExtensions.boolExtensions[i] == defaultExtensions.boolExtensions[i]) {
|
||||
parentLayoutOptionsExtensions.boolExtensions[i] = layoutOptionsStateExtensions.boolExtensions[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < kMaxEnvironmentStateIntegerExtensions; i++) {
|
||||
if (parentLayoutOptionsExtensions.integerExtensions[i] == defaultExtensions.integerExtensions[i]) {
|
||||
parentLayoutOptionsExtensions.integerExtensions[i] = layoutOptionsStateExtensions.integerExtensions[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < kMaxEnvironmentStateEdgeInsetExtensions; i++) {
|
||||
if (UIEdgeInsetsEqualToEdgeInsets(parentLayoutOptionsExtensions.edgeInsetsExtensions[i], defaultExtensions.edgeInsetsExtensions[i])) {
|
||||
parentLayoutOptionsExtensions.edgeInsetsExtensions[i] = layoutOptionsStateExtensions.edgeInsetsExtensions[i];
|
||||
}
|
||||
}
|
||||
parentLayoutOptionsState._extensions = parentLayoutOptionsExtensions;
|
||||
|
||||
// Update layout options state
|
||||
environmentState.layoutOptionsState = parentLayoutOptionsState;
|
||||
}
|
||||
|
||||
return environmentState;
|
||||
|
||||
@@ -67,6 +67,10 @@ typedef struct {
|
||||
int setAccessibilityViewIsModal:1;
|
||||
int setShouldGroupAccessibilityChildren:1;
|
||||
int setAccessibilityIdentifier:1;
|
||||
int setAccessibilityNavigationStyle:1;
|
||||
int setAccessibilityHeaderElements:1;
|
||||
int setAccessibilityActivationPoint:1;
|
||||
int setAccessibilityPath:1;
|
||||
} ASPendingStateFlags;
|
||||
|
||||
@implementation _ASPendingState
|
||||
@@ -106,6 +110,10 @@ typedef struct {
|
||||
BOOL accessibilityViewIsModal;
|
||||
BOOL shouldGroupAccessibilityChildren;
|
||||
NSString *accessibilityIdentifier;
|
||||
UIAccessibilityNavigationStyle accessibilityNavigationStyle;
|
||||
NSArray *accessibilityHeaderElements;
|
||||
CGPoint accessibilityActivationPoint;
|
||||
UIBezierPath *accessibilityPath;
|
||||
|
||||
ASPendingStateFlags _flags;
|
||||
}
|
||||
@@ -226,6 +234,10 @@ static UIColor *defaultTintColor = nil;
|
||||
accessibilityViewIsModal = NO;
|
||||
shouldGroupAccessibilityChildren = NO;
|
||||
accessibilityIdentifier = nil;
|
||||
accessibilityNavigationStyle = UIAccessibilityNavigationStyleAutomatic;
|
||||
accessibilityHeaderElements = nil;
|
||||
accessibilityActivationPoint = CGPointZero;
|
||||
accessibilityPath = nil;
|
||||
edgeAntialiasingMask = (kCALayerLeftEdge | kCALayerRightEdge | kCALayerTopEdge | kCALayerBottomEdge);
|
||||
|
||||
return self;
|
||||
@@ -594,6 +606,59 @@ static UIColor *defaultTintColor = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (UIAccessibilityNavigationStyle)accessibilityNavigationStyle
|
||||
{
|
||||
return accessibilityNavigationStyle;
|
||||
}
|
||||
|
||||
- (void)setAccessibilityNavigationStyle:(UIAccessibilityNavigationStyle)newAccessibilityNavigationStyle
|
||||
{
|
||||
_flags.setAccessibilityNavigationStyle = YES;
|
||||
accessibilityNavigationStyle = newAccessibilityNavigationStyle;
|
||||
}
|
||||
|
||||
- (NSArray *)accessibilityHeaderElements
|
||||
{
|
||||
return accessibilityHeaderElements;
|
||||
}
|
||||
|
||||
- (void)setAccessibilityHeaderElements:(NSArray *)newAccessibilityHeaderElements
|
||||
{
|
||||
_flags.setAccessibilityHeaderElements = YES;
|
||||
if (accessibilityHeaderElements != newAccessibilityHeaderElements) {
|
||||
accessibilityHeaderElements = [newAccessibilityHeaderElements copy];
|
||||
}
|
||||
}
|
||||
|
||||
- (CGPoint)accessibilityActivationPoint
|
||||
{
|
||||
if (_flags.setAccessibilityActivationPoint) {
|
||||
return accessibilityActivationPoint;
|
||||
}
|
||||
|
||||
// Default == Mid-point of the accessibilityFrame
|
||||
return CGPointMake(CGRectGetMidX(accessibilityFrame), CGRectGetMidY(accessibilityFrame));
|
||||
}
|
||||
|
||||
- (void)setAccessibilityActivationPoint:(CGPoint)newAccessibilityActivationPoint
|
||||
{
|
||||
_flags.setAccessibilityActivationPoint = YES;
|
||||
accessibilityActivationPoint = newAccessibilityActivationPoint;
|
||||
}
|
||||
|
||||
- (UIBezierPath *)accessibilityPath
|
||||
{
|
||||
return accessibilityPath;
|
||||
}
|
||||
|
||||
- (void)setAccessibilityPath:(UIBezierPath *)newAccessibilityPath
|
||||
{
|
||||
_flags.setAccessibilityPath = YES;
|
||||
if (accessibilityPath != newAccessibilityPath) {
|
||||
accessibilityPath = newAccessibilityPath;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applyToLayer:(CALayer *)layer
|
||||
{
|
||||
ASPendingStateFlags flags = _flags;
|
||||
@@ -827,6 +892,20 @@ static UIColor *defaultTintColor = nil;
|
||||
|
||||
if (flags.setAccessibilityIdentifier)
|
||||
view.accessibilityIdentifier = accessibilityIdentifier;
|
||||
|
||||
if (flags.setAccessibilityNavigationStyle)
|
||||
view.accessibilityNavigationStyle = accessibilityNavigationStyle;
|
||||
|
||||
#if TARGET_OS_TV
|
||||
if (flags.setAccessibilityHeaderElements)
|
||||
view.accessibilityHeaderElements = accessibilityHeaderElements;
|
||||
#endif
|
||||
|
||||
if (flags.setAccessibilityActivationPoint)
|
||||
view.accessibilityActivationPoint = accessibilityActivationPoint;
|
||||
|
||||
if (flags.setAccessibilityPath)
|
||||
view.accessibilityPath = accessibilityPath;
|
||||
|
||||
// For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - make sure UIView gets setFrame:
|
||||
if (flags.setFrame && setFrameDirectly) {
|
||||
@@ -926,6 +1005,12 @@ static UIColor *defaultTintColor = nil;
|
||||
pendingState.accessibilityViewIsModal = view.accessibilityViewIsModal;
|
||||
pendingState.shouldGroupAccessibilityChildren = view.shouldGroupAccessibilityChildren;
|
||||
pendingState.accessibilityIdentifier = view.accessibilityIdentifier;
|
||||
pendingState.accessibilityNavigationStyle = view.accessibilityNavigationStyle;
|
||||
#if TARGET_OS_TV
|
||||
pendingState.accessibilityHeaderElements = view.accessibilityHeaderElements;
|
||||
#endif
|
||||
pendingState.accessibilityActivationPoint = view.accessibilityActivationPoint;
|
||||
pendingState.accessibilityPath = view.accessibilityPath;
|
||||
return pendingState;
|
||||
}
|
||||
|
||||
@@ -992,7 +1077,11 @@ static UIColor *defaultTintColor = nil;
|
||||
|| flags.setAccessibilityElementsHidden
|
||||
|| flags.setAccessibilityViewIsModal
|
||||
|| flags.setShouldGroupAccessibilityChildren
|
||||
|| flags.setAccessibilityIdentifier);
|
||||
|| flags.setAccessibilityIdentifier
|
||||
|| flags.setAccessibilityNavigationStyle
|
||||
|| flags.setAccessibilityHeaderElements
|
||||
|| flags.setAccessibilityActivationPoint
|
||||
|| flags.setAccessibilityPath);
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
|
||||
@@ -106,6 +106,76 @@
|
||||
XCTAssert(controller.hits == 1, @"Controller did not receive the action event");
|
||||
}
|
||||
|
||||
- (void)testRemoveWithoutTargetRemovesTargetlessAction {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node removeTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 0, @"Controller did not receive exactly zero action events");
|
||||
}
|
||||
|
||||
- (void)testRemoveWithTarget {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node removeTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 0, @"Controller did not receive exactly zero action events");
|
||||
}
|
||||
|
||||
- (void)testRemoveWithTargetRemovesAction {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node removeTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 0, @"Controller did not receive exactly zero action events");
|
||||
}
|
||||
|
||||
- (void)testRemoveWithoutTargetRemovesTargetedAction {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node removeTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 0, @"Controller did not receive exactly zero action events");
|
||||
}
|
||||
|
||||
- (void)testDuplicateEntriesWithoutTarget {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node addTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 1, @"Controller did not receive exactly one action event");
|
||||
}
|
||||
|
||||
- (void)testDuplicateEntriesWithTarget {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 1, @"Controller did not receive exactly one action event");
|
||||
}
|
||||
|
||||
- (void)testDuplicateEntriesWithAndWithoutTarget {
|
||||
ASActionSenderEventController *controller = [[ASActionSenderEventController alloc] init];
|
||||
ASControlNode *node = [[ASControlNode alloc] init];
|
||||
[node addTarget:controller action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[node addTarget:nil action:ACTION_SENDER_EVENT forControlEvents:EVENT];
|
||||
[controller.view addSubview:node.view];
|
||||
[node sendActionsForControlEvents:EVENT withEvent:nil];
|
||||
XCTAssertEqual(controller.hits, 2, @"Controller did not receive exactly two action events");
|
||||
}
|
||||
|
||||
- (void)testDeeperHierarchyWithoutTarget {
|
||||
ASActionController *controller = [[ASActionController alloc] init];
|
||||
UIView *view = [[UIView alloc] init];
|
||||
|
||||
@@ -308,6 +308,17 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
||||
XCTAssertEqual(YES, node.displaysAsynchronously, @"default displaysAsynchronously broken %@", hasLoadedView);
|
||||
XCTAssertEqual(NO, node.asyncdisplaykit_asyncTransactionContainer, @"default asyncdisplaykit_asyncTransactionContainer broken %@", hasLoadedView);
|
||||
XCTAssertEqualObjects(nil, node.name, @"default name broken %@", hasLoadedView);
|
||||
|
||||
XCTAssertEqual(NO, node.isAccessibilityElement, @"default isAccessibilityElement is broken %@", hasLoadedView);
|
||||
XCTAssertEqual((id)nil, node.accessibilityLabel, @"default accessibilityLabel is broken %@", hasLoadedView);
|
||||
XCTAssertEqual((id)nil, node.accessibilityHint, @"default accessibilityHint is broken %@", hasLoadedView);
|
||||
XCTAssertEqual((id)nil, node.accessibilityValue, @"default accessibilityValue is broken %@", hasLoadedView);
|
||||
XCTAssertEqual(UIAccessibilityTraitNone, node.accessibilityTraits, @"default accessibilityTraits is broken %@", hasLoadedView);
|
||||
XCTAssertTrue(CGRectEqualToRect(CGRectZero, node.accessibilityFrame), @"default accessibilityFrame is broken %@", hasLoadedView);
|
||||
XCTAssertEqual((id)nil, node.accessibilityLanguage, @"default accessibilityLanguage is broken %@", hasLoadedView);
|
||||
XCTAssertEqual(NO, node.accessibilityElementsHidden, @"default accessibilityElementsHidden is broken %@", hasLoadedView);
|
||||
XCTAssertEqual(NO, node.accessibilityViewIsModal, @"default accessibilityViewIsModal is broken %@", hasLoadedView);
|
||||
XCTAssertEqual(NO, node.shouldGroupAccessibilityChildren, @"default shouldGroupAccessibilityChildren is broken %@", hasLoadedView);
|
||||
|
||||
if (!isLayerBacked) {
|
||||
XCTAssertEqual(YES, node.userInteractionEnabled, @"default userInteractionEnabled broken %@", hasLoadedView);
|
||||
@@ -318,19 +329,6 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
||||
XCTAssertEqual(NO, node.userInteractionEnabled, @"layer-backed nodes do not support userInteractionEnabled %@", hasLoadedView);
|
||||
XCTAssertEqual(NO, node.exclusiveTouch, @"layer-backed nodes do not support exclusiveTouch %@", hasLoadedView);
|
||||
}
|
||||
|
||||
if (!isLayerBacked) {
|
||||
XCTAssertEqual(NO, node.isAccessibilityElement, @"default isAccessibilityElement is broken %@", hasLoadedView);
|
||||
XCTAssertEqual((id)nil, node.accessibilityLabel, @"default accessibilityLabel is broken %@", hasLoadedView);
|
||||
XCTAssertEqual((id)nil, node.accessibilityHint, @"default accessibilityHint is broken %@", hasLoadedView);
|
||||
XCTAssertEqual((id)nil, node.accessibilityValue, @"default accessibilityValue is broken %@", hasLoadedView);
|
||||
XCTAssertEqual(UIAccessibilityTraitNone, node.accessibilityTraits, @"default accessibilityTraits is broken %@", hasLoadedView);
|
||||
XCTAssertTrue(CGRectEqualToRect(CGRectZero, node.accessibilityFrame), @"default accessibilityFrame is broken %@", hasLoadedView);
|
||||
XCTAssertEqual((id)nil, node.accessibilityLanguage, @"default accessibilityLanguage is broken %@", hasLoadedView);
|
||||
XCTAssertEqual(NO, node.accessibilityElementsHidden, @"default accessibilityElementsHidden is broken %@", hasLoadedView);
|
||||
XCTAssertEqual(NO, node.accessibilityViewIsModal, @"default accessibilityViewIsModal is broken %@", hasLoadedView);
|
||||
XCTAssertEqual(NO, node.shouldGroupAccessibilityChildren, @"default shouldGroupAccessibilityChildren is broken %@", hasLoadedView);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)checkDefaultPropertyValuesWithLayerBacking:(BOOL)isLayerBacked
|
||||
@@ -406,20 +404,25 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
||||
XCTAssertEqual(NO, node.userInteractionEnabled, @"userInteractionEnabled broken %@", hasLoadedView);
|
||||
XCTAssertEqual((BOOL)!isLayerBacked, node.exclusiveTouch, @"exclusiveTouch broken %@", hasLoadedView);
|
||||
XCTAssertEqualObjects(@"quack like a duck", node.name, @"name broken %@", hasLoadedView);
|
||||
|
||||
XCTAssertEqual(YES, node.isAccessibilityElement, @"accessibilityElement broken %@", hasLoadedView);
|
||||
XCTAssertEqualObjects(@"Ship love", node.accessibilityLabel, @"accessibilityLabel broken %@", hasLoadedView);
|
||||
XCTAssertEqualObjects(@"Awesome things will happen", node.accessibilityHint, @"accessibilityHint broken %@", hasLoadedView);
|
||||
XCTAssertEqualObjects(@"1 of 2", node.accessibilityValue, @"accessibilityValue broken %@", hasLoadedView);
|
||||
XCTAssertEqual(UIAccessibilityTraitSelected | UIAccessibilityTraitButton, node.accessibilityTraits, @"accessibilityTraits broken %@", hasLoadedView);
|
||||
XCTAssertTrue(CGRectEqualToRect(CGRectMake(1, 2, 3, 4), node.accessibilityFrame), @"accessibilityFrame broken %@", hasLoadedView);
|
||||
XCTAssertEqualObjects(@"mas", node.accessibilityLanguage, @"accessibilityLanguage broken %@", hasLoadedView);
|
||||
XCTAssertEqual(YES, node.accessibilityElementsHidden, @"accessibilityElementsHidden broken %@", hasLoadedView);
|
||||
XCTAssertEqual(YES, node.accessibilityViewIsModal, @"accessibilityViewIsModal broken %@", hasLoadedView);
|
||||
XCTAssertEqual(YES, node.shouldGroupAccessibilityChildren, @"shouldGroupAccessibilityChildren broken %@", hasLoadedView);
|
||||
XCTAssertEqual(UIAccessibilityNavigationStyleSeparate, node.accessibilityNavigationStyle, @"accessibilityNavigationStyle broken %@", hasLoadedView);
|
||||
XCTAssertTrue(CGPointEqualToPoint(CGPointMake(1.0, 1.0), node.accessibilityActivationPoint), @"accessibilityActivationPoint broken %@", hasLoadedView);
|
||||
XCTAssertNotNil(node.accessibilityPath, @"accessibilityPath broken %@", hasLoadedView);
|
||||
|
||||
|
||||
if (!isLayerBacked) {
|
||||
XCTAssertEqual(UIViewAutoresizingFlexibleLeftMargin, node.autoresizingMask, @"autoresizingMask %@", hasLoadedView);
|
||||
XCTAssertEqual(NO, node.autoresizesSubviews, @"autoresizesSubviews broken %@", hasLoadedView);
|
||||
XCTAssertEqual(YES, node.isAccessibilityElement, @"accessibilityElement broken %@", hasLoadedView);
|
||||
XCTAssertEqualObjects(@"Ship love", node.accessibilityLabel, @"accessibilityLabel broken %@", hasLoadedView);
|
||||
XCTAssertEqualObjects(@"Awesome things will happen", node.accessibilityHint, @"accessibilityHint broken %@", hasLoadedView);
|
||||
XCTAssertEqualObjects(@"1 of 2", node.accessibilityValue, @"accessibilityValue broken %@", hasLoadedView);
|
||||
XCTAssertEqual(UIAccessibilityTraitSelected | UIAccessibilityTraitButton, node.accessibilityTraits, @"accessibilityTraits broken %@", hasLoadedView);
|
||||
XCTAssertTrue(CGRectEqualToRect(CGRectMake(1, 2, 3, 4), node.accessibilityFrame), @"accessibilityFrame broken %@", hasLoadedView);
|
||||
XCTAssertEqualObjects(@"mas", node.accessibilityLanguage, @"accessibilityLanguage broken %@", hasLoadedView);
|
||||
XCTAssertEqual(YES, node.accessibilityElementsHidden, @"accessibilityElementsHidden broken %@", hasLoadedView);
|
||||
XCTAssertEqual(YES, node.accessibilityViewIsModal, @"accessibilityViewIsModal broken %@", hasLoadedView);
|
||||
XCTAssertEqual(YES, node.shouldGroupAccessibilityChildren, @"shouldGroupAccessibilityChildren broken %@", hasLoadedView);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,21 +461,25 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
||||
node.asyncdisplaykit_asyncTransactionContainer = YES;
|
||||
node.userInteractionEnabled = NO;
|
||||
node.name = @"quack like a duck";
|
||||
|
||||
node.isAccessibilityElement = YES;
|
||||
node.accessibilityLabel = @"Ship love";
|
||||
node.accessibilityHint = @"Awesome things will happen";
|
||||
node.accessibilityValue = @"1 of 2";
|
||||
node.accessibilityTraits = UIAccessibilityTraitSelected | UIAccessibilityTraitButton;
|
||||
node.accessibilityFrame = CGRectMake(1, 2, 3, 4);
|
||||
node.accessibilityLanguage = @"mas";
|
||||
node.accessibilityElementsHidden = YES;
|
||||
node.accessibilityViewIsModal = YES;
|
||||
node.shouldGroupAccessibilityChildren = YES;
|
||||
node.accessibilityNavigationStyle = UIAccessibilityNavigationStyleSeparate;
|
||||
node.accessibilityActivationPoint = CGPointMake(1.0, 1.0);
|
||||
node.accessibilityPath = [UIBezierPath bezierPath];
|
||||
|
||||
if (!isLayerBacked) {
|
||||
node.exclusiveTouch = YES;
|
||||
node.autoresizesSubviews = NO;
|
||||
node.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
|
||||
node.isAccessibilityElement = YES;
|
||||
node.accessibilityLabel = @"Ship love";
|
||||
node.accessibilityHint = @"Awesome things will happen";
|
||||
node.accessibilityValue = @"1 of 2";
|
||||
node.accessibilityTraits = UIAccessibilityTraitSelected | UIAccessibilityTraitButton;
|
||||
node.accessibilityFrame = CGRectMake(1, 2, 3, 4);
|
||||
node.accessibilityLanguage = @"mas";
|
||||
node.accessibilityElementsHidden = YES;
|
||||
node.accessibilityViewIsModal = YES;
|
||||
node.shouldGroupAccessibilityChildren = YES;
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
@interface ASVideoNode () {
|
||||
ASDisplayNode *_playerNode;
|
||||
AVPlayer *_player;
|
||||
}
|
||||
@property (atomic) ASInterfaceState interfaceState;
|
||||
@property (atomic) ASDisplayNode *spinner;
|
||||
@@ -37,6 +38,11 @@
|
||||
_playerNode = playerNode;
|
||||
}
|
||||
|
||||
- (void)setPlayer:(AVPlayer *)player
|
||||
{
|
||||
_player = player;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASVideoNodeTests
|
||||
@@ -185,4 +191,23 @@
|
||||
XCTAssertTrue(_videoNode.shouldBePlaying);
|
||||
}
|
||||
|
||||
- (void)testMutingShouldMutePlayer
|
||||
{
|
||||
[_videoNode setPlayer:[[AVPlayer alloc] init]];
|
||||
|
||||
_videoNode.muted = YES;
|
||||
|
||||
XCTAssertTrue(_videoNode.player.muted);
|
||||
}
|
||||
|
||||
- (void)testUnMutingShouldUnMutePlayer
|
||||
{
|
||||
[_videoNode setPlayer:[[AVPlayer alloc] init]];
|
||||
|
||||
_videoNode.muted = YES;
|
||||
_videoNode.muted = NO;
|
||||
|
||||
XCTAssertFalse(_videoNode.player.muted);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
18
README.md
18
README.md
@@ -1,12 +1,16 @@
|
||||

|
||||
|
||||
[](https://travis-ci.org/facebook/AsyncDisplayKit)
|
||||
[](https://coveralls.io/r/facebook/AsyncDisplayKit?branch=master)
|
||||
[](http://cocoapods.org/pods/AsyncDisplayKit)
|
||||
[](http://cocoapods.org/pods/AsyncDisplayKit)
|
||||
|
||||
[](http://AsyncDisplayKit.org)
|
||||
[](http://AsyncDisplayKit.org)
|
||||
|
||||
[](http://cocoapods.org/pods/AsyncDisplayKit)
|
||||
[]()
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
[](https://travis-ci.org/facebook/AsyncDisplayKit)
|
||||
[](https://github.com/facebook/AsyncDisplayKit/blob/master/LICENSE)
|
||||
[](http://cocoapods.org/pods/AsyncDisplayKit)
|
||||
|
||||
|
||||
AsyncDisplayKit is an iOS framework that keeps even the most complex user
|
||||
interfaces smooth and responsive. It was originally built to make Facebook's
|
||||
@@ -95,9 +99,9 @@ to implement node hierarchies or custom drawing.
|
||||
|
||||
### Learn more
|
||||
|
||||
* Read the [Getting Started guide](http://asyncdisplaykit.org/guide/)
|
||||
* Read the [Getting Started guide](http://asyncdisplaykit.org/docs/getting-started.html/)
|
||||
* Get the [sample projects](https://github.com/facebook/AsyncDisplayKit/tree/master/examples)
|
||||
* Browse the [API reference](http://asyncdisplaykit.org/appledoc/)
|
||||
* Browse the [API reference](http://asyncdisplaykit.org/appledocs.html)
|
||||
* Watch the [NSLondon talk](http://vimeo.com/103589245) or the [NSSpain talk](https://www.youtube.com/watch?v=RY_X7l1g79Q)
|
||||
|
||||
## Testing
|
||||
|
||||
10
examples/ASDKgram/Sample.xcworkspace/contents.xcworkspacedata
generated
Normal file
10
examples/ASDKgram/Sample.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Sample.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
Reference in New Issue
Block a user