Merge remote-tracking branch 'facebook/master' into 500pxgram

This commit is contained in:
Hannah Troisi
2016-04-10 21:46:51 -07:00
32 changed files with 1044 additions and 316 deletions

View File

@@ -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 */,

View File

@@ -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];
}];
}
}

View File

@@ -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));
}
}

View File

@@ -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

View File

@@ -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);
}
});
}
}
}

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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];

View File

@@ -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]];
}
}

View File

@@ -334,7 +334,8 @@
- (void)setMuted:(BOOL)muted
{
ASDN::MutexLocker l(_videoLock);
_player.muted = muted;
_muted = muted;
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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];
});
}

View File

@@ -46,7 +46,7 @@ typedef struct ASEnvironmentLayoutOptionsState {
ASRelativeSizeRange sizeRange;// = ASRelativeSizeRangeMake(ASRelativeSizeMakeWithCGSize(CGSizeZero), ASRelativeSizeMakeWithCGSize(CGSizeZero));;
CGPoint layoutPosition;// = CGPointZero;
ASEnvironmentStateExtensions _extensions;
struct ASEnvironmentStateExtensions _extensions;
} ASEnvironmentLayoutOptionsState;

View File

@@ -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;

View 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>

View 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

View File

@@ -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, {

View File

@@ -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;

View File

@@ -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.

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -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];

View File

@@ -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;
}
}];

View File

@@ -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

View File

@@ -1,12 +1,16 @@
![AsyncDisplayKit](https://github.com/facebook/AsyncDisplayKit/blob/master/docs/assets/logo.png)
[![Build Status](https://travis-ci.org/facebook/AsyncDisplayKit.svg)](https://travis-ci.org/facebook/AsyncDisplayKit)
[![Coverage Status](https://coveralls.io/repos/facebook/AsyncDisplayKit/badge.svg?branch=master)](https://coveralls.io/r/facebook/AsyncDisplayKit?branch=master)
[![Apps Using](https://img.shields.io/badge/Apps%20Using%20ASDK-%3E3,178-28B9FE.svg)](http://cocoapods.org/pods/AsyncDisplayKit)
[![Downloads](https://img.shields.io/badge/Total%20Downloads-%3E336,372-28B9FE.svg)](http://cocoapods.org/pods/AsyncDisplayKit)
[![Platform](https://img.shields.io/badge/platforms-iOS%20%7C%20tvOS-orange.svg)](http://AsyncDisplayKit.org)
[![Languages](https://img.shields.io/badge/languages-ObjC%20%7C%20Swift-orange.svg)](http://AsyncDisplayKit.org)
[![Version](https://img.shields.io/cocoapods/v/AsyncDisplayKit.svg)](http://cocoapods.org/pods/AsyncDisplayKit)
[![Platform](https://img.shields.io/cocoapods/p/AsyncDisplayKit.svg)]()
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-59C939.svg?style=flat)](https://github.com/Carthage/Carthage)
[![Build Status](https://travis-ci.org/facebook/AsyncDisplayKit.svg)](https://travis-ci.org/facebook/AsyncDisplayKit)
[![License](https://img.shields.io/cocoapods/l/AsyncDisplayKit.svg)](https://github.com/facebook/AsyncDisplayKit/blob/master/LICENSE)
[![Downloads](https://img.shields.io/badge/downloads-%3E320k-green.svg)](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

View 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>