From fd159aa196746f826574e3718cbb0f91e0a7ec99 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 4 Apr 2016 16:42:51 -0700 Subject: [PATCH 01/46] [ASMapNode] Never set invalid snapshot dimensions into MapKit --- AsyncDisplayKit/ASMapNode.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASMapNode.mm b/AsyncDisplayKit/ASMapNode.mm index ae3d6801..96a4536a 100644 --- a/AsyncDisplayKit/ASMapNode.mm +++ b/AsyncDisplayKit/ASMapNode.mm @@ -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]; From be463c0a17938ce755fc013c084176f910660896 Mon Sep 17 00:00:00 2001 From: Luke Parham Date: Tue, 5 Apr 2016 02:37:07 -0500 Subject: [PATCH 02/46] muting the video node mutes the player --- AsyncDisplayKit/ASVideoNode.mm | 3 ++- AsyncDisplayKitTests/ASVideoNodeTests.m | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 34ac29bf..d0c65e6a 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -334,7 +334,8 @@ - (void)setMuted:(BOOL)muted { ASDN::MutexLocker l(_videoLock); - + + _player.muted = muted; _muted = muted; } diff --git a/AsyncDisplayKitTests/ASVideoNodeTests.m b/AsyncDisplayKitTests/ASVideoNodeTests.m index 1a4d3f81..adabc9e7 100644 --- a/AsyncDisplayKitTests/ASVideoNodeTests.m +++ b/AsyncDisplayKitTests/ASVideoNodeTests.m @@ -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 From 5ba756eac7da235f406a372f651109c67b0652e1 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 5 Apr 2016 07:44:28 -0700 Subject: [PATCH 03/46] Fix comments about maximum number of extended values in ASLayoutableExtensibility --- AsyncDisplayKit/Layout/ASLayoutableExtensibility.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/Layout/ASLayoutableExtensibility.h b/AsyncDisplayKit/Layout/ASLayoutableExtensibility.h index c35d04f0..62cadd3e 100644 --- a/AsyncDisplayKit/Layout/ASLayoutableExtensibility.h +++ b/AsyncDisplayKit/Layout/ASLayoutableExtensibility.h @@ -10,15 +10,15 @@ @protocol ASLayoutableExtensibility -/// 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; From 41eb1917e4f64bc92a16717d9e04c55fbab2d0c5 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 5 Apr 2016 14:21:36 -0700 Subject: [PATCH 04/46] Fix setting ASEnvironmentStateExtensions values --- AsyncDisplayKit/Details/ASEnvironment.h | 2 +- .../Private/ASEnvironmentInternal.mm | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/AsyncDisplayKit/Details/ASEnvironment.h b/AsyncDisplayKit/Details/ASEnvironment.h index 20837d4a..6a42f18c 100644 --- a/AsyncDisplayKit/Details/ASEnvironment.h +++ b/AsyncDisplayKit/Details/ASEnvironment.h @@ -46,7 +46,7 @@ typedef struct ASEnvironmentLayoutOptionsState { ASRelativeSizeRange sizeRange;// = ASRelativeSizeRangeMake(ASRelativeSizeMakeWithCGSize(CGSizeZero), ASRelativeSizeMakeWithCGSize(CGSizeZero));; CGPoint layoutPosition;// = CGPointZero; - ASEnvironmentStateExtensions _extensions; + struct ASEnvironmentStateExtensions _extensions; } ASEnvironmentLayoutOptionsState; diff --git a/AsyncDisplayKit/Private/ASEnvironmentInternal.mm b/AsyncDisplayKit/Private/ASEnvironmentInternal.mm index 7685203e..b49fbd0e 100644 --- a/AsyncDisplayKit/Private/ASEnvironmentInternal.mm +++ b/AsyncDisplayKit/Private/ASEnvironmentInternal.mm @@ -52,9 +52,9 @@ void _ASEnvironmentLayoutOptionsExtensionSetBoolAtIndex(id 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 object, int idx) @@ -67,9 +67,9 @@ void _ASEnvironmentLayoutOptionsExtensionSetIntegerAtIndex(id 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 object, int idx) @@ -82,9 +82,9 @@ void _ASEnvironmentLayoutOptionsExtensionSetEdgeInsetsAtIndex(id { 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 object, int idx) From 27cc2bec82fa7d7779e87a5756c4bbf798241d65 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 5 Apr 2016 14:21:55 -0700 Subject: [PATCH 05/46] Add merging ASEnvironmentStateExtensions on upward propegation --- .../Private/ASEnvironmentInternal.h | 2 + .../Private/ASEnvironmentInternal.mm | 73 +++++++++++++------ 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/AsyncDisplayKit/Private/ASEnvironmentInternal.h b/AsyncDisplayKit/Private/ASEnvironmentInternal.h index 9cf9a11e..e69bed23 100644 --- a/AsyncDisplayKit/Private/ASEnvironmentInternal.h +++ b/AsyncDisplayKit/Private/ASEnvironmentInternal.h @@ -35,6 +35,8 @@ void ASEnvironmentPerformBlockOnObjectAndParents(id object, void( #pragma mark - Merging +static const struct ASEnvironmentStateExtensions ASEnvironmentDefaultStateExtensions = {}; + static const struct ASEnvironmentLayoutOptionsState ASEnvironmentDefaultLayoutOptionsState = {}; ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environmentState, ASEnvironmentLayoutOptionsState state, ASEnvironmentStatePropagation propagation); diff --git a/AsyncDisplayKit/Private/ASEnvironmentInternal.mm b/AsyncDisplayKit/Private/ASEnvironmentInternal.mm index b49fbd0e..909787a5 100644 --- a/AsyncDisplayKit/Private/ASEnvironmentInternal.mm +++ b/AsyncDisplayKit/Private/ASEnvironmentInternal.mm @@ -96,13 +96,13 @@ UIEdgeInsets _ASEnvironmentLayoutOptionsExtensionGetEdgeInsetsAtIndex(id Date: Tue, 5 Apr 2016 15:51:53 -0700 Subject: [PATCH 06/46] [ASControlNode] Squash duplicated target-action-event tuples, fix assertion failure removing nil target --- AsyncDisplayKit/ASControlNode.mm | 11 ++-- AsyncDisplayKitTests/ASControlNodeTests.m | 70 +++++++++++++++++++++++ 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/ASControlNode.mm b/AsyncDisplayKit/ASControlNode.mm index dd576f5a..4ee96d74 100644 --- a/AsyncDisplayKit/ASControlNode.mm +++ b/AsyncDisplayKit/ASControlNode.mm @@ -277,16 +277,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 +372,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 diff --git a/AsyncDisplayKitTests/ASControlNodeTests.m b/AsyncDisplayKitTests/ASControlNodeTests.m index 969947c3..5b094ef0 100644 --- a/AsyncDisplayKitTests/ASControlNodeTests.m +++ b/AsyncDisplayKitTests/ASControlNodeTests.m @@ -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]; From 25096117e77b6a6682e49ada886ce1114c6a2fe9 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Wed, 6 Apr 2016 16:55:12 -0700 Subject: [PATCH 07/46] Prevent ASInsetLayoutSpec from creating a computed layout when child is nil Reviewers: scottg, schneider, ricky, garrett Reviewed By: garrett Subscribers: garrett, jenkins Differential Revision: https://phabricator.pinadmin.com/D83683 JIRA Issue(s): BRIO-4729 --- AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm b/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm index 960360f8..ce7e6b4e 100644 --- a/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm @@ -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, { From a9b02e86322e0db176675bf45e28d0d050f74a06 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Wed, 6 Apr 2016 21:34:06 -0700 Subject: [PATCH 08/46] [ASDataController] Handle incorrect client code that returns a nil ASCellNode (assert, but use zero-size cell in production). --- AsyncDisplayKit/Details/ASDataController.mm | 33 ++++++++++----------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index e5ddc276..9d8a2c81 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -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 *)nodes fromContexts:(NSArray *)contexts inIndexesOfRange:(NSRange)range ofKind:(NSString *)kind +- (void)_layoutNodes:(NSArray *)nodes fromContexts:(NSArray *)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]; }); } From ccddb36ed9b9b5d5fcd4321fa2fab6591747929c Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Thu, 7 Apr 2016 14:10:34 -0700 Subject: [PATCH 09/46] [ASDisplayNode] Ensure that Visible interfaceState is cleared on removal from hierarchy for rasterized elements. --- AsyncDisplayKit/ASDisplayNode.mm | 33 ++++++++++--------- .../contents.xcworkspacedata | 10 ++++++ 2 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 examples/ASDKgram/Sample.xcworkspace/contents.xcworkspacedata diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index ade79378..f7b24627 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -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); + } + }); + } } } diff --git a/examples/ASDKgram/Sample.xcworkspace/contents.xcworkspacedata b/examples/ASDKgram/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..7b5a2f30 --- /dev/null +++ b/examples/ASDKgram/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + From 2cb6969c79e89d66268712679bc24dce56efe4fe Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Fri, 8 Apr 2016 09:30:52 -0700 Subject: [PATCH 10/46] Use unsigned integers for findNodes vectors Summary: Also refector logic and naming to be simpler and more descriptive Reviewers: schneider, chris, scottg Reviewed By: schneider, chris, scottg Subscribers: chris, jenkins Differential Revision: https://phabricator.pinadmin.com/D83911 --- .../Private/ASDisplayNodeLayoutContext.mm | 55 ++++++++----------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/AsyncDisplayKit/Private/ASDisplayNodeLayoutContext.mm b/AsyncDisplayKit/Private/ASDisplayNodeLayoutContext.mm index 446bdb24..128b0177 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeLayoutContext.mm +++ b/AsyncDisplayKit/Private/ASDisplayNodeLayoutContext.mm @@ -23,8 +23,8 @@ BOOL _calculatedSubnodeOperations; NSArray *_insertedSubnodes; NSArray *_removedSubnodes; - std::vector _insertedSubnodePositions; - std::vector _removedSubnodePositions; + std::vector _insertedSubnodePositions; + std::vector _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,42 +142,31 @@ /** * @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 * __strong *storedNodes, - std::vector *storedPositions - ) +static inline void findNodesInLayoutAtIndexes(ASLayout *layout, + NSIndexSet *indexes, + NSArray * __strong *storedNodes, + std::vector *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 *intersectingNodes, - NSArray * __strong *storedNodes, - std::vector *storedPositions - ) +static inline void findNodesInLayoutAtIndexesWithFilteredNodes(ASLayout *layout, + NSIndexSet *indexes, + NSArray *filteredNodes, + NSArray * __strong *storedNodes, + std::vector *storedPositions) { NSMutableArray *nodes = [NSMutableArray array]; - std::vector positions = std::vector(); - NSInteger idx = [indexes firstIndex]; + std::vector positions = std::vector(); + 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; - } - } - if (!skip) { + if (node != nil && [filteredNodes indexOfObjectIdenticalTo:node] != NSNotFound) { [nodes addObject:node]; positions.push_back(idx); } From 2a8a58ec842820a847a14ceac17d9baf56b9c037 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 8 Apr 2016 11:13:02 -0700 Subject: [PATCH 11/46] Expose the rangeController in ASTableView --- AsyncDisplayKit/ASTableView.mm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index d8d9037d..bf9a3522 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -720,6 +720,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; #pragma mark - ASRangeControllerDataSource +- (ASRangeController *)rangeController +{ + return _rangeController; +} + - (NSArray *)visibleNodeIndexPathsForRangeController:(ASRangeController *)rangeController { ASDisplayNodeAssertMainThread(); From 6f41d28dd0c8daf86462f7807b0dd73409ef3485 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Fri, 8 Apr 2016 15:49:28 -0700 Subject: [PATCH 12/46] Fix issue where zero was returned on idential object array lookup Reviewers: scottg, schneider, garrett Reviewed By: garrett Subscribers: jenkins Differential Revision: https://phabricator.pinadmin.com/D84131 --- AsyncDisplayKit/Private/ASDisplayNodeLayoutContext.mm | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/Private/ASDisplayNodeLayoutContext.mm b/AsyncDisplayKit/Private/ASDisplayNodeLayoutContext.mm index 128b0177..797657ac 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeLayoutContext.mm +++ b/AsyncDisplayKit/Private/ASDisplayNodeLayoutContext.mm @@ -166,9 +166,13 @@ static inline void findNodesInLayoutAtIndexesWithFilteredNodes(ASLayout *layout, while (idx != NSNotFound) { ASDisplayNode *node = (ASDisplayNode *)layout.immediateSublayouts[idx].layoutableObject; ASDisplayNodeCAssert(node, @"A flattened layout must consist exclusively of node sublayouts"); - if (node != nil && [filteredNodes indexOfObjectIdenticalTo:node] != NSNotFound) { - [nodes addObject:node]; - positions.push_back(idx); + // 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); + } } idx = [indexes indexGreaterThanIndex:idx]; } From c25a252e1c62256e40b08d3f1b6d9c730c1515a1 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 4 Apr 2016 20:51:24 -0700 Subject: [PATCH 13/46] Fix fetch call won't occur for content smaller than bounds unless user scrolls --- AsyncDisplayKit/ASCollectionView.mm | 40 ++++++-- AsyncDisplayKit/ASTableView.mm | 108 +++++++++++++++----- AsyncDisplayKit/Details/ASBatchContext.h | 8 ++ AsyncDisplayKit/Details/ASBatchContext.mm | 23 +++-- AsyncDisplayKit/Private/ASBatchFetching.m | 6 +- AsyncDisplayKit/Private/ASInternalHelpers.h | 26 ++++- examples/Kittens/Sample/ViewController.m | 2 +- 7 files changed, 160 insertions(+), 53 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index ae1f5792..784ef0b4 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -703,6 +703,19 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; #pragma mark - #pragma mark Batch Fetching +- (void)_checkForBatchFetching +{ + // Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset: + if ([self isDragging] || [self isTracking] || ![self _shouldBatchFetch]) { + return; + } + + // Check if we should batch fetch + if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollableDirections], self.bounds, self.contentSize, self.contentOffset, _leadingScreensForBatching)) { + [self _beginBatchFetching]; + } +} + - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { _deceleratingVelocity = CGPointMake( @@ -711,7 +724,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; ); if (targetContentOffset != NULL) { - [self handleBatchFetchScrollingToOffset:*targetContentOffset]; + [self _handleBatchFetchScrollingToOffset:*targetContentOffset]; } if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { @@ -738,7 +751,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } -- (BOOL)shouldBatchFetch +- (BOOL)_shouldBatchFetch { // 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,22 +762,27 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } } -- (void)handleBatchFetchScrollingToOffset:(CGPoint)targetOffset +- (void)_handleBatchFetchScrollingToOffset:(CGPoint)targetOffset { ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); - if (![self shouldBatchFetch]) { + if (![self _shouldBatchFetch]) { return; } if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollDirection], self.bounds, self.contentSize, targetOffset, _leadingScreensForBatching)) { - [_batchContext beginBatchFetching]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext]; - }); + [self _beginBatchFetching]; } } +- (void)_beginBatchFetching +{ + [_batchContext beginBatchFetching]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext]; + }); +} + #pragma mark - ASDataControllerSource @@ -975,9 +993,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; }]; } else { [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO]; - [UIView performWithoutAnimation:^{ + ASPerformBlockWithoutAnimationCompletion(YES, ^{ [super insertItemsAtIndexPaths:indexPaths]; - }]; + }, ^{ + + }); } } diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index bf9a3522..9a6e7b77 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -587,18 +587,46 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } else { scrollVelocity = _deceleratingVelocity; } + ASScrollDirection scrollDirection = [self _scrollDirectionForVelocity:scrollVelocity]; return ASScrollDirectionApplyTransform(scrollDirection, self.transform); } -- (ASScrollDirection)_scrollDirectionForVelocity:(CGPoint)velocity +- (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; +} + +- (ASScrollDirection)_scrollDirectionForVelocity:(CGPoint)scrollVelocity { ASScrollDirection direction = ASScrollDirectionNone; - if (velocity.y < 0.0) { - direction = ASScrollDirectionDown; - } else if (velocity.y > 0.0) { - direction = ASScrollDirectionUp; + ASScrollDirection scrollableDirections = [self scrollableDirections]; + + if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { // Can scroll horizontally. + if (scrollVelocity.x < 0.0) { + direction |= ASScrollDirectionRight; + } else if (scrollVelocity.x > 0.0) { + direction |= ASScrollDirectionLeft; + } } + if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { // Can scroll vertically. + if (scrollVelocity.y < 0.0) { + direction |= ASScrollDirectionDown; + } else if (scrollVelocity.y > 0.0) { + direction |= ASScrollDirectionUp; + } + } + return direction; } @@ -631,7 +659,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 +678,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."); @@ -675,6 +703,19 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; #pragma mark - #pragma mark Batch Fetching +- (void)_checkForBatchFetching +{ + // Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset: + if ([self isDragging] || [self isTracking] || ![self _shouldBatchFetch]) { + return; + } + + // Check if we should batch fetch + if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollableDirections], self.bounds, self.contentSize, self.contentOffset, _leadingScreensForBatching)) { + [self _beginBatchFetching]; + } +} + - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { _deceleratingVelocity = CGPointMake( @@ -683,7 +724,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; ); if (targetContentOffset != NULL) { - [self handleBatchFetchScrollingToOffset:*targetContentOffset]; + [self _handleBatchFetchScrollingToOffset:*targetContentOffset]; } if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { @@ -691,7 +732,20 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } -- (BOOL)shouldBatchFetch +- (void)_handleBatchFetchScrollingToOffset:(CGPoint)targetOffset +{ + ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); + + if (![self _shouldBatchFetch]) { + return; + } + + if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollDirection], self.bounds, self.contentSize, targetOffset, _leadingScreensForBatching)) { + [self _beginBatchFetching]; + } +} + +- (BOOL)_shouldBatchFetch { // 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,20 +756,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } -- (void)handleBatchFetchScrollingToOffset:(CGPoint)targetOffset +- (void)_beginBatchFetching { - ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); - - if (![self shouldBatchFetch]) { - return; - } - - if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollDirection], self.bounds, self.contentSize, targetOffset, _leadingScreensForBatching)) { - [_batchContext beginBatchFetching]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext]; - }); - } + [_batchContext beginBatchFetching]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext]; + }); } #pragma mark - ASRangeControllerDataSource @@ -853,10 +899,15 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; if (!self.asyncDataSource) { return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } - + BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimation(preventAnimation, ^{ + ASPerformBlockWithoutAnimationCompletion(preventAnimation, ^{ [super insertRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; + }, ^{ + // Push this to the next runloop to be sure the UITableView has the right content size + dispatch_async(dispatch_get_main_queue(), ^{ + [self _checkForBatchFetching]; + }); }); if (_automaticallyAdjustsContentOffset) { @@ -874,8 +925,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimation(preventAnimation, ^{ + ASPerformBlockWithoutAnimationCompletion(preventAnimation, ^{ [super deleteRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; + }, ^{ + // Push this to the next runloop to be sure the UITableView has the right content size + dispatch_async(dispatch_get_main_queue(), ^{ + [self _checkForBatchFetching]; + }); }); if (_automaticallyAdjustsContentOffset) { @@ -1077,7 +1133,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]]; } } diff --git a/AsyncDisplayKit/Details/ASBatchContext.h b/AsyncDisplayKit/Details/ASBatchContext.h index dc1986a7..aace1fac 100644 --- a/AsyncDisplayKit/Details/ASBatchContext.h +++ b/AsyncDisplayKit/Details/ASBatchContext.h @@ -33,6 +33,14 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)completeBatchFetching:(BOOL)didComplete; +/** + * Let the context object know that a batch fetch was completed. + * + * @discussion For instance, when a table has reached the end of its data, a batch fetch will be attempted unless the context + * object thinks that it is still fetching. + */ +- (void)completeBatchFetching; + /** * Ask the context object if the batch fetching process was cancelled by the context owner. * diff --git a/AsyncDisplayKit/Details/ASBatchContext.mm b/AsyncDisplayKit/Details/ASBatchContext.mm index 4833cbd8..f86449d6 100644 --- a/AsyncDisplayKit/Details/ASBatchContext.mm +++ b/AsyncDisplayKit/Details/ASBatchContext.mm @@ -45,24 +45,31 @@ typedef NS_ENUM(NSInteger, ASBatchContextState) { return _state == ASBatchContextStateCancelled; } -- (void)completeBatchFetching:(BOOL)didComplete -{ - if (didComplete) { - ASDN::MutexLocker l(_propertyLock); - _state = ASBatchContextStateCompleted; - } -} - - (void)beginBatchFetching { ASDN::MutexLocker l(_propertyLock); _state = ASBatchContextStateFetching; } +- (void)completeBatchFetching +{ + ASDN::MutexLocker l(_propertyLock); + _state = ASBatchContextStateCompleted; +} + - (void)cancelBatchFetching { ASDN::MutexLocker l(_propertyLock); _state = ASBatchContextStateCancelled; } +#pragma mark - Deprecated + +- (void)completeBatchFetching:(BOOL)didComplete +{ + if (didComplete) { + [self completeBatchFetching]; + } +} + @end diff --git a/AsyncDisplayKit/Private/ASBatchFetching.m b/AsyncDisplayKit/Private/ASBatchFetching.m index b2a471e2..f25408e3 100644 --- a/AsyncDisplayKit/Private/ASBatchFetching.m +++ b/AsyncDisplayKit/Private/ASBatchFetching.m @@ -20,7 +20,7 @@ BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, } // only Down and Right scrolls are currently supported (tail loading) - if (scrollDirection != ASScrollDirectionDown && scrollDirection != ASScrollDirectionRight) { + if (!ASScrollDirectionContainsDown(scrollDirection) && !ASScrollDirectionContainsRight(scrollDirection)) { return NO; } @@ -31,11 +31,11 @@ BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, 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; diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.h b/AsyncDisplayKit/Private/ASInternalHelpers.h index 79f500e7..8cfc7713 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.h +++ b/AsyncDisplayKit/Private/ASInternalHelpers.h @@ -32,6 +32,26 @@ BOOL ASRunningOnOS7(); ASDISPLAYNODE_EXTERN_C_END +/** + @summary Conditionally performs UIView geometry changes in the given block without animation and call completion block afterwards. + + Used primarily to circumvent UITableView forcing insertion animations when explicitly told not to via + `UITableViewRowAnimationNone`. More info: https://github.com/facebook/AsyncDisplayKit/pull/445 + + @param withoutAnimation Set to `YES` to perform given block without animation + @param block Perform UIView geometry changes within the passed block + @param completion Call completion block if UIView geometry changes within the passed block did complete + */ +ASDISPLAYNODE_INLINE void ASPerformBlockWithoutAnimationCompletion(BOOL withoutAnimation, void (^block)(), void (^completion)()) { + [CATransaction begin]; + [CATransaction setDisableActions:withoutAnimation]; + if (completion != nil) { + [CATransaction setCompletionBlock:completion]; + } + block(); + [CATransaction commit]; +} + /** @summary Conditionally performs UIView geometry changes in the given block without animation. @@ -42,11 +62,7 @@ ASDISPLAYNODE_EXTERN_C_END @param block Perform UIView geometry changes within the passed block */ ASDISPLAYNODE_INLINE void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { - if (withoutAnimation) { - [UIView performWithoutAnimation:block]; - } else { - block(); - } + ASPerformBlockWithoutAnimationCompletion(withoutAnimation, block, nil); } ASDISPLAYNODE_INLINE void ASBoundsAndPositionForFrame(CGRect rect, CGPoint origin, CGPoint anchorPoint, CGRect *bounds, CGPoint *position) diff --git a/examples/Kittens/Sample/ViewController.m b/examples/Kittens/Sample/ViewController.m index 9ec66fa4..699e17fd 100644 --- a/examples/Kittens/Sample/ViewController.m +++ b/examples/Kittens/Sample/ViewController.m @@ -177,7 +177,7 @@ static const NSInteger kMaxLitterSize = 100; // max number of kitten cell [_kittenDataSource addObjectsFromArray:moarKittens]; [tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade]; - [context completeBatchFetching:YES]; + [context completeBatchFetching]; }); } From f92e7d5a29bac51dece1cbf2bd4efeeab8bff2d5 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 8 Apr 2016 14:50:31 -0700 Subject: [PATCH 14/46] Move completeBatchFetching: to deprecated location in header --- AsyncDisplayKit/Details/ASBatchContext.h | 25 +++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/AsyncDisplayKit/Details/ASBatchContext.h b/AsyncDisplayKit/Details/ASBatchContext.h index aace1fac..f65c4060 100644 --- a/AsyncDisplayKit/Details/ASBatchContext.h +++ b/AsyncDisplayKit/Details/ASBatchContext.h @@ -22,17 +22,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (BOOL)isFetching; -/** - * Let the context object know that a batch fetch was completed. - * - * @param didComplete A boolean that states whether or not the batch fetch completed. - * - * @discussion Only by passing YES will the owner of the context know to attempt another batch update when necessary. - * For instance, when a table has reached the end of its data, a batch fetch will be attempted unless the context - * object thinks that it is still fetching. - */ -- (void)completeBatchFetching:(BOOL)didComplete; - /** * Let the context object know that a batch fetch was completed. * @@ -67,6 +56,20 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)beginBatchFetching; + +#pragma mark - Deprecated + +/** + * Let the context object know that a batch fetch was completed. + * + * @param didComplete A boolean that states whether or not the batch fetch completed. + * + * @discussion Only by passing YES will the owner of the context know to attempt another batch update when necessary. + * For instance, when a table has reached the end of its data, a batch fetch will be attempted unless the context + * object thinks that it is still fetching. + */ +- (void)completeBatchFetching:(BOOL)didComplete; + @end NS_ASSUME_NONNULL_END From ab8928c140946c86eca4119b9fbb26c67866164f Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 8 Apr 2016 14:56:09 -0700 Subject: [PATCH 15/46] Consolidate methods for batch fetching in ASTableView --- AsyncDisplayKit/ASTableView.mm | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 9a6e7b77..3fa56073 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -700,20 +700,16 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } -#pragma mark - -#pragma mark Batch Fetching +#pragma mark - Batch Fetching - (void)_checkForBatchFetching { // Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset: - if ([self isDragging] || [self isTracking] || ![self _shouldBatchFetch]) { + if ([self isDragging] || [self isTracking]) { return; } - // Check if we should batch fetch - if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollableDirections], self.bounds, self.contentSize, self.contentOffset, _leadingScreensForBatching)) { - [self _beginBatchFetching]; - } + [self _beginBatchFetchingIfNeededForScrollDirection:[self scrollableDirections] contentOffset:self.contentOffset]; } - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset @@ -724,7 +720,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; ); if (targetContentOffset != NULL) { - [self _handleBatchFetchScrollingToOffset:*targetContentOffset]; + ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); + [self _beginBatchFetchingIfNeededForScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset]; } if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { @@ -732,15 +729,14 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } -- (void)_handleBatchFetchScrollingToOffset:(CGPoint)targetOffset +- (void)_beginBatchFetchingIfNeededForScrollDirection:(ASScrollDirection)scrollDirection contentOffset:(CGPoint)contentOffset { - ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); - if (![self _shouldBatchFetch]) { return; } - - if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollDirection], self.bounds, self.contentSize, targetOffset, _leadingScreensForBatching)) { + + // Check if we should batch fetch + if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollableDirections], self.bounds, self.contentSize, self.contentOffset, _leadingScreensForBatching)) { [self _beginBatchFetching]; } } From 40fe1f3ac738db1750f44eebdc81bec6b790f861 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 8 Apr 2016 14:56:55 -0700 Subject: [PATCH 16/46] Schedule _checkForBatchFetching call in animation block --- AsyncDisplayKit/ASTableView.mm | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 3fa56073..18004ae8 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -895,11 +895,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; if (!self.asyncDataSource) { return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } - + BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimationCompletion(preventAnimation, ^{ + + ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super insertRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; - }, ^{ // Push this to the next runloop to be sure the UITableView has the right content size dispatch_async(dispatch_get_main_queue(), ^{ [self _checkForBatchFetching]; @@ -921,9 +921,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimationCompletion(preventAnimation, ^{ + ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super deleteRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; - }, ^{ // Push this to the next runloop to be sure the UITableView has the right content size dispatch_async(dispatch_get_main_queue(), ^{ [self _checkForBatchFetching]; @@ -948,6 +947,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super insertSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; + // Push this to the next runloop to be sure the UITableView has the right content size + dispatch_async(dispatch_get_main_queue(), ^{ + [self _checkForBatchFetching]; + }); }); } @@ -963,6 +966,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super deleteSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; + // Push this to the next runloop to be sure the UITableView has the right content size + dispatch_async(dispatch_get_main_queue(), ^{ + [self _checkForBatchFetching]; + }); }); } From 24ca09ee6caf0595fb701c007f700462dba3d1b5 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 8 Apr 2016 19:42:14 -0700 Subject: [PATCH 17/46] Move most of the batch fetching logic to a central place for ASTableView and ASCollectionView usage --- AsyncDisplayKit/ASCollectionView.mm | 142 +++++++------- AsyncDisplayKit/ASTableView.mm | 219 +++++++++++----------- AsyncDisplayKit/Private/ASBatchFetching.h | 27 ++- AsyncDisplayKit/Private/ASBatchFetching.m | 30 +-- 4 files changed, 219 insertions(+), 199 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 784ef0b4..13e7c743 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -91,7 +91,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; #pragma mark - #pragma mark ASCollectionView. -@interface ASCollectionView () { +@interface ASCollectionView () { 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,58 +737,14 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } -#pragma mark - -#pragma mark Batch Fetching +#pragma mark - Batch Fetching -- (void)_checkForBatchFetching +- (ASBatchContext *)batchContext { - // Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset: - if ([self isDragging] || [self isTracking] || ![self _shouldBatchFetch]) { - return; - } - - // Check if we should batch fetch - if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollableDirections], self.bounds, self.contentSize, self.contentOffset, _leadingScreensForBatching)) { - [self _beginBatchFetching]; - } + return _batchContext; } -- (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) { - [self _handleBatchFetchScrollingToOffset:*targetContentOffset]; - } - - if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { - [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; - } -} - -- (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:)]; @@ -762,25 +755,41 @@ 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 UITableView 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)) { + [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollableDirections] contentOffset:self.contentOffset]; +} + +- (void)_beginBatchFetchingIfNeededWithScrollView:(UIScrollView *)scrollView forScrollDirection:(ASScrollDirection)scrollDirection contentOffset:(CGPoint)contentOffset +{ + if (ASDisplayShouldFetchBatchForScrollView(self, scrollDirection, contentOffset)) { [self _beginBatchFetching]; } } - (void)_beginBatchFetching { + NSLog(@"begin batch fetching"); + [_batchContext beginBatchFetching]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext]; - }); + if ([_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext]; + }); + } } @@ -972,7 +981,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]; @@ -993,11 +1005,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; }]; } else { [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO]; - ASPerformBlockWithoutAnimationCompletion(YES, ^{ + [UIView performWithoutAnimation:^{ [super insertItemsAtIndexPaths:indexPaths]; - }, ^{ - - }); + [self _scheduleCheckForBatchFetching]; + }]; } } @@ -1017,6 +1028,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:indexPaths batched:NO]; [UIView performWithoutAnimation:^{ [super deleteItemsAtIndexPaths:indexPaths]; + [self _scheduleCheckForBatchFetching]; }]; } } @@ -1037,6 +1049,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO]; [UIView performWithoutAnimation:^{ [super insertSections:indexSet]; + [self _scheduleCheckForBatchFetching]; }]; } } @@ -1057,6 +1070,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:indexSet batched:NO]; [UIView performWithoutAnimation:^{ [super deleteSections:indexSet]; + [self _scheduleCheckForBatchFetching]; }]; } } diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 18004ae8..9be26beb 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -88,9 +88,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (instancetype)_initWithTableView:(ASTableView *)tableView; @end -@interface ASTableView () +@interface ASTableView () { 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,75 +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)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; -} - -- (ASScrollDirection)_scrollDirectionForVelocity:(CGPoint)scrollVelocity -{ - ASScrollDirection direction = ASScrollDirectionNone; - ASScrollDirection scrollableDirections = [self scrollableDirections]; - - if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { // Can scroll horizontally. - if (scrollVelocity.x < 0.0) { - direction |= ASScrollDirectionRight; - } else if (scrollVelocity.x > 0.0) { - direction |= ASScrollDirectionLeft; - } - } - if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { // Can scroll vertically. - if (scrollVelocity.y < 0.0) { - direction |= ASScrollDirectionDown; - } else if (scrollVelocity.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; @@ -700,16 +629,22 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } -#pragma mark - Batch Fetching - -- (void)_checkForBatchFetching +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { - // Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset: - if ([self isDragging] || [self isTracking]) { - return; + // If a scroll happenes the current range mode needs to go to full + ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; + if (ASInterfaceStateIncludesVisible(interfaceState)) { + [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; } - [self _beginBatchFetchingIfNeededForScrollDirection:[self scrollableDirections] contentOffset:self.contentOffset]; + 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 @@ -721,7 +656,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; if (targetContentOffset != NULL) { ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); - [self _beginBatchFetchingIfNeededForScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset]; + [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollDirection] contentOffset:*targetContentOffset]; } if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { @@ -729,19 +664,69 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } -- (void)_beginBatchFetchingIfNeededForScrollDirection:(ASScrollDirection)scrollDirection contentOffset:(CGPoint)contentOffset + +#pragma mark - Scroll Direction + +- (ASScrollDirection)scrollDirection { - if (![self _shouldBatchFetch]) { - return; + CGPoint scrollVelocity; + if (self.isTracking) { + scrollVelocity = [self.panGestureRecognizer velocityInView:self.superview]; + } else { + scrollVelocity = _deceleratingVelocity; } - // Check if we should batch fetch - if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollableDirections], self.bounds, self.contentSize, self.contentOffset, _leadingScreensForBatching)) { - [self _beginBatchFetching]; - } + ASScrollDirection scrollDirection = [self _scrollDirectionForVelocity:scrollVelocity]; + return ASScrollDirectionApplyTransform(scrollDirection, self.transform); } -- (BOOL)_shouldBatchFetch +- (ASScrollDirection)_scrollDirectionForVelocity:(CGPoint)scrollVelocity +{ + ASScrollDirection direction = ASScrollDirectionNone; + ASScrollDirection scrollableDirections = [self scrollableDirections]; + + if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { // Can scroll horizontally. + if (scrollVelocity.x < 0.0) { + direction |= ASScrollDirectionRight; + } else if (scrollVelocity.x > 0.0) { + direction |= ASScrollDirectionLeft; + } + } + 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:)]; @@ -752,12 +737,39 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } +- (void)_scheduleCheckForBatchFetching +{ + // Push this to the next runloop to be sure the UITableView 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; + } + + [self _beginBatchFetchingIfNeededWithScrollView:self forScrollDirection:[self scrollableDirections] contentOffset:self.contentOffset]; +} + +- (void)_beginBatchFetchingIfNeededWithScrollView:(UIScrollView *)scrollView forScrollDirection:(ASScrollDirection)scrollDirection contentOffset:(CGPoint)contentOffset +{ + if (ASDisplayShouldFetchBatchForScrollView(self, scrollDirection, contentOffset)) { + [self _beginBatchFetching]; + } +} + - (void)_beginBatchFetching { [_batchContext beginBatchFetching]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext]; - }); + if ([_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext]; + }); + } } #pragma mark - ASRangeControllerDataSource @@ -897,13 +909,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super insertRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; - // Push this to the next runloop to be sure the UITableView has the right content size - dispatch_async(dispatch_get_main_queue(), ^{ - [self _checkForBatchFetching]; - }); + [self _scheduleCheckForBatchFetching]; }); if (_automaticallyAdjustsContentOffset) { @@ -923,10 +931,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super deleteRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOptions]; - // Push this to the next runloop to be sure the UITableView has the right content size - dispatch_async(dispatch_get_main_queue(), ^{ - [self _checkForBatchFetching]; - }); + [self _scheduleCheckForBatchFetching]; }); if (_automaticallyAdjustsContentOffset) { @@ -947,10 +952,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super insertSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; - // Push this to the next runloop to be sure the UITableView has the right content size - dispatch_async(dispatch_get_main_queue(), ^{ - [self _checkForBatchFetching]; - }); + [self _scheduleCheckForBatchFetching]; }); } @@ -966,10 +968,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super deleteSections:indexSet withRowAnimation:(UITableViewRowAnimation)animationOptions]; - // Push this to the next runloop to be sure the UITableView has the right content size - dispatch_async(dispatch_get_main_queue(), ^{ - [self _checkForBatchFetching]; - }); + [self _scheduleCheckForBatchFetching]; }); } diff --git a/AsyncDisplayKit/Private/ASBatchFetching.h b/AsyncDisplayKit/Private/ASBatchFetching.h index 9aeea5ad..6b0c1be5 100644 --- a/AsyncDisplayKit/Private/ASBatchFetching.h +++ b/AsyncDisplayKit/Private/ASBatchFetching.h @@ -10,27 +10,26 @@ #import "ASBatchContext.h" #import "ASScrollDirection.h" -#import "ASBaseDefines.h" ASDISPLAYNODE_EXTERN_C_BEGIN +@protocol ASBatchFetchingScrollView + +- (BOOL)canBatchFetch; +- (ASBatchContext *)batchContext; +- (CGFloat)leadingScreensForBatching; + +@end + /** @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. - @param scrollDirection The current scrolling direction of the scroll view. - @param bounds The bounds of the scrollview. - @param contentSize The content size of the scrollview. - @param targetOffset The offset that the scrollview will scroll to. - @param leadingScreens How many screens in the remaining distance will trigger batch fetching. - @return Whether or not the current state should proceed with batch fetching. @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. */ -extern BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, - ASScrollDirection scrollDirection, - CGRect bounds, - CGSize contentSize, - CGPoint targetOffset, - CGFloat leadingScreens); +BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView *scrollView, ASScrollDirection scrollDirection, CGPoint contentOffset); ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Private/ASBatchFetching.m b/AsyncDisplayKit/Private/ASBatchFetching.m index f25408e3..9158c30b 100644 --- a/AsyncDisplayKit/Private/ASBatchFetching.m +++ b/AsyncDisplayKit/Private/ASBatchFetching.m @@ -8,23 +8,31 @@ #import "ASBatchFetching.h" -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 +BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView *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; + + // 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) + // 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; } @@ -33,11 +41,11 @@ BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, if (ASScrollDirectionContainsDown(scrollDirection)) { viewLength = bounds.size.height; - offset = targetOffset.y; + offset = contentOffset.y; contentLength = contentSize.height; } else { // horizontal / right viewLength = bounds.size.width; - offset = targetOffset.x; + offset = contentOffset.x; contentLength = contentSize.width; } From bb110ca6e8c1ba1fb2fd42e2dbe33902868e6305 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 8 Apr 2016 20:02:20 -0700 Subject: [PATCH 18/46] Readd ASDisplayShouldFetchBatchForContext --- AsyncDisplayKit/Private/ASBatchFetching.h | 20 ++++++++++++++++++++ AsyncDisplayKit/Private/ASBatchFetching.m | 16 ++++++++++++---- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/AsyncDisplayKit/Private/ASBatchFetching.h b/AsyncDisplayKit/Private/ASBatchFetching.h index 6b0c1be5..094a2c6c 100644 --- a/AsyncDisplayKit/Private/ASBatchFetching.h +++ b/AsyncDisplayKit/Private/ASBatchFetching.h @@ -32,4 +32,24 @@ ASDISPLAYNODE_EXTERN_C_BEGIN */ BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView *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. + @param scrollDirection The current scrolling direction of the scroll view. + @param bounds The bounds of the scrollview. + @param contentSize The content size of the scrollview. + @param targetOffset The offset that the scrollview will scroll to. + @param leadingScreens How many screens in the remaining distance will trigger batch fetching. + @return Whether or not the current state should proceed with batch fetching. + @discussion This method is broken into a category for unit testing purposes and should be used with the ASTableView and + * ASCollectionView batch fetching API. + */ +extern BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, + ASScrollDirection scrollDirection, + CGRect bounds, + CGSize contentSize, + CGPoint targetOffset, + CGFloat leadingScreens); + ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Private/ASBatchFetching.m b/AsyncDisplayKit/Private/ASBatchFetching.m index 9158c30b..6a8d9fc1 100644 --- a/AsyncDisplayKit/Private/ASBatchFetching.m +++ b/AsyncDisplayKit/Private/ASBatchFetching.m @@ -10,7 +10,6 @@ BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView *scrollView, ASScrollDirection scrollDirection, CGPoint contentOffset) { - // Don't fetch if the scroll view does not allow if (![scrollView canBatchFetch]) { return NO; @@ -21,7 +20,16 @@ BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView Date: Sat, 9 Apr 2016 10:53:44 -0700 Subject: [PATCH 19/46] Remove deprecation of completeBatchFetching: --- AsyncDisplayKit/Details/ASBatchContext.h | 21 +++++---------------- AsyncDisplayKit/Details/ASBatchContext.mm | 17 +++++------------ examples/Kittens/Sample/ViewController.m | 2 +- 3 files changed, 11 insertions(+), 29 deletions(-) diff --git a/AsyncDisplayKit/Details/ASBatchContext.h b/AsyncDisplayKit/Details/ASBatchContext.h index f65c4060..dc1986a7 100644 --- a/AsyncDisplayKit/Details/ASBatchContext.h +++ b/AsyncDisplayKit/Details/ASBatchContext.h @@ -25,10 +25,13 @@ NS_ASSUME_NONNULL_BEGIN /** * Let the context object know that a batch fetch was completed. * - * @discussion For instance, when a table has reached the end of its data, a batch fetch will be attempted unless the context + * @param didComplete A boolean that states whether or not the batch fetch completed. + * + * @discussion Only by passing YES will the owner of the context know to attempt another batch update when necessary. + * For instance, when a table has reached the end of its data, a batch fetch will be attempted unless the context * object thinks that it is still fetching. */ -- (void)completeBatchFetching; +- (void)completeBatchFetching:(BOOL)didComplete; /** * Ask the context object if the batch fetching process was cancelled by the context owner. @@ -56,20 +59,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)beginBatchFetching; - -#pragma mark - Deprecated - -/** - * Let the context object know that a batch fetch was completed. - * - * @param didComplete A boolean that states whether or not the batch fetch completed. - * - * @discussion Only by passing YES will the owner of the context know to attempt another batch update when necessary. - * For instance, when a table has reached the end of its data, a batch fetch will be attempted unless the context - * object thinks that it is still fetching. - */ -- (void)completeBatchFetching:(BOOL)didComplete; - @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASBatchContext.mm b/AsyncDisplayKit/Details/ASBatchContext.mm index f86449d6..4cb5b96f 100644 --- a/AsyncDisplayKit/Details/ASBatchContext.mm +++ b/AsyncDisplayKit/Details/ASBatchContext.mm @@ -51,10 +51,12 @@ typedef NS_ENUM(NSInteger, ASBatchContextState) { _state = ASBatchContextStateFetching; } -- (void)completeBatchFetching +- (void)completeBatchFetching:(BOOL)didComplete { - ASDN::MutexLocker l(_propertyLock); - _state = ASBatchContextStateCompleted; + if (didComplete) { + ASDN::MutexLocker l(_propertyLock); + _state = ASBatchContextStateCompleted; + } } - (void)cancelBatchFetching @@ -63,13 +65,4 @@ typedef NS_ENUM(NSInteger, ASBatchContextState) { _state = ASBatchContextStateCancelled; } -#pragma mark - Deprecated - -- (void)completeBatchFetching:(BOOL)didComplete -{ - if (didComplete) { - [self completeBatchFetching]; - } -} - @end diff --git a/examples/Kittens/Sample/ViewController.m b/examples/Kittens/Sample/ViewController.m index 699e17fd..9ec66fa4 100644 --- a/examples/Kittens/Sample/ViewController.m +++ b/examples/Kittens/Sample/ViewController.m @@ -177,7 +177,7 @@ static const NSInteger kMaxLitterSize = 100; // max number of kitten cell [_kittenDataSource addObjectsFromArray:moarKittens]; [tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade]; - [context completeBatchFetching]; + [context completeBatchFetching:YES]; }); } From c19c2da2eef1fa75eca1be14a8230de382d36419 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sat, 9 Apr 2016 10:54:07 -0700 Subject: [PATCH 20/46] Remove horizontal scrolling behavior detection for ASTableView --- AsyncDisplayKit/ASTableView.mm | 7 ------- 1 file changed, 7 deletions(-) diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 9be26beb..ac079a1f 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -685,13 +685,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; ASScrollDirection direction = ASScrollDirectionNone; ASScrollDirection scrollableDirections = [self scrollableDirections]; - if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { // Can scroll horizontally. - if (scrollVelocity.x < 0.0) { - direction |= ASScrollDirectionRight; - } else if (scrollVelocity.x > 0.0) { - direction |= ASScrollDirectionLeft; - } - } if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { // Can scroll vertically. if (scrollVelocity.y < 0.0) { direction |= ASScrollDirectionDown; From be26f0c2e549429a2c06e77fca3083bfe8ad739b Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sat, 9 Apr 2016 10:54:45 -0700 Subject: [PATCH 21/46] Revert back to use UIKit version to prevent animations --- AsyncDisplayKit/Private/ASInternalHelpers.h | 26 ++++----------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/AsyncDisplayKit/Private/ASInternalHelpers.h b/AsyncDisplayKit/Private/ASInternalHelpers.h index 8cfc7713..79f500e7 100644 --- a/AsyncDisplayKit/Private/ASInternalHelpers.h +++ b/AsyncDisplayKit/Private/ASInternalHelpers.h @@ -32,26 +32,6 @@ BOOL ASRunningOnOS7(); ASDISPLAYNODE_EXTERN_C_END -/** - @summary Conditionally performs UIView geometry changes in the given block without animation and call completion block afterwards. - - Used primarily to circumvent UITableView forcing insertion animations when explicitly told not to via - `UITableViewRowAnimationNone`. More info: https://github.com/facebook/AsyncDisplayKit/pull/445 - - @param withoutAnimation Set to `YES` to perform given block without animation - @param block Perform UIView geometry changes within the passed block - @param completion Call completion block if UIView geometry changes within the passed block did complete - */ -ASDISPLAYNODE_INLINE void ASPerformBlockWithoutAnimationCompletion(BOOL withoutAnimation, void (^block)(), void (^completion)()) { - [CATransaction begin]; - [CATransaction setDisableActions:withoutAnimation]; - if (completion != nil) { - [CATransaction setCompletionBlock:completion]; - } - block(); - [CATransaction commit]; -} - /** @summary Conditionally performs UIView geometry changes in the given block without animation. @@ -62,7 +42,11 @@ ASDISPLAYNODE_INLINE void ASPerformBlockWithoutAnimationCompletion(BOOL withoutA @param block Perform UIView geometry changes within the passed block */ ASDISPLAYNODE_INLINE void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { - ASPerformBlockWithoutAnimationCompletion(withoutAnimation, block, nil); + if (withoutAnimation) { + [UIView performWithoutAnimation:block]; + } else { + block(); + } } ASDISPLAYNODE_INLINE void ASBoundsAndPositionForFrame(CGRect rect, CGPoint origin, CGPoint anchorPoint, CGRect *bounds, CGPoint *position) From e9fe92444f141337a04c412160661cd876371d4e Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sat, 9 Apr 2016 10:55:04 -0700 Subject: [PATCH 22/46] Small comment fix --- AsyncDisplayKit/ASCollectionView.mm | 2 +- AsyncDisplayKit/ASTableView.mm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 13e7c743..26dfc15c 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -757,7 +757,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)_scheduleCheckForBatchFetching { - // Push this to the next runloop to be sure the UITableView has the right content size + // 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]; }); diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index ac079a1f..1a372084 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -732,7 +732,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)_scheduleCheckForBatchFetching { - // Push this to the next runloop to be sure the UITableView has the right content size + // 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]; }); From 9c29d0efa82063eb0c5b23e996f4d2776051d775 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 4 Apr 2016 20:06:10 -0700 Subject: [PATCH 23/46] Initial commit for improved accessibility support --- AsyncDisplayKit/Details/_ASDisplayView.mm | 79 +++++++++++++++++++ .../Private/ASDisplayNode+UIViewBridge.mm | 6 ++ 2 files changed, 85 insertions(+) diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index f4d95b99..3c869ac1 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -31,6 +31,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; @@ -375,3 +376,81 @@ } #endif @end + + +#pragma mark - Accessibility + +static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; + +@implementation UIAccessibilityElement (_ASDisplayView) + +- (void)setAsyncdisplaykit_node:(ASDisplayNode *)node +{ + objc_setAssociatedObject(self, ASDisplayNodeAssociatedNodeKey, node, OBJC_ASSOCIATION_ASSIGN); // Weak reference to avoid cycle, since the node retains the layer. + + // Update UIAccessibilityElement properties from node + self.accessibilityLabel = node.accessibilityLabel; + self.accessibilityHint = node.accessibilityHint; + self.accessibilityValue = node.accessibilityValue; + self.accessibilityTraits = node.accessibilityTraits; +} + +- (ASDisplayNode *)asyncdisplaykit_node +{ + return objc_getAssociatedObject(self, ASDisplayNodeAssociatedNodeKey); +} + +@end + +@implementation _ASDisplayView (UIAccessibilityContainer) + +#pragma mark - UIAccessibility + +- (NSArray *)accessibleElements +{ + ASDisplayNode *asyncDisplayKitNode = self.asyncdisplaykit_node; + if ( _accessibleElements != nil ) { + return _accessibleElements; + } + + _accessibleElements = [[NSMutableArray alloc] init]; + + // Create UI accessiblity elements for each subnode that represent the subnode within the accessibility container + for (ASDisplayNode *subnode in asyncDisplayKitNode.subnodes) { + if (subnode.isAccessibilityElement || [subnode accessibilityElementCount] > 0) { + UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self]; + accessibilityElement.asyncdisplaykit_node = subnode; + [_accessibleElements addObject:accessibilityElement]; + } + } + + return _accessibleElements; +} + +- (NSInteger)accessibilityElementCount +{ + return [self accessibleElements].count; +} + +- (id)accessibilityElementAtIndex:(NSInteger)index +{ + UIAccessibilityElement *element = [self accessibleElements][index]; + ASDisplayNode *subnode = element.asyncdisplaykit_node; + if (subnode == nil) { + return nil; + } + + // We have to update the accessiblity frame in accessibilityElementAtIndex: as the accessibility frame is in screen + // coordinates and between creating the accessibilityElement and returning it in accessibilityElementAtIndex: + // the frame can change + element.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(subnode.frame, self); + + return element; +} + +- (NSInteger)indexOfAccessibilityElement:(id)element +{ + return [self.accessibleElements indexOfObject:element]; +} + +@end diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index bf0b9f20..ee672262 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -841,6 +841,12 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo _setToViewOnly(accessibilityIdentifier, accessibilityIdentifier); } +- (NSInteger)accessibilityElementCount +{ + _bridge_prologue_read; + return _getFromViewOnly(accessibilityElementCount); +} + @end From 03a7c55d59da519e050775a5191a5aa3171adaf9 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Wed, 6 Apr 2016 16:06:08 -0700 Subject: [PATCH 24/46] Add accessibility support for shouldRasterizeDescendants and layerBacked nodese --- AsyncDisplayKit/Details/_ASDisplayView.mm | 80 ++++++++++++++++++++--- 1 file changed, 71 insertions(+), 9 deletions(-) diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index 3c869ac1..556169f4 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -18,6 +18,8 @@ #import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+Subclasses.h" +#import + @interface _ASDisplayView () @property (nonatomic, assign, readwrite) ASDisplayNode *asyncdisplaykit_node; @@ -380,6 +382,22 @@ #pragma mark - Accessibility +static BOOL ASNodeValidForAccessibility(ASDisplayNode *node) +{ + if (node.isAccessibilityElement) { + return YES; + } + + if (node.isLayerBacked) { + // Assume for now that all nodes that have an accessibility label or + return node.accessibilityLabel.length > 0 || + node.accessibilityValue.length > 0 || + ((node.accessibilityTraits & UIAccessibilityTraitNone) != UIAccessibilityTraitNone); + } + + return YES; +} + static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; @implementation UIAccessibilityElement (_ASDisplayView) @@ -408,16 +426,50 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; - (NSArray *)accessibleElements { - ASDisplayNode *asyncDisplayKitNode = self.asyncdisplaykit_node; if ( _accessibleElements != nil ) { return _accessibleElements; } _accessibleElements = [[NSMutableArray alloc] init]; + + ASDisplayNode *selfNode = self.asyncdisplaykit_node; - // Create UI accessiblity elements for each subnode that represent the subnode within the accessibility container - for (ASDisplayNode *subnode in asyncDisplayKitNode.subnodes) { - if (subnode.isAccessibilityElement || [subnode accessibilityElementCount] > 0) { + // Handle rasterize case + if (selfNode.shouldRasterizeDescendants) { + // In this case we have to go through the whole subnodes tree in BFS fashion and create all + // accessibility elements ourselves as the view hierarchy is flattened + + // Queue used to keep track of subnodes while traversing this layout in a BFS fashion. + std::queue queue; + queue.push(selfNode); + + while (!queue.empty()) { + ASDisplayNode *node = queue.front(); + queue.pop(); + + // Check if we have to add the node to the accessiblity nodes as it's an accessiblity element + if (node != selfNode && ASNodeValidForAccessibility(node)) { + UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self]; + accessibilityElement.asyncdisplaykit_node = node; + [_accessibleElements addObject:accessibilityElement]; + } + + // Add all subnodes to process in next step + for (int i = 0; i < node.subnodes.count; i++) + queue.push(node.subnodes[i]); + } + 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) { + // Check if this subnode is a UIAccessibilityContainer + if (!subnode.isAccessibilityElement && [subnode accessibilityElementCount] > 0) { + // We are good and the view is an UIAccessibilityContainer so add that + [_accessibleElements addObject:subnode.view]; + } else if (ASNodeValidForAccessibility(subnode)) { + // Create a accessiblity element from the subnode UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self]; accessibilityElement.asyncdisplaykit_node = subnode; [_accessibleElements addObject:accessibilityElement]; @@ -434,18 +486,28 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; - (id)accessibilityElementAtIndex:(NSInteger)index { - UIAccessibilityElement *element = [self accessibleElements][index]; - ASDisplayNode *subnode = element.asyncdisplaykit_node; - if (subnode == nil) { + UIAccessibilityElement *accessibilityElement = [[self accessibleElements] objectAtIndex:index]; + ASDisplayNode *accessibilityElementNode = accessibilityElement.asyncdisplaykit_node; + if (accessibilityElementNode == nil) { return nil; } // We have to update the accessiblity frame in accessibilityElementAtIndex: as the accessibility frame is in screen // coordinates and between creating the accessibilityElement and returning it in accessibilityElementAtIndex: // the frame can change - element.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(subnode.frame, self); - return element; + // Handle if node is rasterized + ASDisplayNode *selfNode = self.asyncdisplaykit_node; + if (selfNode.shouldRasterizeDescendants) { + // We need to convert the accessibilityElementNode frame into the coordinate system of the selfNode + CGRect frame = [selfNode convertRect:accessibilityElementNode.bounds fromNode:accessibilityElementNode]; + accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(frame, self); + return accessibilityElement; + } + + // Handle non rasterized case + accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibilityElementNode.frame, self); + return accessibilityElement; } - (NSInteger)indexOfAccessibilityElement:(id)element From 4056bf9ef3d68ebf0b43dccd9d5a0ba162979160 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Wed, 6 Apr 2016 17:41:41 -0700 Subject: [PATCH 25/46] Return NO if node i not valid for accessibility --- AsyncDisplayKit/Details/_ASDisplayView.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index 556169f4..816938e2 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -395,7 +395,7 @@ static BOOL ASNodeValidForAccessibility(ASDisplayNode *node) ((node.accessibilityTraits & UIAccessibilityTraitNone) != UIAccessibilityTraitNone); } - return YES; + return NO; } static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; From ef31c3abf0dc6c99d58c3ea7c3b263b42cd40e2d Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 7 Apr 2016 10:20:46 -0700 Subject: [PATCH 26/46] Copy accessibilityIdentifier to UIAccessibilityElement if a node is assigned --- AsyncDisplayKit/Details/_ASDisplayView.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index 816938e2..2547ee09 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -407,6 +407,7 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; objc_setAssociatedObject(self, ASDisplayNodeAssociatedNodeKey, node, OBJC_ASSOCIATION_ASSIGN); // Weak reference to avoid cycle, since the node retains the layer. // Update UIAccessibilityElement properties from node + self.accessibilityIdentifier = node.accessibilityIdentifier; self.accessibilityLabel = node.accessibilityLabel; self.accessibilityHint = node.accessibilityHint; self.accessibilityValue = node.accessibilityValue; From f3303c1fb8d176a25bfcc914f18292df81b0b27a Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 7 Apr 2016 10:45:14 -0700 Subject: [PATCH 27/46] Add properties for accessibilty to ASDisplayNode to support layer backed nodes --- AsyncDisplayKit/ASDisplayNode.h | 4 + .../Private/ASDisplayNode+UIViewBridge.mm | 74 +++++++++++++------ .../Private/ASDisplayNodeInternal.h | 13 ++++ 3 files changed, 67 insertions(+), 24 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 83ff98bc..0318ab58 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -695,6 +695,10 @@ 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; diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index ee672262..9c3b692d 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -709,147 +709,173 @@ 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); } - (NSInteger)accessibilityElementCount { - _bridge_prologue_read; - return _getFromViewOnly(accessibilityElementCount); + _bridge_prologue_read; + return _getFromViewOnly(accessibilityElementCount); } @end +#pragma mark - ASAsyncTransactionContainer + @implementation ASDisplayNode (ASAsyncTransactionContainer) - (BOOL)asyncdisplaykit_isAsyncTransactionContainer diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index e8930b98..64128808 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -129,6 +129,19 @@ 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; + #if TIME_DISPLAYNODE_OPS @public NSTimeInterval _debugTimeToCreateView; From 34df512eeb577d5b2320eae937cd7f8ca45ec22f Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 7 Apr 2016 10:55:04 -0700 Subject: [PATCH 28/46] Remove ASNodeValidForAccessibility --- AsyncDisplayKit/Details/_ASDisplayView.mm | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index 2547ee09..fc2dff6c 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -382,22 +382,6 @@ #pragma mark - Accessibility -static BOOL ASNodeValidForAccessibility(ASDisplayNode *node) -{ - if (node.isAccessibilityElement) { - return YES; - } - - if (node.isLayerBacked) { - // Assume for now that all nodes that have an accessibility label or - return node.accessibilityLabel.length > 0 || - node.accessibilityValue.length > 0 || - ((node.accessibilityTraits & UIAccessibilityTraitNone) != UIAccessibilityTraitNone); - } - - return NO; -} - static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; @implementation UIAccessibilityElement (_ASDisplayView) @@ -449,7 +433,7 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; queue.pop(); // Check if we have to add the node to the accessiblity nodes as it's an accessiblity element - if (node != selfNode && ASNodeValidForAccessibility(node)) { + if (node != selfNode && node.isAccessibilityElement) { UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self]; accessibilityElement.asyncdisplaykit_node = node; [_accessibleElements addObject:accessibilityElement]; @@ -469,7 +453,7 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; if (!subnode.isAccessibilityElement && [subnode accessibilityElementCount] > 0) { // We are good and the view is an UIAccessibilityContainer so add that [_accessibleElements addObject:subnode.view]; - } else if (ASNodeValidForAccessibility(subnode)) { + } else if (subnode.isAccessibilityElement) { // Create a accessiblity element from the subnode UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self]; accessibilityElement.asyncdisplaykit_node = subnode; From b284d3f6064e742e23eae6db76e4456456acf14b Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 7 Apr 2016 12:02:58 -0700 Subject: [PATCH 29/46] Enable tests for accessibility properties for layer backed nodes --- AsyncDisplayKitTests/ASDisplayNodeTests.m | 66 +++++++++++------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index 3c2fb4c6..4b3a12c5 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -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,21 @@ 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); 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 +457,22 @@ 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; 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; } }]; From 28c4fede57e8b11421c103fc36e76ed55fc7f4cf Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 7 Apr 2016 15:00:47 -0700 Subject: [PATCH 30/46] Add support for newly added a11y properties in iOS 8 / 9 and tvOS New a11y properties: - accessibilityNavigationStyle - accessibilityHeaderElements - accessibilityActivationPoint - accessibilityPath --- AsyncDisplayKit/ASDisplayNode.h | 7 ++ .../Private/ASDisplayNode+UIViewBridge.mm | 50 ++++++++++ .../Private/ASDisplayNodeInternal.h | 4 + AsyncDisplayKit/Private/_ASPendingState.mm | 91 ++++++++++++++++++- AsyncDisplayKitTests/ASDisplayNodeTests.m | 7 ++ 5 files changed, 158 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 0318ab58..bef4fc5b 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -711,6 +711,13 @@ NS_ASSUME_NONNULL_END @property (atomic, assign) BOOL accessibilityViewIsModal; @property (atomic, assign) BOOL shouldGroupAccessibilityChildren; +@property (nonatomic) UIAccessibilityNavigationStyle accessibilityNavigationStyle NS_AVAILABLE_IOS(8_0); +#if TARGET_OS_TV +@property(nullable, nonatomic, copy) NSArray *accessibilityHeaderElements; +#endif +@property (nonatomic) CGPoint accessibilityActivationPoint; +@property (nullable, nonatomic, copy) UIBezierPath *accessibilityPath; + // Accessibility identification support @property (nullable, nonatomic, copy) NSString *accessibilityIdentifier; diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 9c3b692d..abb2fedb 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -865,6 +865,56 @@ nodeProperty = nodeValueExpr; _setToViewOnly(viewAndPendingViewStateProperty, vi _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; diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 64128808..878d921e 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -141,6 +141,10 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo BOOL _accessibilityViewIsModal; BOOL _shouldGroupAccessibilityChildren; NSString *_accessibilityIdentifier; + UIAccessibilityNavigationStyle _accessibilityNavigationStyle; + NSArray *_accessibilityHeaderElements; + CGPoint _accessibilityActivationPoint; + UIBezierPath *_accessibilityPath; #if TIME_DISPLAYNODE_OPS @public diff --git a/AsyncDisplayKit/Private/_ASPendingState.mm b/AsyncDisplayKit/Private/_ASPendingState.mm index 51aa345e..643728f6 100644 --- a/AsyncDisplayKit/Private/_ASPendingState.mm +++ b/AsyncDisplayKit/Private/_ASPendingState.mm @@ -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 diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index 4b3a12c5..213923f4 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -415,6 +415,10 @@ for (ASDisplayNode *n in @[ nodes ]) {\ 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); @@ -468,6 +472,9 @@ for (ASDisplayNode *n in @[ nodes ]) {\ 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; From 39950e74b2876e16599b17fae38700a34fd0ec10 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 7 Apr 2016 15:11:20 -0700 Subject: [PATCH 31/46] Remove NS_AVAILABLE_IOS(8_0); --- AsyncDisplayKit/ASDisplayNode.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index bef4fc5b..cda5e568 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -711,7 +711,7 @@ NS_ASSUME_NONNULL_END @property (atomic, assign) BOOL accessibilityViewIsModal; @property (atomic, assign) BOOL shouldGroupAccessibilityChildren; -@property (nonatomic) UIAccessibilityNavigationStyle accessibilityNavigationStyle NS_AVAILABLE_IOS(8_0); +@property (nonatomic) UIAccessibilityNavigationStyle accessibilityNavigationStyle; #if TARGET_OS_TV @property(nullable, nonatomic, copy) NSArray *accessibilityHeaderElements; #endif From 3b91c22fdf23bb72c8a317f9fac8c91a44dc3653 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 7 Apr 2016 20:08:03 -0700 Subject: [PATCH 32/46] Recreate the accessibleElements if accessed --- AsyncDisplayKit/Details/_ASDisplayView.mm | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index fc2dff6c..a6700327 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -411,10 +411,6 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; - (NSArray *)accessibleElements { - if ( _accessibleElements != nil ) { - return _accessibleElements; - } - _accessibleElements = [[NSMutableArray alloc] init]; ASDisplayNode *selfNode = self.asyncdisplaykit_node; @@ -451,7 +447,7 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; for (ASDisplayNode *subnode in selfNode.subnodes) { // Check if this subnode is a UIAccessibilityContainer if (!subnode.isAccessibilityElement && [subnode accessibilityElementCount] > 0) { - // We are good and the view is an UIAccessibilityContainer so add that + // We are good and the view is an UIAccessibilityContainer so add it [_accessibleElements addObject:subnode.view]; } else if (subnode.isAccessibilityElement) { // Create a accessiblity element from the subnode @@ -471,7 +467,11 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; - (id)accessibilityElementAtIndex:(NSInteger)index { - UIAccessibilityElement *accessibilityElement = [[self accessibleElements] objectAtIndex:index]; + if (_accessibleElements == nil) { + return nil; + } + + UIAccessibilityElement *accessibilityElement = [_accessibleElements objectAtIndex:index]; ASDisplayNode *accessibilityElementNode = accessibilityElement.asyncdisplaykit_node; if (accessibilityElementNode == nil) { return nil; @@ -497,7 +497,11 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; - (NSInteger)indexOfAccessibilityElement:(id)element { - return [self.accessibleElements indexOfObject:element]; + if (_accessibleElements == nil) { + return NSNotFound; + } + + return [_accessibleElements indexOfObject:element]; } @end From a6f287a8fd0e78b3f2da2fd60317991bbac90c7e Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 7 Apr 2016 20:18:33 -0700 Subject: [PATCH 33/46] Address pull request comment regarding ASDisplayNode properties --- AsyncDisplayKit/ASDisplayNode.h | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index cda5e568..2ec93bda 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -700,26 +700,25 @@ NS_ASSUME_NONNULL_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) UIAccessibilityNavigationStyle accessibilityNavigationStyle; +@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(nullable, nonatomic, copy) NSArray *accessibilityHeaderElements; +@property(nonatomic, copy, nullable) NSArray *accessibilityHeaderElements; #endif -@property (nonatomic) CGPoint accessibilityActivationPoint; -@property (nullable, nonatomic, copy) UIBezierPath *accessibilityPath; // Accessibility identification support -@property (nullable, nonatomic, copy) NSString *accessibilityIdentifier; +@property (nonatomic, copy, nullable) NSString *accessibilityIdentifier; @end From 14ca529911bdfea9fb859aec96f23aa2e9c634c1 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 8 Apr 2016 10:04:49 -0700 Subject: [PATCH 34/46] Moving accessibility related code to _ASDisplayViewAccessibility --- AsyncDisplayKit.xcodeproj/project.pbxproj | 12 ++ AsyncDisplayKit/Details/_ASDisplayView.mm | 134 --------------- .../Details/_ASDisplayViewAccessiblity.h | 9 ++ .../Details/_ASDisplayViewAccessiblity.mm | 153 ++++++++++++++++++ 4 files changed, 174 insertions(+), 134 deletions(-) create mode 100644 AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h create mode 100644 AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 1d77edc2..c9bc71fb 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -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 = ""; }; 698548621CA9E025008A345F /* ASEnvironment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASEnvironment.m; sourceTree = ""; }; 698C8B601CAB49FC0052DC3F /* ASLayoutableExtensibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutableExtensibility.h; path = AsyncDisplayKit/Layout/ASLayoutableExtensibility.h; sourceTree = ""; }; + 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASDisplayViewAccessiblity.h; sourceTree = ""; }; + 69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASDisplayViewAccessiblity.mm; sourceTree = ""; }; 69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEnvironmentInternal.h; sourceTree = ""; }; 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEnvironmentInternal.mm; sourceTree = ""; }; 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASRangeControllerUpdateRangeProtocol+Beta.h"; sourceTree = ""; }; @@ -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 */, diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index a6700327..ebfd1dab 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -8,18 +8,11 @@ #import "_ASDisplayView.h" -#import - #import "_ASCoreAnimationExtras.h" -#import "_ASAsyncTransactionContainer.h" -#import "ASAssert.h" -#import "ASDisplayNodeExtras.h" #import "ASDisplayNodeInternal.h" #import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+Subclasses.h" -#import - @interface _ASDisplayView () @property (nonatomic, assign, readwrite) ASDisplayNode *asyncdisplaykit_node; @@ -378,130 +371,3 @@ } #endif @end - - -#pragma mark - Accessibility - -static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; - -@implementation UIAccessibilityElement (_ASDisplayView) - -- (void)setAsyncdisplaykit_node:(ASDisplayNode *)node -{ - objc_setAssociatedObject(self, ASDisplayNodeAssociatedNodeKey, node, OBJC_ASSOCIATION_ASSIGN); // Weak reference to avoid cycle, since the node retains the layer. - - // Update UIAccessibilityElement properties from node - self.accessibilityIdentifier = node.accessibilityIdentifier; - self.accessibilityLabel = node.accessibilityLabel; - self.accessibilityHint = node.accessibilityHint; - self.accessibilityValue = node.accessibilityValue; - self.accessibilityTraits = node.accessibilityTraits; -} - -- (ASDisplayNode *)asyncdisplaykit_node -{ - return objc_getAssociatedObject(self, ASDisplayNodeAssociatedNodeKey); -} - -@end - -@implementation _ASDisplayView (UIAccessibilityContainer) - -#pragma mark - UIAccessibility - -- (NSArray *)accessibleElements -{ - _accessibleElements = [[NSMutableArray alloc] init]; - - ASDisplayNode *selfNode = self.asyncdisplaykit_node; - - // Handle rasterize case - if (selfNode.shouldRasterizeDescendants) { - // In this case we have to go through the whole subnodes tree in BFS fashion and create all - // accessibility elements ourselves as the view hierarchy is flattened - - // Queue used to keep track of subnodes while traversing this layout in a BFS fashion. - std::queue queue; - queue.push(selfNode); - - while (!queue.empty()) { - ASDisplayNode *node = queue.front(); - queue.pop(); - - // Check if we have to add the node to the accessiblity nodes as it's an accessiblity element - if (node != selfNode && node.isAccessibilityElement) { - UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self]; - accessibilityElement.asyncdisplaykit_node = node; - [_accessibleElements addObject:accessibilityElement]; - } - - // Add all subnodes to process in next step - for (int i = 0; i < node.subnodes.count; i++) - queue.push(node.subnodes[i]); - } - 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) { - // Check if this subnode is a UIAccessibilityContainer - if (!subnode.isAccessibilityElement && [subnode accessibilityElementCount] > 0) { - // We are good and the view is an UIAccessibilityContainer so add it - [_accessibleElements addObject:subnode.view]; - } else if (subnode.isAccessibilityElement) { - // Create a accessiblity element from the subnode - UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self]; - accessibilityElement.asyncdisplaykit_node = subnode; - [_accessibleElements addObject:accessibilityElement]; - } - } - - return _accessibleElements; -} - -- (NSInteger)accessibilityElementCount -{ - return [self accessibleElements].count; -} - -- (id)accessibilityElementAtIndex:(NSInteger)index -{ - if (_accessibleElements == nil) { - return nil; - } - - UIAccessibilityElement *accessibilityElement = [_accessibleElements objectAtIndex:index]; - ASDisplayNode *accessibilityElementNode = accessibilityElement.asyncdisplaykit_node; - if (accessibilityElementNode == nil) { - return nil; - } - - // We have to update the accessiblity frame in accessibilityElementAtIndex: as the accessibility frame is in screen - // coordinates and between creating the accessibilityElement and returning it in accessibilityElementAtIndex: - // the frame can change - - // Handle if node is rasterized - ASDisplayNode *selfNode = self.asyncdisplaykit_node; - if (selfNode.shouldRasterizeDescendants) { - // We need to convert the accessibilityElementNode frame into the coordinate system of the selfNode - CGRect frame = [selfNode convertRect:accessibilityElementNode.bounds fromNode:accessibilityElementNode]; - accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(frame, self); - return accessibilityElement; - } - - // Handle non rasterized case - accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibilityElementNode.frame, self); - return accessibilityElement; -} - -- (NSInteger)indexOfAccessibilityElement:(id)element -{ - if (_accessibleElements == nil) { - return NSNotFound; - } - - return [_accessibleElements indexOfObject:element]; -} - -@end diff --git a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h new file mode 100644 index 00000000..2a4eca69 --- /dev/null +++ b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h @@ -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 diff --git a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm new file mode 100644 index 00000000..f311447d --- /dev/null +++ b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm @@ -0,0 +1,153 @@ +/* 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 "ASDisplayNode+FrameworkPrivate.h" + +#import +#import + + +#pragma mark - UIAccessibilityElement + +static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; + +@implementation UIAccessibilityElement (_ASDisplayView) + +- (void)setAsyncdisplaykit_node:(ASDisplayNode *)node +{ + objc_setAssociatedObject(self, ASDisplayNodeAssociatedNodeKey, node, OBJC_ASSOCIATION_ASSIGN); // Weak reference to avoid cycle, since the node retains the layer. + + // Update UIAccessibilityElement properties from node + self.accessibilityIdentifier = node.accessibilityIdentifier; + self.accessibilityLabel = node.accessibilityLabel; + self.accessibilityHint = node.accessibilityHint; + self.accessibilityValue = node.accessibilityValue; + self.accessibilityTraits = node.accessibilityTraits; +} + +- (ASDisplayNode *)asyncdisplaykit_node +{ + return objc_getAssociatedObject(self, ASDisplayNodeAssociatedNodeKey); +} + +@end + + +#pragma mark - _ASDisplayView + +@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) { + // In this case we have to go through the whole subnodes tree in BFS fashion and create all + // accessibility elements ourselves as the view hierarchy is flattened + + // Queue used to keep track of subnodes while traversing this layout in a BFS fashion. + std::queue queue; + queue.push(selfNode); + + while (!queue.empty()) { + ASDisplayNode *node = queue.front(); + queue.pop(); + + // Check if we have to add the node to the accessiblity nodes as it's an accessiblity element + if (node != selfNode && node.isAccessibilityElement) { + UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self]; + accessibilityElement.asyncdisplaykit_node = node; + [_accessibleElements addObject:accessibilityElement]; + } + + // Add all subnodes to process in next step + for (int i = 0; i < node.subnodes.count; i++) + queue.push(node.subnodes[i]); + } + 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) { + // Check if this subnode is a UIAccessibilityContainer + if (!subnode.isAccessibilityElement && [subnode accessibilityElementCount] > 0) { + // We are good and the view is an UIAccessibilityContainer so add it + [_accessibleElements addObject:subnode.view]; + } else if (subnode.isAccessibilityElement) { + // Create a accessiblity element from the subnode + UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self]; + accessibilityElement.asyncdisplaykit_node = subnode; + [_accessibleElements addObject:accessibilityElement]; + } + } + + return _accessibleElements; +} + +- (NSInteger)accessibilityElementCount +{ + return [self accessibleElements].count; +} + +- (id)accessibilityElementAtIndex:(NSInteger)index +{ + if (_accessibleElements == nil) { + return nil; + } + + UIAccessibilityElement *accessibilityElement = [_accessibleElements objectAtIndex:index]; + ASDisplayNode *accessibilityElementNode = accessibilityElement.asyncdisplaykit_node; + if (accessibilityElementNode == nil) { + return nil; + } + + // We have to update the accessiblity frame in accessibilityElementAtIndex: as the accessibility frame is in screen + // coordinates and between creating the accessibilityElement and returning it in accessibilityElementAtIndex: + // the frame can change + + // Handle if node is rasterized + ASDisplayNode *selfNode = self.asyncdisplaykit_node; + if (selfNode.shouldRasterizeDescendants) { + // We need to convert the accessibilityElementNode frame into the coordinate system of the selfNode + CGRect frame = [selfNode convertRect:accessibilityElementNode.bounds fromNode:accessibilityElementNode]; + accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(frame, self); + return accessibilityElement; + } + + // Handle non rasterized case + accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibilityElementNode.frame, self); + return accessibilityElement; +} + +- (NSInteger)indexOfAccessibilityElement:(id)element +{ + if (_accessibleElements == nil) { + return NSNotFound; + } + + return [_accessibleElements indexOfObject:element]; +} + +@end From 1b7db082dd5af037414916ab36c848d7591bf81a Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sat, 9 Apr 2016 16:04:08 -0700 Subject: [PATCH 35/46] Improve _ASDisplayViewAccessibility - Add class method to create a UIAccessibilityElement from a ASDisplayNode - Add function to iterate through a ASDisplayNode tree in bfs fashion - Add assert for _accessibleElements in accessibilityElementAtIndex: --- AsyncDisplayKit/ASDisplayNodeExtras.h | 6 ++ AsyncDisplayKit/ASDisplayNodeExtras.mm | 20 ++++++ .../Details/_ASDisplayViewAccessiblity.mm | 65 +++++++++---------- 3 files changed, 57 insertions(+), 34 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.h b/AsyncDisplayKit/ASDisplayNodeExtras.h index 171d6f38..97fe09c2 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.h +++ b/AsyncDisplayKit/ASDisplayNodeExtras.h @@ -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. diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.mm b/AsyncDisplayKit/ASDisplayNodeExtras.mm index dad6da1b..b64ba06a 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.mm +++ b/AsyncDisplayKit/ASDisplayNodeExtras.mm @@ -10,6 +10,8 @@ #import "ASDisplayNodeInternal.h" #import "ASDisplayNode+FrameworkPrivate.h" +#import + 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 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) { diff --git a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm index f311447d..91b0eafb 100644 --- a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm +++ b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm @@ -8,23 +8,27 @@ #import "_ASDisplayViewAccessiblity.h" #import "_ASDisplayView.h" +#import "ASDisplayNodeExtras.h" #import "ASDisplayNode+FrameworkPrivate.h" #import -#import #pragma mark - UIAccessibilityElement -static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; - @implementation UIAccessibilityElement (_ASDisplayView) ++ (UIAccessibilityElement *)accessibilityElementWithContainer:(id)container node:(ASDisplayNode *)node +{ + UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:container]; + accessibilityElement.asyncdisplaykit_node = node; + return accessibilityElement; +} + - (void)setAsyncdisplaykit_node:(ASDisplayNode *)node { - objc_setAssociatedObject(self, ASDisplayNodeAssociatedNodeKey, node, OBJC_ASSOCIATION_ASSIGN); // Weak reference to avoid cycle, since the node retains the layer. - - // Update UIAccessibilityElement properties from node + objc_setAssociatedObject(self, @selector(asyncdisplaykit_node), node, OBJC_ASSOCIATION_ASSIGN); + self.accessibilityIdentifier = node.accessibilityIdentifier; self.accessibilityLabel = node.accessibilityLabel; self.accessibilityHint = node.accessibilityHint; @@ -34,13 +38,17 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; - (ASDisplayNode *)asyncdisplaykit_node { - return objc_getAssociatedObject(self, ASDisplayNodeAssociatedNodeKey); + return objc_getAssociatedObject(self, @selector(asyncdisplaykit_node)); } @end -#pragma mark - _ASDisplayView +#pragma mark - _ASDisplayView / UIAccessibilityContainer + +static BOOL ASNodeIsAccessiblityContainer(ASDisplayNode *node) { + return (!node.isAccessibilityElement && [node accessibilityElementCount] > 0); +} @interface _ASDisplayView () { NSMutableArray *_accessibleElements; @@ -65,41 +73,29 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; if (selfNode.shouldRasterizeDescendants) { // In this case we have to go through the whole subnodes tree in BFS fashion and create all // accessibility elements ourselves as the view hierarchy is flattened - - // Queue used to keep track of subnodes while traversing this layout in a BFS fashion. - std::queue queue; - queue.push(selfNode); - - while (!queue.empty()) { - ASDisplayNode *node = queue.front(); - queue.pop(); - - // Check if we have to add the node to the accessiblity nodes as it's an accessiblity element + 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 alloc] initWithAccessibilityContainer:self]; - accessibilityElement.asyncdisplaykit_node = node; - [_accessibleElements addObject:accessibilityElement]; + [_accessibleElements addObject:[UIAccessibilityElement accessibilityElementWithContainer:self node:node]]; } - - // Add all subnodes to process in next step - for (int i = 0; i < node.subnodes.count; i++) - queue.push(node.subnodes[i]); - } + }); 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) { - // Check if this subnode is a UIAccessibilityContainer - if (!subnode.isAccessibilityElement && [subnode accessibilityElementCount] > 0) { - // We are good and the view is an UIAccessibilityContainer so add it + if (subnode.isAccessibilityElement) { + if (subnode.isLayerBacked) { + // The same comment for layer backed subnodes is true as for subnodes within a shouldRasterizeDescendants node. + // See details above + [_accessibleElements addObject:[UIAccessibilityElement accessibilityElementWithContainer:self node:subnode]]; + } else { + [_accessibleElements addObject:subnode.view]; + } + } else if (ASNodeIsAccessiblityContainer(subnode)) { [_accessibleElements addObject:subnode.view]; - } else if (subnode.isAccessibilityElement) { - // Create a accessiblity element from the subnode - UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self]; - accessibilityElement.asyncdisplaykit_node = subnode; - [_accessibleElements addObject:accessibilityElement]; } } @@ -113,6 +109,7 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; - (id)accessibilityElementAtIndex:(NSInteger)index { + ASDisplayNodeAssertNotNil(_accessibleElements, @"At this point _accessibleElements should be created."); if (_accessibleElements == nil) { return nil; } From 6a147021bc2767a768ea031fc4cba308fda048c4 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sat, 9 Apr 2016 17:03:35 -0700 Subject: [PATCH 36/46] Move setting of accessibilityFrame into accessibleElements - Calculate accessibilityFrame for accessibleElements already in accessibleElements method - Remove asyncdisplaykit_node associated object as it's not needed anymore --- .../Details/_ASDisplayViewAccessiblity.mm | 72 ++++++------------- 1 file changed, 22 insertions(+), 50 deletions(-) diff --git a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm index 91b0eafb..83592db3 100644 --- a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm +++ b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm @@ -21,35 +21,19 @@ + (UIAccessibilityElement *)accessibilityElementWithContainer:(id)container node:(ASDisplayNode *)node { UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:container]; - accessibilityElement.asyncdisplaykit_node = node; + accessibilityElement.accessibilityIdentifier = node.accessibilityIdentifier; + accessibilityElement.accessibilityLabel = node.accessibilityLabel; + accessibilityElement.accessibilityHint = node.accessibilityHint; + accessibilityElement.accessibilityValue = node.accessibilityValue; + accessibilityElement.accessibilityTraits = node.accessibilityTraits; return accessibilityElement; } -- (void)setAsyncdisplaykit_node:(ASDisplayNode *)node -{ - objc_setAssociatedObject(self, @selector(asyncdisplaykit_node), node, OBJC_ASSOCIATION_ASSIGN); - - self.accessibilityIdentifier = node.accessibilityIdentifier; - self.accessibilityLabel = node.accessibilityLabel; - self.accessibilityHint = node.accessibilityHint; - self.accessibilityValue = node.accessibilityValue; - self.accessibilityTraits = node.accessibilityTraits; -} - -- (ASDisplayNode *)asyncdisplaykit_node -{ - return objc_getAssociatedObject(self, @selector(asyncdisplaykit_node)); -} - @end #pragma mark - _ASDisplayView / UIAccessibilityContainer -static BOOL ASNodeIsAccessiblityContainer(ASDisplayNode *node) { - return (!node.isAccessibilityElement && [node accessibilityElementCount] > 0); -} - @interface _ASDisplayView () { NSMutableArray *_accessibleElements; } @@ -71,13 +55,19 @@ static BOOL ASNodeIsAccessiblityContainer(ASDisplayNode *node) { // Handle rasterize case if (selfNode.shouldRasterizeDescendants) { - // In this case we have to go through the whole subnodes tree in BFS fashion and create all - // accessibility elements ourselves as the view hierarchy is flattened + // 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) { - [_accessibleElements addObject:[UIAccessibilityElement accessibilityElementWithContainer:self node:node]]; + 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; @@ -87,14 +77,17 @@ static BOOL ASNodeIsAccessiblityContainer(ASDisplayNode *node) { // 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 subnodes is true as for subnodes within a shouldRasterizeDescendants node. + // The same comment for layer backed nodes is true as for subnodes within a shouldRasterizeDescendants node. // See details above - [_accessibleElements addObject:[UIAccessibilityElement accessibilityElementWithContainer:self node:subnode]]; + accessiblityElement = [UIAccessibilityElement accessibilityElementWithContainer:self node:subnode]; } else { - [_accessibleElements addObject:subnode.view]; + accessiblityElement = subnode.view; } - } else if (ASNodeIsAccessiblityContainer(subnode)) { + [accessiblityElement setAccessibilityFrame:UIAccessibilityConvertFrameToScreenCoordinates(subnode.frame, self)]; + [_accessibleElements addObject:accessiblityElement]; + } else if ([subnode accessibilityElementCount] > 0) { // Check if it's an UIAccessibilityContainer [_accessibleElements addObject:subnode.view]; } } @@ -114,28 +107,7 @@ static BOOL ASNodeIsAccessiblityContainer(ASDisplayNode *node) { return nil; } - UIAccessibilityElement *accessibilityElement = [_accessibleElements objectAtIndex:index]; - ASDisplayNode *accessibilityElementNode = accessibilityElement.asyncdisplaykit_node; - if (accessibilityElementNode == nil) { - return nil; - } - - // We have to update the accessiblity frame in accessibilityElementAtIndex: as the accessibility frame is in screen - // coordinates and between creating the accessibilityElement and returning it in accessibilityElementAtIndex: - // the frame can change - - // Handle if node is rasterized - ASDisplayNode *selfNode = self.asyncdisplaykit_node; - if (selfNode.shouldRasterizeDescendants) { - // We need to convert the accessibilityElementNode frame into the coordinate system of the selfNode - CGRect frame = [selfNode convertRect:accessibilityElementNode.bounds fromNode:accessibilityElementNode]; - accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(frame, self); - return accessibilityElement; - } - - // Handle non rasterized case - accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibilityElementNode.frame, self); - return accessibilityElement; + return _accessibleElements[index]; } - (NSInteger)indexOfAccessibilityElement:(id)element From f633f6fff0b666550270dbda1bf899b4622eb328 Mon Sep 17 00:00:00 2001 From: Hannah Troisi Date: Sat, 9 Apr 2016 21:36:57 -0700 Subject: [PATCH 37/46] [ASControlNode] modify hitTestDebug tool to highlight edges clipped by one or more superviewsw --- AsyncDisplayKit/ASControlNode.mm | 60 ++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASControlNode.mm b/AsyncDisplayKit/ASControlNode.mm index 4ee96d74..02b96157 100644 --- a/AsyncDisplayKit/ASControlNode.mm +++ b/AsyncDisplayKit/ASControlNode.mm @@ -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]; } } @@ -468,7 +467,9 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v // Even if our parents don't have clipsToBounds set and would allow us to display the debug overlay, UIKit event delivery (hitTest:) // 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]); + CGRect originalRect = UIEdgeInsetsInsetRect(self.bounds, [self hitTestSlop]); + CGRect intersectRect = originalRect; + UIRectEdge clippedEdges = UIRectEdgeNone; CALayer *layer = self.layer; CALayer *intersectLayer = layer; CALayer *intersectSuperlayer = layer.superlayer; @@ -492,7 +493,60 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v 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 which edges were clipped + if (!CGSizeEqualToSize(originalRect.size, finalRect.size)) { + + if (originalRect.origin.y != finalRect.origin.y) { + clippedEdges |= UIRectEdgeTop; + } + if (originalRect.origin.x != finalRect.origin.x) { + clippedEdges |= UIRectEdgeLeft; + } + if (CGRectGetMaxY(originalRect) != CGRectGetMaxY(finalRect)) { + clippedEdges |= UIRectEdgeBottom; + } + if (CGRectGetMaxX(originalRect) != CGRectGetMaxX(finalRect)) { + clippedEdges |= UIRectEdgeRight; + } + + const CGFloat borderWidth = 2.0; + const UIColor *borderColor = [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); + + [borderColor setFill]; + + if (clippedEdges & UIRectEdgeTop) { + UIRectFill(CGRectMake(0.0, 0.0, imgRect.size.width, borderWidth)); + } + if (clippedEdges & UIRectEdgeLeft) { + UIRectFill(CGRectMake(0.0, 0.0, borderWidth, imgRect.size.height)); + } + if (clippedEdges & UIRectEdgeBottom) { + UIRectFill(CGRectMake(0.0, imgRect.size.height - borderWidth, imgRect.size.width, borderWidth)); + } + if (clippedEdges & UIRectEdgeRight) { + UIRectFill(CGRectMake(imgRect.size.width - borderWidth, 0.0, borderWidth, imgRect.size.height)); + } + + UIImage *debugHighlightImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + UIEdgeInsets edgeInsets = UIEdgeInsetsMake(borderWidth, borderWidth, borderWidth, borderWidth); + _debugHighlightOverlay.image = [debugHighlightImage resizableImageWithCapInsets:edgeInsets + resizingMode:UIImageResizingModeStretch]; + _debugHighlightOverlay.backgroundColor = nil; + } else { + _debugHighlightOverlay.backgroundColor = fillColor; + } + + _debugHighlightOverlay.frame = finalRect; } } From 516158865ab8fe1926922c7a474f0cf61627445f Mon Sep 17 00:00:00 2001 From: Hannah Troisi Date: Sat, 9 Apr 2016 22:51:36 -0700 Subject: [PATCH 38/46] Update README.md updated sheilds --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 82be85be..ba1dc48d 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ ![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) -[![Version](https://img.shields.io/cocoapods/v/AsyncDisplayKit.svg)](http://cocoapods.org/pods/AsyncDisplayKit) -[![Platform](https://img.shields.io/cocoapods/p/AsyncDisplayKit.svg)]() +[![Apps Using](https://img.shields.io/badge/appsUsing-%3E3,200-green.svg)](http://cocoapods.org/pods/AsyncDisplayKit) +[![Downloads](https://img.shields.io/badge/downloads-%3E336,372-green.svg)](http://cocoapods.org/pods/AsyncDisplayKit) +[![Platform](https://img.shields.io/badge/platforms-iOS%20%7C%20tvOS-lightgrey.svg)](http://AsyncDisplayKit.org) +[![Languages](https://img.shields.io/badge/languages-ObjC%20%7C%20Swift-orange.svg)](http://AsyncDisplayKit.org) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) +[![Version](https://img.shields.io/cocoapods/v/AsyncDisplayKit.svg)](http://cocoapods.org/pods/AsyncDisplayKit) +[![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 From 7fa810c77fc5dfa535d23fa1e1ca1a4460b19284 Mon Sep 17 00:00:00 2001 From: Hannah Troisi Date: Sun, 10 Apr 2016 00:19:00 -0700 Subject: [PATCH 39/46] [ASControlNode] modified hitTestDebug tool vizualize clipping of .clipsToBounds & fixed bug - added orange overlay edge highlighting if tapable area clipped by .clipsToBounds of any parent in the hierarchy - fixed bug in comparison of child / parent rect edges --- AsyncDisplayKit/ASControlNode.mm | 77 ++++++++++++++++--------- AsyncDisplayKit/AsyncDisplayKit+Debug.h | 2 + 2 files changed, 51 insertions(+), 28 deletions(-) diff --git a/AsyncDisplayKit/ASControlNode.mm b/AsyncDisplayKit/ASControlNode.mm index 02b96157..9a2fd276 100644 --- a/AsyncDisplayKit/ASControlNode.mm +++ b/AsyncDisplayKit/ASControlNode.mm @@ -470,6 +470,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v CGRect originalRect = UIEdgeInsetsInsetRect(self.bounds, [self hitTestSlop]); CGRect intersectRect = originalRect; UIRectEdge clippedEdges = UIRectEdgeNone; + UIRectEdge clipsToBoundsClippedEdges = UIRectEdgeNone; CALayer *layer = self.layer; CALayer *intersectLayer = layer; CALayer *intersectSuperlayer = layer.superlayer; @@ -479,14 +480,20 @@ 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 = intersectSuperlayer.masksToBounds; ASDisplayNode *parentNode = ASLayerToDisplayNode(intersectSuperlayer); - if (parentNode) { + if (parentNode && !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 (parentClipsToBounds) { + if (!CGSizeEqualToSize(parentHitRect.size, intersectRectInParentCoordinates.size)) { + clipsToBoundsClippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates parentRect:parentHitRect rectEdge:clipsToBoundsClippedEdges]; + } + } // Advance up the tree. intersectLayer = intersectSuperlayer; @@ -499,41 +506,19 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v // determine which edges were clipped if (!CGSizeEqualToSize(originalRect.size, finalRect.size)) { - if (originalRect.origin.y != finalRect.origin.y) { - clippedEdges |= UIRectEdgeTop; - } - if (originalRect.origin.x != finalRect.origin.x) { - clippedEdges |= UIRectEdgeLeft; - } - if (CGRectGetMaxY(originalRect) != CGRectGetMaxY(finalRect)) { - clippedEdges |= UIRectEdgeBottom; - } - if (CGRectGetMaxX(originalRect) != CGRectGetMaxX(finalRect)) { - clippedEdges |= UIRectEdgeRight; - } + clippedEdges = [self setEdgesOfIntersectionForChildRect:originalRect parentRect:finalRect rectEdge:clippedEdges]; const CGFloat borderWidth = 2.0; - const UIColor *borderColor = [UIColor colorWithRed:30/255.0 green:90/255.0 blue:50/255.0 alpha:0.7]; + UIColor *superhitTestSlopClipsBorderColor = [UIColor colorWithRed:30/255.0 green:90/255.0 blue:50/255.0 alpha:0.7]; + UIColor *superClipsToBoundsBorderColor = [[UIColor orangeColor] colorWithAlphaComponent:0.8]; CGRect imgRect = CGRectMake(0, 0, 2.0 * borderWidth + 1.0, 2.0 * borderWidth + 1.0); UIGraphicsBeginImageContext(imgRect.size); [fillColor setFill]; UIRectFill(imgRect); - [borderColor setFill]; - - if (clippedEdges & UIRectEdgeTop) { - UIRectFill(CGRectMake(0.0, 0.0, imgRect.size.width, borderWidth)); - } - if (clippedEdges & UIRectEdgeLeft) { - UIRectFill(CGRectMake(0.0, 0.0, borderWidth, imgRect.size.height)); - } - if (clippedEdges & UIRectEdgeBottom) { - UIRectFill(CGRectMake(0.0, imgRect.size.height - borderWidth, imgRect.size.width, borderWidth)); - } - if (clippedEdges & UIRectEdgeRight) { - UIRectFill(CGRectMake(imgRect.size.width - borderWidth, 0.0, borderWidth, imgRect.size.height)); - } + [self drawEdgeIfClippedWithEdges:clippedEdges color:superhitTestSlopClipsBorderColor borderWidth:borderWidth imgRect:imgRect]; + [self drawEdgeIfClippedWithEdges:clipsToBoundsClippedEdges color:superClipsToBoundsBorderColor borderWidth:borderWidth imgRect:imgRect]; UIImage *debugHighlightImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); @@ -550,6 +535,42 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v } } +- (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)); + } +} + + (void)setEnableHitTestDebug:(BOOL)enable { _enableHitTestDebug = enable; diff --git a/AsyncDisplayKit/AsyncDisplayKit+Debug.h b/AsyncDisplayKit/AsyncDisplayKit+Debug.h index 4192d673..ba6897b9 100644 --- a/AsyncDisplayKit/AsyncDisplayKit+Debug.h +++ b/AsyncDisplayKit/AsyncDisplayKit+Debug.h @@ -13,6 +13,8 @@ /** Class method to enable a visualization overlay of the tapable area on the ASControlNode. For app debugging purposes only. + Overlay = translucent green color, edges clipped by hitTestDebug of any parent in the hierarchy = dark green bordered edge, + edges clipped by clipToBounds = YES of any parent in the hierarchy = orange bordered edge. @param enable Specify YES to make this debug feature enabled when messaging the ASControlNode class. */ + (void)setEnableHitTestDebug:(BOOL)enable; From a6f779a7feab0ae1bda53d1d8390441d14d167cb Mon Sep 17 00:00:00 2001 From: Hannah Troisi Date: Sun, 10 Apr 2016 00:51:36 -0700 Subject: [PATCH 40/46] [ASControlNode] hitTestDebug tool refactoring + comments:wq --- AsyncDisplayKit/ASControlNode.mm | 35 ++++++++++++++++--------- AsyncDisplayKit/AsyncDisplayKit+Debug.h | 9 ++++--- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/AsyncDisplayKit/ASControlNode.mm b/AsyncDisplayKit/ASControlNode.mm index 9a2fd276..3fc2f43e 100644 --- a/AsyncDisplayKit/ASControlNode.mm +++ b/AsyncDisplayKit/ASControlNode.mm @@ -467,8 +467,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v // Even if our parents don't have clipsToBounds set and would allow us to display the debug overlay, UIKit event delivery (hitTest:) // 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 originalRect = UIEdgeInsetsInsetRect(self.bounds, [self hitTestSlop]); - CGRect intersectRect = originalRect; + CGRect intersectRect = UIEdgeInsetsInsetRect(self.bounds, [self hitTestSlop]); UIRectEdge clippedEdges = UIRectEdgeNone; UIRectEdge clipsToBoundsClippedEdges = UIRectEdgeNone; CALayer *layer = self.layer; @@ -480,18 +479,33 @@ 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 = intersectSuperlayer.masksToBounds; + BOOL parentClipsToBounds = NO; + ASDisplayNode *parentNode = ASLayerToDisplayNode(intersectSuperlayer); - if (parentNode && !parentClipsToBounds) { - parentHitRect = UIEdgeInsetsInsetRect(parentHitRect, [parentNode hitTestSlop]); + if (parentNode) { + 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 (parentClipsToBounds) { - if (!CGSizeEqualToSize(parentHitRect.size, intersectRectInParentCoordinates.size)) { - clipsToBoundsClippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates parentRect:parentHitRect rectEdge:clipsToBoundsClippedEdges]; + if (!CGSizeEqualToSize(parentHitRect.size, intersectRectInParentCoordinates.size)) { + clippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates + parentRect:parentHitRect rectEdge:clippedEdges]; + if (parentClipsToBounds) { + clipsToBoundsClippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates + parentRect:parentHitRect rectEdge:clipsToBoundsClippedEdges]; } } @@ -504,10 +518,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v UIColor *fillColor = [[UIColor greenColor] colorWithAlphaComponent:0.4]; // determine which edges were clipped - if (!CGSizeEqualToSize(originalRect.size, finalRect.size)) { - - clippedEdges = [self setEdgesOfIntersectionForChildRect:originalRect parentRect:finalRect rectEdge:clippedEdges]; - + if (clippedEdges != UIRectEdgeNone) { const CGFloat borderWidth = 2.0; UIColor *superhitTestSlopClipsBorderColor = [UIColor colorWithRed:30/255.0 green:90/255.0 blue:50/255.0 alpha:0.7]; UIColor *superClipsToBoundsBorderColor = [[UIColor orangeColor] colorWithAlphaComponent:0.8]; diff --git a/AsyncDisplayKit/AsyncDisplayKit+Debug.h b/AsyncDisplayKit/AsyncDisplayKit+Debug.h index ba6897b9..b2667d1e 100644 --- a/AsyncDisplayKit/AsyncDisplayKit+Debug.h +++ b/AsyncDisplayKit/AsyncDisplayKit+Debug.h @@ -12,9 +12,12 @@ @interface ASControlNode (Debugging) /** - Class method to enable a visualization overlay of the tapable area on the ASControlNode. For app debugging purposes only. - Overlay = translucent green color, edges clipped by hitTestDebug of any parent in the hierarchy = dark green bordered edge, - edges clipped by clipToBounds = YES of any parent in the hierarchy = orange bordered edge. + 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; From b76f886a3fb0340fdd7e9e23bb9fa08d30dc5083 Mon Sep 17 00:00:00 2001 From: appleguy Date: Sun, 10 Apr 2016 01:06:24 -0700 Subject: [PATCH 41/46] GitHub Shields - Refine Colors and Groupings for README.md --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ba1dc48d..b984037d 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ ![AsyncDisplayKit](https://github.com/facebook/AsyncDisplayKit/blob/master/docs/assets/logo.png) -[![Apps Using](https://img.shields.io/badge/appsUsing-%3E3,200-green.svg)](http://cocoapods.org/pods/AsyncDisplayKit) -[![Downloads](https://img.shields.io/badge/downloads-%3E336,372-green.svg)](http://cocoapods.org/pods/AsyncDisplayKit) -[![Platform](https://img.shields.io/badge/platforms-iOS%20%7C%20tvOS-lightgrey.svg)](http://AsyncDisplayKit.org) +[![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) -[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) + [![Version](https://img.shields.io/cocoapods/v/AsyncDisplayKit.svg)](http://cocoapods.org/pods/AsyncDisplayKit) +[![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) From 4bd48505af08d4a3b5c1b343d7a9c50c825a75a0 Mon Sep 17 00:00:00 2001 From: Hannah Troisi Date: Sun, 10 Apr 2016 01:15:15 -0700 Subject: [PATCH 42/46] [ASControlNode] hitTestDebug name refactoring --- AsyncDisplayKit/ASControlNode.mm | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/AsyncDisplayKit/ASControlNode.mm b/AsyncDisplayKit/ASControlNode.mm index 3fc2f43e..1952b71c 100644 --- a/AsyncDisplayKit/ASControlNode.mm +++ b/AsyncDisplayKit/ASControlNode.mm @@ -517,19 +517,21 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v CGRect finalRect = [intersectLayer convertRect:intersectRect toLayer:layer]; UIColor *fillColor = [[UIColor greenColor] colorWithAlphaComponent:0.4]; - // determine which edges were clipped - if (clippedEdges != UIRectEdgeNone) { + // determine if edges are clipped + if (clippedEdges == UIRectEdgeNone) { + _debugHighlightOverlay.backgroundColor = fillColor; + } else { const CGFloat borderWidth = 2.0; - UIColor *superhitTestSlopClipsBorderColor = [UIColor colorWithRed:30/255.0 green:90/255.0 blue:50/255.0 alpha:0.7]; - UIColor *superClipsToBoundsBorderColor = [[UIColor orangeColor] colorWithAlphaComponent:0.8]; + 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:superhitTestSlopClipsBorderColor borderWidth:borderWidth imgRect:imgRect]; - [self drawEdgeIfClippedWithEdges:clipsToBoundsClippedEdges color:superClipsToBoundsBorderColor borderWidth:borderWidth imgRect:imgRect]; + [self drawEdgeIfClippedWithEdges:clippedEdges color:clipsBorderColor borderWidth:borderWidth imgRect:imgRect]; + [self drawEdgeIfClippedWithEdges:clipsToBoundsClippedEdges color:borderColor borderWidth:borderWidth imgRect:imgRect]; UIImage *debugHighlightImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); @@ -538,8 +540,6 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v _debugHighlightOverlay.image = [debugHighlightImage resizableImageWithCapInsets:edgeInsets resizingMode:UIImageResizingModeStretch]; _debugHighlightOverlay.backgroundColor = nil; - } else { - _debugHighlightOverlay.backgroundColor = fillColor; } _debugHighlightOverlay.frame = finalRect; From 974d8c8817d9c3f2e24006646854c00314246f9f Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 10 Apr 2016 01:31:32 -0700 Subject: [PATCH 43/46] [ASEnvironmentState] Don't upward-propagate sizeRange or layoutPosition (this behavior, if ultimately desirable, requires additional limiting conditions). --- AsyncDisplayKit/Private/ASEnvironmentInternal.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/Private/ASEnvironmentInternal.mm b/AsyncDisplayKit/Private/ASEnvironmentInternal.mm index 909787a5..ca1dec44 100644 --- a/AsyncDisplayKit/Private/ASEnvironmentInternal.mm +++ b/AsyncDisplayKit/Private/ASEnvironmentInternal.mm @@ -138,10 +138,12 @@ ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environme } if (ASRelativeSizeRangeEqualToRelativeSizeRange(parentLayoutOptionsState.sizeRange, defaultState.sizeRange)) { - parentLayoutOptionsState.sizeRange = layoutOptionsState.sizeRange; + // For now it is unclear if we should be up-propagating sizeRange or layoutPosition. + // parentLayoutOptionsState.sizeRange = layoutOptionsState.sizeRange; } if (CGPointEqualToPoint(parentLayoutOptionsState.layoutPosition, defaultState.layoutPosition)) { - parentLayoutOptionsState.layoutPosition = layoutOptionsState.layoutPosition; + // For now it is unclear if we should be up-propagating sizeRange or layoutPosition. + // parentLayoutOptionsState.layoutPosition = layoutOptionsState.layoutPosition; } // Merge extended values if necessary From 3c660130881ddc55e4d0bbfc6e8e93254c46838d Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sun, 10 Apr 2016 14:23:33 -0700 Subject: [PATCH 44/46] Remove logging message --- AsyncDisplayKit/ASCollectionView.mm | 2 -- 1 file changed, 2 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 26dfc15c..64c28d58 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -782,8 +782,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)_beginBatchFetching { - NSLog(@"begin batch fetching"); - [_batchContext beginBatchFetching]; if ([_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ From ed0ed74b7281d2b6a1385c5f194527b1e671873d Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sun, 10 Apr 2016 15:04:49 -0700 Subject: [PATCH 45/46] Add switch to enable / disable layout option properties --- AsyncDisplayKit/Private/ASEnvironmentInternal.mm | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/AsyncDisplayKit/Private/ASEnvironmentInternal.mm b/AsyncDisplayKit/Private/ASEnvironmentInternal.mm index ca1dec44..1c834946 100644 --- a/AsyncDisplayKit/Private/ASEnvironmentInternal.mm +++ b/AsyncDisplayKit/Private/ASEnvironmentInternal.mm @@ -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 object, void(^block)(id node)) @@ -106,6 +109,10 @@ ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environme // 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) { From 5c7d43551e6d80cdd6d09338f5d117624f28a4c7 Mon Sep 17 00:00:00 2001 From: Luke Parham Date: Sun, 10 Apr 2016 18:40:07 -0500 Subject: [PATCH 46/46] updated links in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b984037d..d8f1445a 100644 --- a/README.md +++ b/README.md @@ -99,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