From bd3c73624e7dc869be71ba1bca02b3e7d206c7f9 Mon Sep 17 00:00:00 2001 From: aaronschubert0 Date: Fri, 29 Jan 2016 11:40:26 +0000 Subject: [PATCH 01/16] Enabled tvOS spec for dev --- AsyncDisplayKit.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index 974b390886..5552079848 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -49,5 +49,5 @@ Pod::Spec.new do |spec| spec.ios.deployment_target = '7.0' # tvOS not recognized by older versions of Cocoapods - add this only after tvOS support complete. - # spec.tvos.deployment_target = '9.0' + spec.tvos.deployment_target = '9.0' end From f39ee46b617e7add9aa1dd6a056c19bcfda16a4e Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Fri, 5 Feb 2016 14:23:01 +0000 Subject: [PATCH 02/16] Gate ASButtonNode setTitle method since [UIFont buttonFontSize] is unavailable on tvOS 9.1 --- AsyncDisplayKit/ASButtonNode.h | 3 ++- AsyncDisplayKit/ASButtonNode.mm | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASButtonNode.h b/AsyncDisplayKit/ASButtonNode.h index 305121b3a6..d949d1b51d 100644 --- a/AsyncDisplayKit/ASButtonNode.h +++ b/AsyncDisplayKit/ASButtonNode.h @@ -54,6 +54,7 @@ */ - (void)setAttributedTitle:(nullable NSAttributedString *)title forState:(ASControlState)state; +#if TARGET_OS_IOS /** * Sets the title to use for the specified state. This will reset styled title previously set with -setAttributedTitle:forState. * @@ -63,7 +64,7 @@ * @param state The state that uses the specified title. The possible values are described in ASControlState. */ - (void)setTitle:(nonnull NSString *)title withFont:(nullable UIFont *)font withColor:(nullable UIColor *)color forState:(ASControlState)state; - +#endif /** * Returns the image used for a button state. * diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index 7ed676e744..b22a707fca 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -196,6 +196,7 @@ [self setNeedsLayout]; } +#if TARGET_OS_IOS - (void)setTitle:(NSString *)title withFont:(UIFont *)font withColor:(UIColor *)color forState:(ASControlState)state { NSDictionary *attributes = @{ @@ -207,6 +208,7 @@ attributes:attributes]; [self setAttributedTitle:string forState:state]; } +#endif - (NSAttributedString *)attributedTitleForState:(ASControlState)state { From 2588721cf0350f7ded579d363e4f91337be22101 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Fri, 5 Feb 2016 14:37:51 +0000 Subject: [PATCH 03/16] Gate ASVideoNode for the time being --- AsyncDisplayKit/ASVideoNode.h | 4 ++-- AsyncDisplayKit/ASVideoNode.mm | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h index 7df171d005..0bb44cce48 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/AsyncDisplayKit/ASVideoNode.h @@ -7,7 +7,7 @@ */ #import - +#if TARGET_OS_IOS @protocol ASVideoNodeDelegate; // If you need ASVideoNode, please use AsyncDisplayKit master until this comment is removed. @@ -40,4 +40,4 @@ @optional - (void)videoPlaybackDidFinish:(ASVideoNode *)videoNode; @end - +#endif diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index f6f717f452..7ac770b128 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -5,10 +5,9 @@ * 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. */ - +#if TARGET_OS_IOS #import "ASVideoNode.h" #import "ASDefaultPlayButton.h" - @interface ASVideoNode () { ASDN::RecursiveMutex _lock; @@ -426,3 +425,4 @@ } @end +#endif From ae965ae9401611f8b3cf3982c9f24ec6f8ae6a36 Mon Sep 17 00:00:00 2001 From: aaronschubert0 Date: Tue, 23 Feb 2016 10:29:43 +0000 Subject: [PATCH 04/16] Disable PINRemoteImage Probably won't work but easier than writing support for PINRemoteImage --- AsyncDisplayKit.podspec | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index d2c525fc5e..bcc1cbf29b 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -49,14 +49,14 @@ Pod::Spec.new do |spec| ] end - spec.subspec 'PINRemoteImage' do |pin| - pin.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) PIN_REMOTE_IMAGE=1' } - pin.dependency 'PINRemoteImage/iOS', '>= 2' - pin.dependency 'AsyncDisplayKit/ASDealloc2MainObject' - end + # spec.subspec 'PINRemoteImage' do |pin| + # pin.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) PIN_REMOTE_IMAGE=1' } + # pin.dependency 'PINRemoteImage/iOS', '>= 2' + # pin.dependency 'AsyncDisplayKit/ASDealloc2MainObject' + # end # Include optional FLAnimatedImage module - spec.default_subspec = 'PINRemoteImage' + //spec.default_subspec = 'PINRemoteImage' spec.social_media_url = 'https://twitter.com/fbOpenSource' spec.library = 'c++' From 99e578a56f36bcecf75e8f641c1e8912b4c34769 Mon Sep 17 00:00:00 2001 From: aaronschubert0 Date: Tue, 23 Feb 2016 10:30:51 +0000 Subject: [PATCH 05/16] Comment out PINRemoteImage Properly. --- AsyncDisplayKit.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index bcc1cbf29b..c7f0bf7a73 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -56,7 +56,7 @@ Pod::Spec.new do |spec| # end # Include optional FLAnimatedImage module - //spec.default_subspec = 'PINRemoteImage' + # spec.default_subspec = 'PINRemoteImage' spec.social_media_url = 'https://twitter.com/fbOpenSource' spec.library = 'c++' From 682903fba5f2fc5e109ce36bb9d96421b70b74ff Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Tue, 23 Feb 2016 10:36:53 +0000 Subject: [PATCH 06/16] Make ASDisplayNode unable to be focused by default. ASControlNode will be the new default. --- AsyncDisplayKit/ASDisplayNode.mm | 2 +- AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 6b39ea8848..01af3356e0 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -2602,7 +2602,7 @@ static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority"; - (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context { - return YES; + return NO; } - (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index dbbf6d504c..923e9763ee 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -104,7 +104,7 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo // Focus Engine - (BOOL)canBecomeFocused { - return YES; + return NO; } - (void)setNeedsFocusUpdate @@ -121,7 +121,7 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo - (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context { - return YES; + return NO; } - (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator From 5d367328d9dd76ed7398424f548115e7f3bb3a79 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Tue, 23 Feb 2016 10:39:32 +0000 Subject: [PATCH 07/16] Make ASControlNode the default focusable view. This is so that we can receive touch events that will manipulate the view. --- AsyncDisplayKit/ASControlNode.mm | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/AsyncDisplayKit/ASControlNode.mm b/AsyncDisplayKit/ASControlNode.mm index d4a0598f39..cd5c9e22fd 100644 --- a/AsyncDisplayKit/ASControlNode.mm +++ b/AsyncDisplayKit/ASControlNode.mm @@ -424,5 +424,16 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v - (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent { } +#if TARGET_OS_TV +#pragma mark - tvOS +- (BOOL)canBecomeFocused +{ + return YES; +} +- (BOOL)shouldUpdateFocusInContext:(nonnull UIFocusUpdateContext *)context +{ + return YES; +} +#endif @end From 4ad6d91a105b030eb0fde0740729595b4cfda7c3 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Tue, 23 Feb 2016 11:16:07 +0000 Subject: [PATCH 08/16] Add default focus response to ASControlNode --- AsyncDisplayKit/ASControlNode.mm | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/AsyncDisplayKit/ASControlNode.mm b/AsyncDisplayKit/ASControlNode.mm index cd5c9e22fd..cc36506e76 100644 --- a/AsyncDisplayKit/ASControlNode.mm +++ b/AsyncDisplayKit/ASControlNode.mm @@ -82,6 +82,9 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v // As we have no targets yet, we start off with user interaction off. When a target is added, it'll get turned back on. self.userInteractionEnabled = NO; +#if TARGET_OS_TV + [self addTarget:self action:@selector(updateUI) forControlEvents:ASControlNodeEventAllEvents]; +#endif return self; } @@ -426,6 +429,11 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v } #if TARGET_OS_TV #pragma mark - tvOS +- (void)updateUI +{ + NSLog(@"Update UI"); +} + - (BOOL)canBecomeFocused { return YES; @@ -435,5 +443,36 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v { return YES; } + +- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator +{ + NSLog(@"Focused"); + if (context.nextFocusedView && context.nextFocusedView == self.view) { + //Focused + [coordinator addCoordinatedAnimations:^{ + self.layer.shadowOffset = CGSizeMake(2, 10); + self.layer.shadowColor = [UIColor blackColor].CGColor; + self.layer.shadowRadius = 12.0; + self.layer.shadowOpacity = 0.45; + self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; + self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.1, 1.1); + } completion:^{ + + }]; + } else{ + //Not focused + [coordinator addCoordinatedAnimations:^{ + self.layer.shadowOffset = CGSizeZero; + self.layer.shadowColor = [UIColor blackColor].CGColor; + self.layer.shadowRadius = 0; + self.layer.shadowOpacity = 0; + self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; + self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); + } completion:^{ + + }]; + + } +} #endif @end From d9cde1f08c8a202f2946d15a72a929ebb77c11b7 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Tue, 23 Feb 2016 12:18:33 +0000 Subject: [PATCH 09/16] [tvOS] Added a default touch down animation for ASControlNode --- AsyncDisplayKit/ASControlNode.h | 3 + AsyncDisplayKit/ASControlNode.mm | 160 ++++++++++++++++++------------- 2 files changed, 99 insertions(+), 64 deletions(-) diff --git a/AsyncDisplayKit/ASControlNode.h b/AsyncDisplayKit/ASControlNode.h index 9f5c37e343..c1e13c2ee1 100644 --- a/AsyncDisplayKit/ASControlNode.h +++ b/AsyncDisplayKit/ASControlNode.h @@ -30,6 +30,9 @@ typedef NS_OPTIONS(NSUInteger, ASControlNodeEvent) ASControlNodeEventTouchUpOutside = 1 << 5, /** A system event canceling the current touches for the control node. */ ASControlNodeEventTouchCancel = 1 << 6, + /** A system event when the Play/Pause button on the Apple TV remote is pressed. */ + ASControlNodeEventPrimaryActionTriggered = 1 << 13, + /** All events, including system events. */ ASControlNodeEventAllEvents = 0xFFFFFFFF }; diff --git a/AsyncDisplayKit/ASControlNode.mm b/AsyncDisplayKit/ASControlNode.mm index cc36506e76..7e0df9e670 100644 --- a/AsyncDisplayKit/ASControlNode.mm +++ b/AsyncDisplayKit/ASControlNode.mm @@ -27,11 +27,11 @@ // Control Attributes BOOL _enabled; BOOL _highlighted; - + // Tracking BOOL _tracking; BOOL _touchInside; - + // Target Messages. /* The table structure is as follows: @@ -77,26 +77,35 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v { if (!(self = [super init])) return nil; - + _enabled = YES; - + // As we have no targets yet, we start off with user interaction off. When a target is added, it'll get turned back on. self.userInteractionEnabled = NO; -#if TARGET_OS_TV - [self addTarget:self action:@selector(updateUI) forControlEvents:ASControlNodeEventAllEvents]; -#endif + return self; } +- (void)didLoad +{ +#if TARGET_OS_TV + // [self addTarget:self action:@selector(updateUI) forControlEvents:ASControlNodeEventPrimaryActionTriggered]; + self.userInteractionEnabled = YES; + UITapGestureRecognizer *tapGestureRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(pressDown)]; + tapGestureRec.allowedPressTypes = @[@(UIPressTypeSelect)]; + [self.view addGestureRecognizer:tapGestureRec]; +#endif +} + #pragma mark - ASDisplayNode Overrides - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // If we're not interested in touches, we have nothing to do. if (!self.enabled) return; - + ASControlNodeEvent controlEventMask = 0; - + // If we get more than one touch down on us, cancel. // Additionally, if we're already tracking a touch, a second touch beginning is cause for cancellation. if ([touches count] > 1 || self.tracking) @@ -110,18 +119,18 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v { // Otherwise, begin tracking. self.tracking = YES; - + // No need to check bounds on touchesBegan as we wouldn't get the call if it wasn't in our bounds. self.touchInside = YES; self.highlighted = YES; - + UITouch *theTouch = [touches anyObject]; [self beginTrackingWithTouch:theTouch withEvent:event]; - + // Send the appropriate touch-down control event depending on how many times we've been tapped. controlEventMask |= (theTouch.tapCount == 1) ? ASControlNodeEventTouchDown : ASControlNodeEventTouchDownRepeat; } - + [self sendActionsForControlEvents:controlEventMask withEvent:event]; } @@ -130,41 +139,42 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v // If we're not interested in touches, we have nothing to do. if (!self.enabled) return; - + NSParameterAssert([touches count] == 1); UITouch *theTouch = [touches anyObject]; CGPoint touchLocation = [theTouch locationInView:self.view]; - + // Update our touchInside state. BOOL dragIsInsideBounds = [self pointInside:touchLocation withEvent:nil]; - + // Update our highlighted state. CGRect expandedBounds = CGRectInset(self.view.bounds, kASControlNodeExpandedInset, kASControlNodeExpandedInset); BOOL dragIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation); self.touchInside = dragIsInsideExpandedBounds; self.highlighted = dragIsInsideExpandedBounds; - + // Note we are continuing to track the touch. [self continueTrackingWithTouch:theTouch withEvent:event]; - + [self sendActionsForControlEvents:(dragIsInsideBounds ? ASControlNodeEventTouchDragInside : ASControlNodeEventTouchDragOutside) withEvent:event]; } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { + NSLog(@"Touches Cancelled"); // If we're not interested in touches, we have nothing to do. if (!self.enabled) return; - + // We're no longer tracking and there is no touch to be inside. self.tracking = NO; self.touchInside = NO; self.highlighted = NO; - + // Note that we've cancelled tracking. [self cancelTrackingWithEvent:event]; - + // Send the cancel event. [self sendActionsForControlEvents:ASControlNodeEventTouchCancel withEvent:event]; @@ -175,7 +185,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v // If we're not interested in touches, we have nothing to do. if (!self.enabled) return; - + // On iPhone 6s, iOS 9.2 (and maybe other versions) sometimes calls -touchesEnded:withEvent: // twice on the view for one call to -touchesBegan:withEvent:. On ASControlNode, it used to // trigger an action twice unintentionally. Now, we ignore that event if we're not in a tracking @@ -183,23 +193,23 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v // It might be related to that issue: http://www.openradar.me/22910171 if (!self.tracking) return; - + NSParameterAssert([touches count] == 1); UITouch *theTouch = [touches anyObject]; CGPoint touchLocation = [theTouch locationInView:self.view]; - + // Update state. self.tracking = NO; self.touchInside = NO; self.highlighted = NO; - + // Note that we've ended tracking. [self endTrackingWithTouch:theTouch withEvent:event]; - + // Send the appropriate touch-up control event. CGRect expandedBounds = CGRectInset(self.view.bounds, kASControlNodeExpandedInset, kASControlNodeExpandedInset); BOOL touchUpIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation); - + [self sendActionsForControlEvents:(touchUpIsInsideExpandedBounds ? ASControlNodeEventTouchUpInside : ASControlNodeEventTouchUpOutside) withEvent:event]; } @@ -212,7 +222,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v // Allow double-tap gestures return tapRecognizer.numberOfTapsRequired != 1; } - + // Otherwise, go ahead. :] return YES; } @@ -228,11 +238,11 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v // Convert nil to [NSNull null] so that it can be used as a key for NSMapTable. if (!target) target = [NSNull null]; - + if (!_controlEventDispatchTable) { _controlEventDispatchTable = [[NSMutableDictionary alloc] initWithCapacity:kASControlNodeEventDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries. } - + // Enumerate the events in the mask, adding the target-action pair for each control event included in controlEventMask _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^ (ASControlNodeEvent controlEvent) @@ -269,14 +279,14 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v { NSParameterAssert(target); NSParameterAssert(controlEvent != 0 && controlEvent != ASControlNodeEventAllEvents); - + ASDN::MutexLocker l(_controlLock); // Grab the event dispatch table for this event. NSMapTable *eventDispatchTable = [_controlEventDispatchTable objectForKey:_ASControlNodeEventKeyForControlEvent(controlEvent)]; if (!eventDispatchTable) return nil; - + // Return the actions for this target. return [eventDispatchTable objectForKey:target]; } @@ -286,7 +296,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v ASDN::MutexLocker l(_controlLock); NSMutableSet *targets = [[NSMutableSet alloc] init]; - + // Look at each event... for (NSMapTable *eventDispatchTable in [_controlEventDispatchTable allValues]) { @@ -294,7 +304,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v for (id target in eventDispatchTable) [targets addObject:target]; } - + return targets; } @@ -303,7 +313,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v NSParameterAssert(controlEventMask != 0); ASDN::MutexLocker l(_controlLock); - + // Enumerate the events in the mask, removing the target-action pair for each control event included in controlEventMask. _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^ (ASControlNodeEvent controlEvent) @@ -357,7 +367,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v NSParameterAssert(controlEvents != 0); ASDN::MutexLocker l(_controlLock); - + // Enumerate the events in the mask, invoking the target-action pairs for each. _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEvents, ^ (ASControlNodeEvent controlEvent) @@ -429,9 +439,17 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v } #if TARGET_OS_TV #pragma mark - tvOS -- (void)updateUI +- (void)pressDown { - NSLog(@"Update UI"); + [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationCurveLinear animations:^{ + [self setPressedState]; + } completion:^(BOOL finished) { + if (finished) { + [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationCurveLinear animations:^{ + [self setFocusedState]; + } completion:nil]; + } + }]; } - (BOOL)canBecomeFocused @@ -446,33 +464,47 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v - (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator { - NSLog(@"Focused"); - if (context.nextFocusedView && context.nextFocusedView == self.view) { - //Focused - [coordinator addCoordinatedAnimations:^{ - self.layer.shadowOffset = CGSizeMake(2, 10); - self.layer.shadowColor = [UIColor blackColor].CGColor; - self.layer.shadowRadius = 12.0; - self.layer.shadowOpacity = 0.45; - self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; - self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.1, 1.1); - } completion:^{ - - }]; - } else{ - //Not focused - [coordinator addCoordinatedAnimations:^{ - self.layer.shadowOffset = CGSizeZero; - self.layer.shadowColor = [UIColor blackColor].CGColor; - self.layer.shadowRadius = 0; - self.layer.shadowOpacity = 0; - self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; - self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); - } completion:^{ - - }]; + if (context.nextFocusedView && context.nextFocusedView == self.view) { + //Focused + [coordinator addCoordinatedAnimations:^{ + [self setFocusedState]; + } completion:nil]; + } else{ + //Not focused + [coordinator addCoordinatedAnimations:^{ + [self setDefaultState]; + } completion:nil]; + } +} - } +- (void)setFocusedState +{ + self.layer.shadowOffset = CGSizeMake(2, 10); + self.layer.shadowColor = [UIColor blackColor].CGColor; + self.layer.shadowRadius = 12.0; + self.layer.shadowOpacity = 0.45; + self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; + self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.1, 1.1); +} + +- (void)setPressedState +{ + self.layer.shadowOffset = CGSizeMake(2, 2); + self.layer.shadowColor = [UIColor blackColor].CGColor; + self.layer.shadowRadius = 12.0; + self.layer.shadowOpacity = 0.45; + self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; + self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); +} + +- (void)setDefaultState +{ + self.layer.shadowOffset = CGSizeZero; + self.layer.shadowColor = [UIColor blackColor].CGColor; + self.layer.shadowRadius = 0; + self.layer.shadowOpacity = 0; + self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; + self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); } #endif @end From 3d6f6766e9b3535b9242ac8125c75d9b04bd16cf Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Wed, 24 Feb 2016 13:58:11 +0000 Subject: [PATCH 10/16] [tvOS] Implement a default behaviour of ASImageNode, trying to match the behaviour of UIImageView. Still need to add shine. --- AsyncDisplayKit/ASControlNode.h | 7 +- AsyncDisplayKit/ASImageNode.mm | 147 ++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASControlNode.h b/AsyncDisplayKit/ASControlNode.h index c1e13c2ee1..50eee12902 100644 --- a/AsyncDisplayKit/ASControlNode.h +++ b/AsyncDisplayKit/ASControlNode.h @@ -122,7 +122,12 @@ typedef NS_OPTIONS(NSUInteger, ASControlState) { @param event The event which triggered these control actions. May be nil. */ - (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(nullable UIEvent *)event; - +#if TARGET_OS_TV +/** + @abstract How the node looks when it isn't focused. Exposed here so that subclasses can override. + */ +- (void)setDefaultState; +#endif @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index 0a9c5cbbe6..d97cd3dfe7 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -64,6 +64,11 @@ void (^_displayCompletionBlock)(BOOL canceled); ASDN::RecursiveMutex _imageLock; +#if TARGET_OS_TV + //tvOS + BOOL isDefaultState; +#endif + // Cropping. BOOL _cropEnabled; // Defaults to YES. BOOL _forceUpscaling; //Defaults to NO. @@ -410,6 +415,148 @@ _imageModificationBlock = imageModificationBlock; } + +#if TARGET_OS_TV +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event +{ + [super touchesBegan:touches withEvent:event]; + isDefaultState = NO; + CGSize targetShadowOffset = CGSizeMake(0.0, self.bounds.size.height/8); + [self.layer removeAllAnimations]; + [CATransaction begin]; + [CATransaction setCompletionBlock:^{ + self.layer.shadowOffset = targetShadowOffset; + }]; + + CABasicAnimation *shadowOffsetAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOffset"]; + shadowOffsetAnimation.toValue = [NSValue valueWithCGSize:targetShadowOffset]; + shadowOffsetAnimation.duration = 0.4; + shadowOffsetAnimation.removedOnCompletion = NO; + shadowOffsetAnimation.fillMode = kCAFillModeForwards; + shadowOffsetAnimation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeOut"]; + [self.layer addAnimation:shadowOffsetAnimation forKey:@"shadowOffset"]; + [CATransaction commit]; + + CABasicAnimation *shadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"]; + shadowOpacityAnimation.toValue = [NSNumber numberWithFloat:0.45]; + shadowOpacityAnimation.duration = 0.4; + shadowOpacityAnimation.removedOnCompletion = false; + shadowOpacityAnimation.fillMode = kCAFillModeForwards; + shadowOpacityAnimation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeOut"]; + [self.layer addAnimation:shadowOpacityAnimation forKey:@"shadowOpacityAnimation"]; + + self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25); + + [CATransaction commit]; +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + if (!isDefaultState) { + UITouch *touch = [touches anyObject]; + // Get the specific point that was touched + CGPoint point = [touch locationInView:self.view]; + float tilt = 0; + float yaw = 0; + BOOL topHalf = NO; + if (point.y > CGRectGetHeight(self.view.frame)) { + tilt = 15; + } else if (point.y < -CGRectGetHeight(self.view.frame)) { + tilt = -15; + } else { + tilt = (point.y/CGRectGetHeight(self.view.frame))*15; + } + if (tilt < 0) { + topHalf = YES; + } + + if (point.x > CGRectGetWidth(self.view.frame)) { + yaw = 10; + } else if (point.x < -CGRectGetWidth(self.view.frame)) { + yaw = -10; + } else { + yaw = (point.x/CGRectGetWidth(self.view.frame))*10; + } + if (!topHalf) { + if (yaw > 0) { + yaw = -yaw; + } else { + yaw = fabsf(yaw); + } + } + + CATransform3D tiltTransform = CATransform3DMakeRotation([self degressToRadians:tilt],1.0,0.0,0.0); + CATransform3D yawTransform = CATransform3DMakeRotation([self degressToRadians:yaw],0.0,1.0,0.0); + CATransform3D transform = CATransform3DConcat(tiltTransform, yawTransform); + CATransform3D scaleAndTransform = CATransform3DConcat(transform, CATransform3DMakeAffineTransform(CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25))); + + [UIView animateWithDuration:0.5 animations:^{ + self.view.layer.transform = scaleAndTransform; + }]; + } else { + [self setDefaultState]; + } +} + + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event +{ + [self finishTouches]; +} + +- (void)finishTouches +{ + if (!isDefaultState) { + CGSize targetShadowOffset = CGSizeMake(0.0, self.bounds.size.height/8); + CATransform3D targetScaleTransform = CATransform3DMakeScale(1.2, 1.2, 1.2); + [CATransaction begin]; + [CATransaction setCompletionBlock:^{ + self.layer.shadowOffset = targetShadowOffset; + }]; + [CATransaction commit]; + + [UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ + self.view.layer.transform = targetScaleTransform; + } completion:^(BOOL finished) { + if (finished) { + [self.layer removeAnimationForKey:@"shadowOffset"]; + [self.layer removeAnimationForKey:@"shadowOpacity"]; + } + }]; + } else { + [self setDefaultState]; + } +} + +- (void)setFocusedState +{ + self.layer.shadowOffset = CGSizeMake(2, 10); + self.layer.shadowColor = [UIColor blackColor].CGColor; + self.layer.shadowRadius = 12.0; + self.layer.shadowOpacity = 0.45; + self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; + self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25); +} + +- (void)setDefaultState +{ + self.view.transform = CGAffineTransformIdentity; + self.layer.shadowOpacity = 0; + self.layer.shadowOffset = CGSizeZero; + self.layer.shadowRadius = 0; + self.layer.shadowPath = nil; + [self.layer removeAnimationForKey:@"shadowOffset"]; + [self.layer removeAnimationForKey:@"shadowOpacity"]; + isDefaultState = YES; +} + + +- (float)degressToRadians:(float)value +{ + return value * M_PI / 180; +} + +#endif + @end From 58f083cef905f07db738402a49387bbdf4024049 Mon Sep 17 00:00:00 2001 From: aaronschubert0 Date: Thu, 25 Feb 2016 08:26:19 +0000 Subject: [PATCH 11/16] [tvOS] Revert pod spec changes, to get ready for merge. --- AsyncDisplayKit.podspec | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index c7f0bf7a73..2a06115c4a 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -49,14 +49,14 @@ Pod::Spec.new do |spec| ] end - # spec.subspec 'PINRemoteImage' do |pin| - # pin.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) PIN_REMOTE_IMAGE=1' } - # pin.dependency 'PINRemoteImage/iOS', '>= 2' - # pin.dependency 'AsyncDisplayKit/ASDealloc2MainObject' - # end + spec.subspec 'PINRemoteImage' do |pin| + pin.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) PIN_REMOTE_IMAGE=1' } + pin.dependency 'PINRemoteImage/iOS', '>= 2' + pin.dependency 'AsyncDisplayKit/ASDealloc2MainObject' + end # Include optional FLAnimatedImage module - # spec.default_subspec = 'PINRemoteImage' + spec.default_subspec = 'PINRemoteImage' spec.social_media_url = 'https://twitter.com/fbOpenSource' spec.library = 'c++' @@ -67,5 +67,5 @@ Pod::Spec.new do |spec| spec.ios.deployment_target = '7.0' # tvOS not recognized by older versions of Cocoapods - add this only after tvOS support complete. - spec.tvos.deployment_target = '9.0' + # spec.tvos.deployment_target = '9.0' end From f9bf40204717f62da39d5debdf7a8aae476cb809 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Thu, 25 Feb 2016 09:06:11 +0000 Subject: [PATCH 12/16] [tvOS] Clean up and document parts of code. --- AsyncDisplayKit/ASButtonNode.mm | 37 +++ AsyncDisplayKit/ASButtonNode.mm.orig | 469 --------------------------- AsyncDisplayKit/ASControlNode.mm | 4 +- AsyncDisplayKit/ASImageNode.mm | 86 +++-- 4 files changed, 95 insertions(+), 501 deletions(-) delete mode 100644 AsyncDisplayKit/ASButtonNode.mm.orig diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index 5ac473570b..03c3ab7b0c 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -223,6 +223,43 @@ [self setNeedsLayout]; } +- (ASVerticalAlignment)contentVerticalAlignment +{ + ASDN::MutexLocker l(_propertyLock); + return _contentVerticalAlignment; +} + +- (void)setContentVerticalAlignment:(ASVerticalAlignment)contentVerticalAlignment +{ + ASDN::MutexLocker l(_propertyLock); + _contentVerticalAlignment = contentVerticalAlignment; +} + +- (ASHorizontalAlignment)contentHorizontalAlignment +{ + ASDN::MutexLocker l(_propertyLock); + return _contentHorizontalAlignment; +} + +- (void)setContentHorizontalAlignment:(ASHorizontalAlignment)contentHorizontalAlignment +{ + ASDN::MutexLocker l(_propertyLock); + _contentHorizontalAlignment = contentHorizontalAlignment; +} + +- (UIEdgeInsets)contentEdgeInsets +{ + ASDN::MutexLocker l(_propertyLock); + return _contentEdgeInsets; +} + +- (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets +{ + ASDN::MutexLocker l(_propertyLock); + _contentEdgeInsets = contentEdgeInsets; +} + + #if TARGET_OS_IOS - (void)setTitle:(NSString *)title withFont:(UIFont *)font withColor:(UIColor *)color forState:(ASControlState)state { diff --git a/AsyncDisplayKit/ASButtonNode.mm.orig b/AsyncDisplayKit/ASButtonNode.mm.orig deleted file mode 100644 index 9a41349cda..0000000000 --- a/AsyncDisplayKit/ASButtonNode.mm.orig +++ /dev/null @@ -1,469 +0,0 @@ -/* 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 "ASButtonNode.h" -#import "ASStackLayoutSpec.h" -#import "ASThread.h" -#import "ASDisplayNode+Subclasses.h" -#import "ASBackgroundLayoutSpec.h" -#import "ASInsetLayoutSpec.h" -#import "ASDisplayNode+Beta.h" - -@interface ASButtonNode () -{ - ASDN::RecursiveMutex _propertyLock; - - NSAttributedString *_normalAttributedTitle; - NSAttributedString *_highlightedAttributedTitle; - NSAttributedString *_selectedAttributedTitle; - NSAttributedString *_disabledAttributedTitle; - - UIImage *_normalImage; - UIImage *_highlightedImage; - UIImage *_selectedImage; - UIImage *_disabledImage; - - UIImage *_normalBackgroundImage; - UIImage *_highlightedBackgroundImage; - UIImage *_selectedBackgroundImage; - UIImage *_disabledBackgroundImage; -} - -@end - -@implementation ASButtonNode - -@synthesize contentSpacing = _contentSpacing; -@synthesize laysOutHorizontally = _laysOutHorizontally; -@synthesize contentVerticalAlignment = _contentVerticalAlignment; -@synthesize contentHorizontalAlignment = _contentHorizontalAlignment; -@synthesize contentEdgeInsets = _contentEdgeInsets; -@synthesize titleNode = _titleNode; -@synthesize imageNode = _imageNode; -@synthesize backgroundImageNode = _backgroundImageNode; - -- (instancetype)init -{ - if (self = [super init]) { - self.usesImplicitHierarchyManagement = YES; - - _contentSpacing = 8.0; - _laysOutHorizontally = YES; - _contentHorizontalAlignment = ASAlignmentMiddle; - _contentVerticalAlignment = ASAlignmentCenter; - _contentEdgeInsets = UIEdgeInsetsZero; - } - return self; -} - -- (ASTextNode *)titleNode -{ - if (!_titleNode) { - _titleNode = [[ASTextNode alloc] init]; - [_titleNode setLayerBacked:YES]; - } - return _titleNode; -} - -- (ASImageNode *)imageNode -{ - if (!_imageNode) { - _imageNode = [[ASImageNode alloc] init]; - [_imageNode setLayerBacked:YES]; - [_titleNode setFlexShrink:YES]; - } - return _imageNode; -} - -- (ASImageNode *)backgroundImageNode -{ - if (!_backgroundImageNode) { - _backgroundImageNode = [[ASImageNode alloc] init]; - [_backgroundImageNode setLayerBacked:YES]; - [_backgroundImageNode setContentMode:UIViewContentModeScaleToFill]; - } - return _backgroundImageNode; -} - -- (void)setLayerBacked:(BOOL)layerBacked -{ - ASDisplayNodeAssert(!layerBacked, @"ASButtonNode must not be layer backed!"); - [super setLayerBacked:layerBacked]; -} - -- (void)setEnabled:(BOOL)enabled -{ - [super setEnabled:enabled]; - [self updateButtonContent]; -} - -- (void)setHighlighted:(BOOL)highlighted -{ - [super setHighlighted:highlighted]; - [self updateButtonContent]; -} - -- (void)setSelected:(BOOL)selected -{ - [super setSelected:selected]; - [self updateButtonContent]; -} - -- (void)updateButtonContent -{ - [self updateBackgroundImage]; - [self updateImage]; - [self updateTitle]; -} - -- (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously -{ - [super setDisplaysAsynchronously:displaysAsynchronously]; - [self.backgroundImageNode setDisplaysAsynchronously:displaysAsynchronously]; - [self.imageNode setDisplaysAsynchronously:displaysAsynchronously]; - [self.titleNode setDisplaysAsynchronously:displaysAsynchronously]; -} - -- (void)updateImage -{ - ASDN::MutexLocker l(_propertyLock); - - UIImage *newImage; - if (self.enabled == NO && _disabledImage) { - newImage = _disabledImage; - } else if (self.highlighted && _highlightedImage) { - newImage = _highlightedImage; - } else if (self.selected && _selectedImage) { - newImage = _selectedImage; - } else { - newImage = _normalImage; - } - - if ((_imageNode != nil || newImage != nil) && newImage != self.imageNode.image) { - _imageNode.image = newImage; - [self setNeedsLayout]; - } -} - -- (void)updateTitle -{ - ASDN::MutexLocker l(_propertyLock); - NSAttributedString *newTitle; - if (self.enabled == NO && _disabledAttributedTitle) { - newTitle = _disabledAttributedTitle; - } else if (self.highlighted && _highlightedAttributedTitle) { - newTitle = _highlightedAttributedTitle; - } else if (self.selected && _selectedAttributedTitle) { - newTitle = _selectedAttributedTitle; - } else { - newTitle = _normalAttributedTitle; - } - - if ((_titleNode != nil || newTitle.length > 0) && newTitle != self.titleNode.attributedString) { - _titleNode.attributedString = newTitle; - [self setNeedsLayout]; - } -} - -- (void)updateBackgroundImage -{ - ASDN::MutexLocker l(_propertyLock); - - UIImage *newImage; - if (self.enabled == NO && _disabledBackgroundImage) { - newImage = _disabledBackgroundImage; - } else if (self.highlighted && _highlightedBackgroundImage) { - newImage = _highlightedBackgroundImage; - } else if (self.selected && _selectedBackgroundImage) { - newImage = _selectedBackgroundImage; - } else { - newImage = _normalBackgroundImage; - } - - if ((_backgroundImageNode != nil || newImage != nil) && newImage != self.backgroundImageNode.image) { - _backgroundImageNode.image = newImage; - [self setNeedsLayout]; - } -} - -- (CGFloat)contentSpacing -{ - ASDN::MutexLocker l(_propertyLock); - return _contentSpacing; -} - -- (void)setContentSpacing:(CGFloat)contentSpacing -{ - ASDN::MutexLocker l(_propertyLock); - if (contentSpacing == _contentSpacing) - return; - - _contentSpacing = contentSpacing; - [self setNeedsLayout]; -} - -- (BOOL)laysOutHorizontally -{ - ASDN::MutexLocker l(_propertyLock); - return _laysOutHorizontally; -} - -- (void)setLaysOutHorizontally:(BOOL)laysOutHorizontally -{ - ASDN::MutexLocker l(_propertyLock); - if (laysOutHorizontally == _laysOutHorizontally) - return; - - _laysOutHorizontally = laysOutHorizontally; - [self setNeedsLayout]; -} - -<<<<<<< HEAD -#if TARGET_OS_IOS -======= -- (ASVerticalAlignment)contentVerticalAlignment -{ - ASDN::MutexLocker l(_propertyLock); - return _contentVerticalAlignment; -} - -- (void)setContentVerticalAlignment:(ASVerticalAlignment)contentVerticalAlignment -{ - ASDN::MutexLocker l(_propertyLock); - _contentVerticalAlignment = contentVerticalAlignment; -} - -- (ASHorizontalAlignment)contentHorizontalAlignment -{ - ASDN::MutexLocker l(_propertyLock); - return _contentHorizontalAlignment; -} - -- (void)setContentHorizontalAlignment:(ASHorizontalAlignment)contentHorizontalAlignment -{ - ASDN::MutexLocker l(_propertyLock); - _contentHorizontalAlignment = contentHorizontalAlignment; -} - -- (UIEdgeInsets)contentEdgeInsets -{ - ASDN::MutexLocker l(_propertyLock); - return _contentEdgeInsets; -} - -- (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets -{ - ASDN::MutexLocker l(_propertyLock); - _contentEdgeInsets = contentEdgeInsets; -} - ->>>>>>> upstream/master -- (void)setTitle:(NSString *)title withFont:(UIFont *)font withColor:(UIColor *)color forState:(ASControlState)state -{ - NSDictionary *attributes = @{ - NSFontAttributeName: font ? font :[UIFont systemFontOfSize:[UIFont buttonFontSize]], - NSForegroundColorAttributeName : color ? color : [UIColor blackColor] - }; - - NSAttributedString *string = [[NSAttributedString alloc] initWithString:title - attributes:attributes]; - [self setAttributedTitle:string forState:state]; -} -#endif - -- (NSAttributedString *)attributedTitleForState:(ASControlState)state -{ - ASDN::MutexLocker l(_propertyLock); - switch (state) { - case ASControlStateNormal: - return _normalAttributedTitle; - - case ASControlStateHighlighted: - return _highlightedAttributedTitle; - - case ASControlStateSelected: - return _selectedAttributedTitle; - - case ASControlStateDisabled: - return _disabledAttributedTitle; - - default: - return _normalAttributedTitle; - } -} - -- (void)setAttributedTitle:(NSAttributedString *)title forState:(ASControlState)state -{ - ASDN::MutexLocker l(_propertyLock); - switch (state) { - case ASControlStateNormal: - _normalAttributedTitle = [title copy]; - break; - - case ASControlStateHighlighted: - _highlightedAttributedTitle = [title copy]; - break; - - case ASControlStateSelected: - _selectedAttributedTitle = [title copy]; - break; - - case ASControlStateDisabled: - _disabledAttributedTitle = [title copy]; - break; - - default: - break; - } - [self updateTitle]; -} - -- (UIImage *)imageForState:(ASControlState)state -{ - ASDN::MutexLocker l(_propertyLock); - switch (state) { - case ASControlStateNormal: - return _normalImage; - - case ASControlStateHighlighted: - return _highlightedImage; - - case ASControlStateSelected: - return _selectedImage; - - case ASControlStateDisabled: - return _disabledImage; - - default: - return _normalImage; - } -} - -- (void)setImage:(UIImage *)image forState:(ASControlState)state -{ - ASDN::MutexLocker l(_propertyLock); - switch (state) { - case ASControlStateNormal: - _normalImage = image; - break; - - case ASControlStateHighlighted: - _highlightedImage = image; - break; - - case ASControlStateSelected: - _selectedImage = image; - break; - - case ASControlStateDisabled: - _disabledImage = image; - break; - - default: - break; - } - [self updateImage]; -} - -- (void)setBackgroundImage:(UIImage *)image forState:(ASControlState)state -{ - ASDN::MutexLocker l(_propertyLock); - switch (state) { - case ASControlStateNormal: - _normalBackgroundImage = image; - break; - - case ASControlStateHighlighted: - _highlightedBackgroundImage = image; - break; - - case ASControlStateSelected: - _selectedBackgroundImage = image; - break; - - case ASControlStateDisabled: - _disabledBackgroundImage = image; - break; - - default: - break; - } - [self updateBackgroundImage]; -} - -- (UIImage *)backgroundImageForState:(ASControlState)state -{ - ASDN::MutexLocker l(_propertyLock); - switch (state) { - case ASControlStateNormal: - return _normalBackgroundImage; - - case ASControlStateHighlighted: - return _highlightedBackgroundImage; - - case ASControlStateSelected: - return _selectedBackgroundImage; - - case ASControlStateDisabled: - return _disabledBackgroundImage; - - default: - return _normalBackgroundImage; - } - -} - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - UIEdgeInsets contentEdgeInsets; - ASLayoutSpec *spec; - ASStackLayoutSpec *stack = [[ASStackLayoutSpec alloc] init]; - { - ASDN::MutexLocker l(_propertyLock); - stack.direction = _laysOutHorizontally ? ASStackLayoutDirectionHorizontal : ASStackLayoutDirectionVertical; - stack.spacing = _contentSpacing; - stack.horizontalAlignment = _contentHorizontalAlignment; - stack.verticalAlignment = _contentVerticalAlignment; - - contentEdgeInsets = _contentEdgeInsets; - } - - NSMutableArray *children = [[NSMutableArray alloc] initWithCapacity:2]; - if (_imageNode.image) { - [children addObject:_imageNode]; - } - - if (_titleNode.attributedString.length > 0) { - [children addObject:_titleNode]; - } - - stack.children = children; - - spec = stack; - - if (UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, contentEdgeInsets) == NO) { - spec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:contentEdgeInsets child:spec]; - } - - if (_backgroundImageNode.image) { - spec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:spec - background:_backgroundImageNode]; - } - - return spec; -} - -- (void)layout -{ - [super layout]; - _backgroundImageNode.hidden = (_backgroundImageNode.image == nil); - _imageNode.hidden = (_imageNode.image == nil); - _titleNode.hidden = (_titleNode.attributedString.length == 0); -} - -@end diff --git a/AsyncDisplayKit/ASControlNode.mm b/AsyncDisplayKit/ASControlNode.mm index 7e0df9e670..32a2c8288f 100644 --- a/AsyncDisplayKit/ASControlNode.mm +++ b/AsyncDisplayKit/ASControlNode.mm @@ -89,7 +89,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v - (void)didLoad { #if TARGET_OS_TV - // [self addTarget:self action:@selector(updateUI) forControlEvents:ASControlNodeEventPrimaryActionTriggered]; + //On tvOS all control views, such as buttons, interact with the focus system even if they don't have a target set on them. Here we add our own internal tap gesture to handle this behaviour. self.userInteractionEnabled = YES; UITapGestureRecognizer *tapGestureRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(pressDown)]; tapGestureRec.allowedPressTypes = @[@(UIPressTypeSelect)]; @@ -162,7 +162,6 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { - NSLog(@"Touches Cancelled"); // If we're not interested in touches, we have nothing to do. if (!self.enabled) return; @@ -464,6 +463,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v - (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator { + //FIXME: This is never valid inside an ASCellNode if (context.nextFocusedView && context.nextFocusedView == self.view) { //Focused [coordinator addCoordinatedAnimations:^{ diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index d97cd3dfe7..ec18a4d49c 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -421,11 +421,14 @@ { [super touchesBegan:touches withEvent:event]; isDefaultState = NO; + UIView *view = [self getView]; + CALayer *layer = view.layer; + CGSize targetShadowOffset = CGSizeMake(0.0, self.bounds.size.height/8); - [self.layer removeAllAnimations]; + [layer removeAllAnimations]; [CATransaction begin]; [CATransaction setCompletionBlock:^{ - self.layer.shadowOffset = targetShadowOffset; + layer.shadowOffset = targetShadowOffset; }]; CABasicAnimation *shadowOffsetAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOffset"]; @@ -434,7 +437,7 @@ shadowOffsetAnimation.removedOnCompletion = NO; shadowOffsetAnimation.fillMode = kCAFillModeForwards; shadowOffsetAnimation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeOut"]; - [self.layer addAnimation:shadowOffsetAnimation forKey:@"shadowOffset"]; + [layer addAnimation:shadowOffsetAnimation forKey:@"shadowOffset"]; [CATransaction commit]; CABasicAnimation *shadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"]; @@ -443,29 +446,35 @@ shadowOpacityAnimation.removedOnCompletion = false; shadowOpacityAnimation.fillMode = kCAFillModeForwards; shadowOpacityAnimation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeOut"]; - [self.layer addAnimation:shadowOpacityAnimation forKey:@"shadowOpacityAnimation"]; + [layer addAnimation:shadowOpacityAnimation forKey:@"shadowOpacityAnimation"]; - self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25); + view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25); [CATransaction commit]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { + [super touchesMoved:touches withEvent:event]; + if (!isDefaultState) { + UIView *view = [self getView]; + UITouch *touch = [touches anyObject]; // Get the specific point that was touched + // This is quite messy in it's current state so is not ready for production. The reason it is here is for others to contribute and to make it clear what is occuring. + // TODO: Clean up, and improve visuals. CGPoint point = [touch locationInView:self.view]; - float tilt = 0; + float pitch = 0; float yaw = 0; BOOL topHalf = NO; if (point.y > CGRectGetHeight(self.view.frame)) { - tilt = 15; + pitch = 15; } else if (point.y < -CGRectGetHeight(self.view.frame)) { - tilt = -15; + pitch = -15; } else { - tilt = (point.y/CGRectGetHeight(self.view.frame))*15; + pitch = (point.y/CGRectGetHeight(self.view.frame))*15; } - if (tilt < 0) { + if (pitch < 0) { topHalf = YES; } @@ -484,13 +493,13 @@ } } - CATransform3D tiltTransform = CATransform3DMakeRotation([self degressToRadians:tilt],1.0,0.0,0.0); + CATransform3D pitchTransform = CATransform3DMakeRotation([self degressToRadians:pitch],1.0,0.0,0.0); CATransform3D yawTransform = CATransform3DMakeRotation([self degressToRadians:yaw],0.0,1.0,0.0); - CATransform3D transform = CATransform3DConcat(tiltTransform, yawTransform); + CATransform3D transform = CATransform3DConcat(pitchTransform, yawTransform); CATransform3D scaleAndTransform = CATransform3DConcat(transform, CATransform3DMakeAffineTransform(CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25))); [UIView animateWithDuration:0.5 animations:^{ - self.view.layer.transform = scaleAndTransform; + view.layer.transform = scaleAndTransform; }]; } else { [self setDefaultState]; @@ -500,26 +509,30 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + [super touchesEnded:touches withEvent:event]; [self finishTouches]; } - (void)finishTouches { if (!isDefaultState) { + UIView *view = [self getView]; + CALayer *layer = view.layer; + CGSize targetShadowOffset = CGSizeMake(0.0, self.bounds.size.height/8); CATransform3D targetScaleTransform = CATransform3DMakeScale(1.2, 1.2, 1.2); [CATransaction begin]; [CATransaction setCompletionBlock:^{ - self.layer.shadowOffset = targetShadowOffset; + layer.shadowOffset = targetShadowOffset; }]; [CATransaction commit]; [UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ - self.view.layer.transform = targetScaleTransform; + view.layer.transform = targetScaleTransform; } completion:^(BOOL finished) { if (finished) { - [self.layer removeAnimationForKey:@"shadowOffset"]; - [self.layer removeAnimationForKey:@"shadowOpacity"]; + [layer removeAnimationForKey:@"shadowOffset"]; + [layer removeAnimationForKey:@"shadowOpacity"]; } }]; } else { @@ -529,26 +542,39 @@ - (void)setFocusedState { - self.layer.shadowOffset = CGSizeMake(2, 10); - self.layer.shadowColor = [UIColor blackColor].CGColor; - self.layer.shadowRadius = 12.0; - self.layer.shadowOpacity = 0.45; - self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; - self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25); + UIView *view = [self getView]; + CALayer *layer = view.layer; + layer.shadowOffset = CGSizeMake(2, 10); + layer.shadowColor = [UIColor blackColor].CGColor; + layer.shadowRadius = 12.0; + layer.shadowOpacity = 0.45; + layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; + view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25); } - (void)setDefaultState { - self.view.transform = CGAffineTransformIdentity; - self.layer.shadowOpacity = 0; - self.layer.shadowOffset = CGSizeZero; - self.layer.shadowRadius = 0; - self.layer.shadowPath = nil; - [self.layer removeAnimationForKey:@"shadowOffset"]; - [self.layer removeAnimationForKey:@"shadowOpacity"]; + UIView *view = [self getView]; + CALayer *layer = view.layer; + view.transform = CGAffineTransformIdentity; + layer.shadowOpacity = 0; + layer.shadowOffset = CGSizeZero; + layer.shadowRadius = 0; + layer.shadowPath = nil; + [layer removeAnimationForKey:@"shadowOffset"]; + [layer removeAnimationForKey:@"shadowOpacity"]; isDefaultState = YES; } +- (UIView *)getView +{ + UIView *view = self.view; + //If we are inside a ASCellNode, then we need to apply our focus effects to the ASCellNode view/layer rather than the ASImageNode view/layer. + if (CGSizeEqualToSize(self.view.superview.frame.size, self.view.frame.size) && self.view.superview.superview) { + view = self.view.superview.superview; + } + return view; +} - (float)degressToRadians:(float)value { From 26d9effdeca53711b9a5c648396c6bf615a58530 Mon Sep 17 00:00:00 2001 From: aaronschubert0 Date: Thu, 25 Feb 2016 09:10:47 +0000 Subject: [PATCH 13/16] [tvOS] Remove extra space so that there are no git differences. --- AsyncDisplayKit.podspec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index 2a06115c4a..4872c47f84 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -50,9 +50,9 @@ Pod::Spec.new do |spec| end spec.subspec 'PINRemoteImage' do |pin| - pin.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) PIN_REMOTE_IMAGE=1' } - pin.dependency 'PINRemoteImage/iOS', '>= 2' - pin.dependency 'AsyncDisplayKit/ASDealloc2MainObject' + pin.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) PIN_REMOTE_IMAGE=1' } + pin.dependency 'PINRemoteImage/iOS', '>= 2' + pin.dependency 'AsyncDisplayKit/ASDealloc2MainObject' end # Include optional FLAnimatedImage module From 88e4ec41304698056c10fe5beae323372c0e11d8 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Fri, 26 Feb 2016 09:40:14 +0000 Subject: [PATCH 14/16] [tvOS] Optimize shadow methods to use local variable. --- AsyncDisplayKit/ASControlNode.mm | 35 ++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/AsyncDisplayKit/ASControlNode.mm b/AsyncDisplayKit/ASControlNode.mm index 32a2c8288f..54a1c708f2 100644 --- a/AsyncDisplayKit/ASControlNode.mm +++ b/AsyncDisplayKit/ASControlNode.mm @@ -479,31 +479,36 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v - (void)setFocusedState { - self.layer.shadowOffset = CGSizeMake(2, 10); - self.layer.shadowColor = [UIColor blackColor].CGColor; - self.layer.shadowRadius = 12.0; - self.layer.shadowOpacity = 0.45; - self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; + CALayer *layer = self.layer; + layer.shadowOffset = CGSizeMake(2, 10); + [self applyDefaultShadowProperties: layer]; self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.1, 1.1); } - (void)setPressedState { - self.layer.shadowOffset = CGSizeMake(2, 2); - self.layer.shadowColor = [UIColor blackColor].CGColor; - self.layer.shadowRadius = 12.0; - self.layer.shadowOpacity = 0.45; - self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; + CALayer *layer = self.layer; + layer.shadowOffset = CGSizeMake(2, 2); + [self applyDefaultShadowProperties: layer]; self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); } +- (void)applyDefaultShadowProperties:(CALayer *)layer +{ + layer.shadowColor = [UIColor blackColor].CGColor; + layer.shadowRadius = 12.0; + layer.shadowOpacity = 0.45; + layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; +} + - (void)setDefaultState { - self.layer.shadowOffset = CGSizeZero; - self.layer.shadowColor = [UIColor blackColor].CGColor; - self.layer.shadowRadius = 0; - self.layer.shadowOpacity = 0; - self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; + CALayer *layer = self.layer; + layer.shadowOffset = CGSizeZero; + layer.shadowColor = [UIColor blackColor].CGColor; + layer.shadowRadius = 0; + layer.shadowOpacity = 0; + layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); } #endif From 3b6ed98e5f252f560daa2e4850ac17a9dcffc741 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Fri, 4 Mar 2016 08:30:24 +0000 Subject: [PATCH 15/16] Revert unnecessary spacing changes. Improve gating based on feedback. --- AsyncDisplayKit/ASControlNode.mm | 73 ++++++++++++++++---------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/AsyncDisplayKit/ASControlNode.mm b/AsyncDisplayKit/ASControlNode.mm index 54a1c708f2..6d951c8dbc 100644 --- a/AsyncDisplayKit/ASControlNode.mm +++ b/AsyncDisplayKit/ASControlNode.mm @@ -27,11 +27,11 @@ // Control Attributes BOOL _enabled; BOOL _highlighted; - + // Tracking BOOL _tracking; BOOL _touchInside; - + // Target Messages. /* The table structure is as follows: @@ -77,25 +77,25 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v { if (!(self = [super init])) return nil; - + _enabled = YES; - + // As we have no targets yet, we start off with user interaction off. When a target is added, it'll get turned back on. self.userInteractionEnabled = NO; return self; } +#if TARGET_OS_TV - (void)didLoad { -#if TARGET_OS_TV //On tvOS all control views, such as buttons, interact with the focus system even if they don't have a target set on them. Here we add our own internal tap gesture to handle this behaviour. self.userInteractionEnabled = YES; UITapGestureRecognizer *tapGestureRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(pressDown)]; tapGestureRec.allowedPressTypes = @[@(UIPressTypeSelect)]; [self.view addGestureRecognizer:tapGestureRec]; -#endif } +#endif #pragma mark - ASDisplayNode Overrides - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event @@ -103,9 +103,9 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v // If we're not interested in touches, we have nothing to do. if (!self.enabled) return; - + ASControlNodeEvent controlEventMask = 0; - + // If we get more than one touch down on us, cancel. // Additionally, if we're already tracking a touch, a second touch beginning is cause for cancellation. if ([touches count] > 1 || self.tracking) @@ -119,18 +119,18 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v { // Otherwise, begin tracking. self.tracking = YES; - + // No need to check bounds on touchesBegan as we wouldn't get the call if it wasn't in our bounds. self.touchInside = YES; self.highlighted = YES; - + UITouch *theTouch = [touches anyObject]; [self beginTrackingWithTouch:theTouch withEvent:event]; - + // Send the appropriate touch-down control event depending on how many times we've been tapped. controlEventMask |= (theTouch.tapCount == 1) ? ASControlNodeEventTouchDown : ASControlNodeEventTouchDownRepeat; } - + [self sendActionsForControlEvents:controlEventMask withEvent:event]; } @@ -139,23 +139,23 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v // If we're not interested in touches, we have nothing to do. if (!self.enabled) return; - + NSParameterAssert([touches count] == 1); UITouch *theTouch = [touches anyObject]; CGPoint touchLocation = [theTouch locationInView:self.view]; - + // Update our touchInside state. BOOL dragIsInsideBounds = [self pointInside:touchLocation withEvent:nil]; - + // Update our highlighted state. CGRect expandedBounds = CGRectInset(self.view.bounds, kASControlNodeExpandedInset, kASControlNodeExpandedInset); BOOL dragIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation); self.touchInside = dragIsInsideExpandedBounds; self.highlighted = dragIsInsideExpandedBounds; - + // Note we are continuing to track the touch. [self continueTrackingWithTouch:theTouch withEvent:event]; - + [self sendActionsForControlEvents:(dragIsInsideBounds ? ASControlNodeEventTouchDragInside : ASControlNodeEventTouchDragOutside) withEvent:event]; } @@ -165,15 +165,15 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v // If we're not interested in touches, we have nothing to do. if (!self.enabled) return; - + // We're no longer tracking and there is no touch to be inside. self.tracking = NO; self.touchInside = NO; self.highlighted = NO; - + // Note that we've cancelled tracking. [self cancelTrackingWithEvent:event]; - + // Send the cancel event. [self sendActionsForControlEvents:ASControlNodeEventTouchCancel withEvent:event]; @@ -184,7 +184,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v // If we're not interested in touches, we have nothing to do. if (!self.enabled) return; - + // On iPhone 6s, iOS 9.2 (and maybe other versions) sometimes calls -touchesEnded:withEvent: // twice on the view for one call to -touchesBegan:withEvent:. On ASControlNode, it used to // trigger an action twice unintentionally. Now, we ignore that event if we're not in a tracking @@ -192,23 +192,23 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v // It might be related to that issue: http://www.openradar.me/22910171 if (!self.tracking) return; - + NSParameterAssert([touches count] == 1); UITouch *theTouch = [touches anyObject]; CGPoint touchLocation = [theTouch locationInView:self.view]; - + // Update state. self.tracking = NO; self.touchInside = NO; self.highlighted = NO; - + // Note that we've ended tracking. [self endTrackingWithTouch:theTouch withEvent:event]; - + // Send the appropriate touch-up control event. CGRect expandedBounds = CGRectInset(self.view.bounds, kASControlNodeExpandedInset, kASControlNodeExpandedInset); BOOL touchUpIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation); - + [self sendActionsForControlEvents:(touchUpIsInsideExpandedBounds ? ASControlNodeEventTouchUpInside : ASControlNodeEventTouchUpOutside) withEvent:event]; } @@ -221,7 +221,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v // Allow double-tap gestures return tapRecognizer.numberOfTapsRequired != 1; } - + // Otherwise, go ahead. :] return YES; } @@ -237,11 +237,11 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v // Convert nil to [NSNull null] so that it can be used as a key for NSMapTable. if (!target) target = [NSNull null]; - + if (!_controlEventDispatchTable) { _controlEventDispatchTable = [[NSMutableDictionary alloc] initWithCapacity:kASControlNodeEventDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries. } - + // Enumerate the events in the mask, adding the target-action pair for each control event included in controlEventMask _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^ (ASControlNodeEvent controlEvent) @@ -278,14 +278,14 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v { NSParameterAssert(target); NSParameterAssert(controlEvent != 0 && controlEvent != ASControlNodeEventAllEvents); - + ASDN::MutexLocker l(_controlLock); // Grab the event dispatch table for this event. NSMapTable *eventDispatchTable = [_controlEventDispatchTable objectForKey:_ASControlNodeEventKeyForControlEvent(controlEvent)]; if (!eventDispatchTable) return nil; - + // Return the actions for this target. return [eventDispatchTable objectForKey:target]; } @@ -295,7 +295,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v ASDN::MutexLocker l(_controlLock); NSMutableSet *targets = [[NSMutableSet alloc] init]; - + // Look at each event... for (NSMapTable *eventDispatchTable in [_controlEventDispatchTable allValues]) { @@ -303,7 +303,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v for (id target in eventDispatchTable) [targets addObject:target]; } - + return targets; } @@ -312,7 +312,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v NSParameterAssert(controlEventMask != 0); ASDN::MutexLocker l(_controlLock); - + // Enumerate the events in the mask, removing the target-action pair for each control event included in controlEventMask. _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^ (ASControlNodeEvent controlEvent) @@ -366,7 +366,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v NSParameterAssert(controlEvents != 0); ASDN::MutexLocker l(_controlLock); - + // Enumerate the events in the mask, invoking the target-action pairs for each. _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEvents, ^ (ASControlNodeEvent controlEvent) @@ -436,6 +436,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v - (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent { } + #if TARGET_OS_TV #pragma mark - tvOS - (void)pressDown @@ -511,5 +512,5 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); } -#endif +#endif //TARGET_OS_TV @end From dbb9026415ad217c5b35fd3a9eaf6e931a9b997f Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Wed, 20 Apr 2016 08:50:30 +0100 Subject: [PATCH 16/16] Remove .orig files --- AsyncDisplayKit/ASControlNode.mm.orig | 683 ------------------------- AsyncDisplayKit/ASImageNode.mm.orig | 694 -------------------------- AsyncDisplayKit/ASVideoNode.h.orig | 76 --- AsyncDisplayKit/ASVideoNode.mm.orig | 612 ----------------------- 4 files changed, 2065 deletions(-) delete mode 100644 AsyncDisplayKit/ASControlNode.mm.orig delete mode 100644 AsyncDisplayKit/ASImageNode.mm.orig delete mode 100644 AsyncDisplayKit/ASVideoNode.h.orig delete mode 100644 AsyncDisplayKit/ASVideoNode.mm.orig diff --git a/AsyncDisplayKit/ASControlNode.mm.orig b/AsyncDisplayKit/ASControlNode.mm.orig deleted file mode 100644 index 5c860fe360..0000000000 --- a/AsyncDisplayKit/ASControlNode.mm.orig +++ /dev/null @@ -1,683 +0,0 @@ -/* 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 "ASControlNode.h" -#import "ASControlNode+Subclasses.h" -#import "ASThread.h" -#import "ASDisplayNodeExtras.h" -#import "ASImageNode.h" - -// UIControl allows dragging some distance outside of the control itself during -// tracking. This value depends on the device idiom (25 or 70 points), so -// so replicate that effect with the same values here for our own controls. -#define kASControlNodeExpandedInset (([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) ? -25.0f : -70.0f) - -// Initial capacities for dispatch tables. -#define kASControlNodeEventDispatchTableInitialCapacity 4 -#define kASControlNodeActionDispatchTableInitialCapacity 4 - -@interface ASControlNode () -{ -@private - ASDN::RecursiveMutex _controlLock; - - // Control Attributes - BOOL _enabled; - BOOL _highlighted; - - // Tracking - BOOL _tracking; - BOOL _touchInside; - - // Target Messages. - /* - The table structure is as follows: - - { - AnEvent -> { - target1 -> (action1, ...) - target2 -> (action1, ...) - ... - } - ... - } - */ - NSMutableDictionary *_controlEventDispatchTable; -} - -// Read-write overrides. -@property (nonatomic, readwrite, assign, getter=isTracking) BOOL tracking; -@property (nonatomic, readwrite, assign, getter=isTouchInside) BOOL touchInside; - -/** - @abstract Returns a key to be used in _controlEventDispatchTable that identifies the control event. - @param controlEvent A control event. - @result A key for use in _controlEventDispatchTable. - */ -id _ASControlNodeEventKeyForControlEvent(ASControlNodeEvent controlEvent); - -/** - @abstract Enumerates the ASControlNode events included mask, invoking the block for each event. - @param mask An ASControlNodeEvent mask. - @param block The block to be invoked for each ASControlNodeEvent included in mask. - @param anEvent An even that is included in mask. - */ -void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent)); - -@end - -static BOOL _enableHitTestDebug = NO; - -@implementation ASControlNode -{ - ASImageNode *_debugHighlightOverlay; -} - -#pragma mark - Lifecycle - -- (instancetype)init -{ - if (!(self = [super init])) - return nil; - - _enabled = YES; - - // As we have no targets yet, we start off with user interaction off. When a target is added, it'll get turned back on. - self.userInteractionEnabled = NO; - - return self; -} - -<<<<<<< HEAD -#if TARGET_OS_TV -- (void)didLoad -{ - //On tvOS all control views, such as buttons, interact with the focus system even if they don't have a target set on them. Here we add our own internal tap gesture to handle this behaviour. - self.userInteractionEnabled = YES; - UITapGestureRecognizer *tapGestureRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(pressDown)]; - tapGestureRec.allowedPressTypes = @[@(UIPressTypeSelect)]; - [self.view addGestureRecognizer:tapGestureRec]; -} -#endif -======= -- (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled -{ - [super setUserInteractionEnabled:userInteractionEnabled]; - self.isAccessibilityElement = userInteractionEnabled; -} - - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wobjc-missing-super-calls" ->>>>>>> master - -#pragma mark - ASDisplayNode Overrides -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - // If we're not interested in touches, we have nothing to do. - if (!self.enabled) - return; - - ASControlNodeEvent controlEventMask = 0; - - // If we get more than one touch down on us, cancel. - // Additionally, if we're already tracking a touch, a second touch beginning is cause for cancellation. - if ([touches count] > 1 || self.tracking) - { - self.tracking = NO; - self.touchInside = NO; - [self cancelTrackingWithEvent:event]; - controlEventMask |= ASControlNodeEventTouchCancel; - } - else - { - // Otherwise, begin tracking. - self.tracking = YES; - - // No need to check bounds on touchesBegan as we wouldn't get the call if it wasn't in our bounds. - self.touchInside = YES; - self.highlighted = YES; - - UITouch *theTouch = [touches anyObject]; - [self beginTrackingWithTouch:theTouch withEvent:event]; - - // Send the appropriate touch-down control event depending on how many times we've been tapped. - controlEventMask |= (theTouch.tapCount == 1) ? ASControlNodeEventTouchDown : ASControlNodeEventTouchDownRepeat; - } - - [self sendActionsForControlEvents:controlEventMask withEvent:event]; -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -{ - // If we're not interested in touches, we have nothing to do. - if (!self.enabled) - return; - - NSParameterAssert([touches count] == 1); - UITouch *theTouch = [touches anyObject]; - CGPoint touchLocation = [theTouch locationInView:self.view]; - - // Update our touchInside state. - BOOL dragIsInsideBounds = [self pointInside:touchLocation withEvent:nil]; - - // Update our highlighted state. - CGRect expandedBounds = CGRectInset(self.view.bounds, kASControlNodeExpandedInset, kASControlNodeExpandedInset); - BOOL dragIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation); - self.touchInside = dragIsInsideExpandedBounds; - self.highlighted = dragIsInsideExpandedBounds; - - // Note we are continuing to track the touch. - [self continueTrackingWithTouch:theTouch withEvent:event]; - - [self sendActionsForControlEvents:(dragIsInsideBounds ? ASControlNodeEventTouchDragInside : ASControlNodeEventTouchDragOutside) - withEvent:event]; -} - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event -{ - // If we're not interested in touches, we have nothing to do. - if (!self.enabled) - return; - - // We're no longer tracking and there is no touch to be inside. - self.tracking = NO; - self.touchInside = NO; - self.highlighted = NO; - - // Note that we've cancelled tracking. - [self cancelTrackingWithEvent:event]; - - // Send the cancel event. - [self sendActionsForControlEvents:ASControlNodeEventTouchCancel - withEvent:event]; -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - // If we're not interested in touches, we have nothing to do. - if (!self.enabled) - return; - - // On iPhone 6s, iOS 9.2 (and maybe other versions) sometimes calls -touchesEnded:withEvent: - // twice on the view for one call to -touchesBegan:withEvent:. On ASControlNode, it used to - // trigger an action twice unintentionally. Now, we ignore that event if we're not in a tracking - // state in order to have a correct behavior. - // It might be related to that issue: http://www.openradar.me/22910171 - if (!self.tracking) - return; - - NSParameterAssert([touches count] == 1); - UITouch *theTouch = [touches anyObject]; - CGPoint touchLocation = [theTouch locationInView:self.view]; - - // Update state. - self.tracking = NO; - self.touchInside = NO; - self.highlighted = NO; - - // Note that we've ended tracking. - [self endTrackingWithTouch:theTouch withEvent:event]; - - // Send the appropriate touch-up control event. - CGRect expandedBounds = CGRectInset(self.view.bounds, kASControlNodeExpandedInset, kASControlNodeExpandedInset); - BOOL touchUpIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation); - - [self sendActionsForControlEvents:(touchUpIsInsideExpandedBounds ? ASControlNodeEventTouchUpInside : ASControlNodeEventTouchUpOutside) - withEvent:event]; -} - -#pragma clang diagnostic pop - -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer -{ - // If we're interested in touches, this is a tap (the only gesture we care about) and passed -hitTest for us, then no, you may not begin. Sir. - if (self.enabled && [gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] && gestureRecognizer.view != self.view) { - UITapGestureRecognizer *tapRecognizer = (UITapGestureRecognizer *)gestureRecognizer; - // Allow double-tap gestures - return tapRecognizer.numberOfTapsRequired != 1; - } - - // Otherwise, go ahead. :] - return YES; -} - -#pragma mark - Action Messages -- (void)addTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEventMask -{ - NSParameterAssert(action); - NSParameterAssert(controlEventMask != 0); - - ASDN::MutexLocker l(_controlLock); - - // Convert nil to [NSNull null] so that it can be used as a key for NSMapTable. - if (!target) - target = [NSNull null]; - - if (!_controlEventDispatchTable) { - _controlEventDispatchTable = [[NSMutableDictionary alloc] initWithCapacity:kASControlNodeEventDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries. - - // only show tap-able areas for views with 1 or more addTarget:action: pairs - if (_enableHitTestDebug) { - - // add a highlight overlay node with area of ASControlNode + UIEdgeInsets - self.clipsToBounds = 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; - [self addSubnode:_debugHighlightOverlay]; - } - } - - // Enumerate the events in the mask, adding the target-action pair for each control event included in controlEventMask - _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^ - (ASControlNodeEvent controlEvent) - { - // Do we already have an event table for this control event? - id eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent); - NSMapTable *eventDispatchTable = _controlEventDispatchTable[eventKey]; - // Create it if necessary. - if (!eventDispatchTable) - { - // Create the dispatch table for this event. - eventDispatchTable = [NSMapTable weakToStrongObjectsMapTable]; - _controlEventDispatchTable[eventKey] = eventDispatchTable; - } - - // Have we seen this target before for this event? - NSMutableSet *targetActions = [eventDispatchTable objectForKey:target]; - if (!targetActions) - { - // 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. - // 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)]; - }); - - self.userInteractionEnabled = YES; -} - -- (NSArray *)actionsForTarget:(id)target forControlEvent:(ASControlNodeEvent)controlEvent -{ - NSParameterAssert(target); - NSParameterAssert(controlEvent != 0 && controlEvent != ASControlNodeEventAllEvents); - - ASDN::MutexLocker l(_controlLock); - - // Grab the event dispatch table for this event. - NSMapTable *eventDispatchTable = _controlEventDispatchTable[_ASControlNodeEventKeyForControlEvent(controlEvent)]; - if (!eventDispatchTable) - return nil; - - // Return the actions for this target. - return [eventDispatchTable objectForKey:target]; -} - -- (NSSet *)allTargets -{ - ASDN::MutexLocker l(_controlLock); - - NSMutableSet *targets = [[NSMutableSet alloc] init]; - - // Look at each event... - for (NSMapTable *eventDispatchTable in [_controlEventDispatchTable allValues]) - { - // and each event's targets... - for (id target in eventDispatchTable) - [targets addObject:target]; - } - - return targets; -} - -- (void)removeTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEventMask -{ - NSParameterAssert(controlEventMask != 0); - - ASDN::MutexLocker l(_controlLock); - - // Enumerate the events in the mask, removing the target-action pair for each control event included in controlEventMask. - _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^ - (ASControlNodeEvent controlEvent) - { - // Grab the dispatch table for this event (if we have it). - id eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent); - NSMapTable *eventDispatchTable = _controlEventDispatchTable[eventKey]; - if (!eventDispatchTable) - return; - - void (^removeActionFromTarget)(id targetKey, SEL action) = ^ - (id aTarget, SEL theAction) - { - // Grab the targetActions for this target. - NSMutableArray *targetActions = [eventDispatchTable objectForKey:aTarget]; - - // Remove action if we have it. - if (theAction) - [targetActions removeObject:NSStringFromSelector(theAction)]; - // Or all actions if not. - else - [targetActions removeAllObjects]; - - // If there are no actions left, remove this target entry. - if ([targetActions count] == 0) - { - [eventDispatchTable removeObjectForKey:aTarget]; - - // If there are no targets for this event anymore, remove it. - if ([eventDispatchTable count] == 0) - [_controlEventDispatchTable removeObjectForKey:eventKey]; - } - }; - - - // Unlike addTarget:, if target is nil here we remove all targets with action. - if (!target) - { - // Look at every target, removing target-pairs that have action (or all of its actions). - for (id aTarget in [eventDispatchTable copy]) - removeActionFromTarget(aTarget, action); - } - else - removeActionFromTarget(target, action); - }); -} - -#pragma mark - -- (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(UIEvent *)event -{ - NSParameterAssert(controlEvents != 0); - - ASDN::MutexLocker l(_controlLock); - - // Enumerate the events in the mask, invoking the target-action pairs for each. - _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEvents, ^ - (ASControlNodeEvent controlEvent) - { - // Use a copy to itereate, the action perform could call remove causing a mutation crash. - NSMapTable *eventDispatchTable = [_controlEventDispatchTable[_ASControlNodeEventKeyForControlEvent(controlEvent)] copy]; - - // For each target interested in this event... - for (id target in eventDispatchTable) - { - NSArray *targetActions = [eventDispatchTable objectForKey:target]; - - // Invoke each of the actions on target. - for (NSString *actionMessage in targetActions) - { - SEL action = NSSelectorFromString(actionMessage); - id responder = target; - - // NSNull means that a nil target was set, so start at self and travel the responder chain - if (responder == [NSNull null]) { - // if the target cannot perform the action, travel the responder chain to try to find something that does - responder = [self.view targetForAction:action withSender:self]; - } - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - [responder performSelector:action withObject:self withObject:event]; -#pragma clang diagnostic pop - } - } - }); -} - -#pragma mark - Convenience - -id _ASControlNodeEventKeyForControlEvent(ASControlNodeEvent controlEvent) -{ - return @(controlEvent); -} - -void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent)) -{ - // Start with our first event (touch down) and work our way up to the last event (touch cancel) - for (ASControlNodeEvent thisEvent = ASControlNodeEventTouchDown; thisEvent <= ASControlNodeEventTouchCancel; thisEvent <<= 1) - { - // If it's included in the mask, invoke the block. - if ((mask & thisEvent) == thisEvent) - block(thisEvent); - } -} - -#pragma mark - For Subclasses -- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent -{ - return YES; -} - -- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent -{ - return YES; -} - -- (void)cancelTrackingWithEvent:(UIEvent *)touchEvent -{ -} - -- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent -{ -} - -<<<<<<< HEAD -#if TARGET_OS_TV -#pragma mark - tvOS -- (void)pressDown -{ - [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationCurveLinear animations:^{ - [self setPressedState]; - } completion:^(BOOL finished) { - if (finished) { - [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationCurveLinear animations:^{ - [self setFocusedState]; - } completion:nil]; - } - }]; -} - -- (BOOL)canBecomeFocused -{ - return YES; -} - -- (BOOL)shouldUpdateFocusInContext:(nonnull UIFocusUpdateContext *)context -{ - return YES; -} - -- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator -{ - //FIXME: This is never valid inside an ASCellNode - if (context.nextFocusedView && context.nextFocusedView == self.view) { - //Focused - [coordinator addCoordinatedAnimations:^{ - [self setFocusedState]; - } completion:nil]; - } else{ - //Not focused - [coordinator addCoordinatedAnimations:^{ - [self setDefaultState]; - } completion:nil]; - } -} - -- (void)setFocusedState -{ - CALayer *layer = self.layer; - layer.shadowOffset = CGSizeMake(2, 10); - [self applyDefaultShadowProperties: layer]; - self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.1, 1.1); -} - -- (void)setPressedState -{ - CALayer *layer = self.layer; - layer.shadowOffset = CGSizeMake(2, 2); - [self applyDefaultShadowProperties: layer]; - self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); -} - -- (void)applyDefaultShadowProperties:(CALayer *)layer -{ - layer.shadowColor = [UIColor blackColor].CGColor; - layer.shadowRadius = 12.0; - layer.shadowOpacity = 0.45; - layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; -} - -- (void)setDefaultState -{ - CALayer *layer = self.layer; - layer.shadowOffset = CGSizeZero; - layer.shadowColor = [UIColor blackColor].CGColor; - layer.shadowRadius = 0; - layer.shadowOpacity = 0; - layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; - self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); -} -#endif //TARGET_OS_TV -======= -#pragma mark - Debug -// Layout method required when _enableHitTestDebug is enabled. -- (void)layout -{ - [super layout]; - - if (_debugHighlightOverlay) { - - // 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]); - UIRectEdge clippedEdges = UIRectEdgeNone; - UIRectEdge clipsToBoundsClippedEdges = UIRectEdgeNone; - CALayer *layer = self.layer; - CALayer *intersectLayer = layer; - CALayer *intersectSuperlayer = layer.superlayer; - - // Stop climbing if we encounter a UIScrollView, as its offset bounds origin may make it seem like our events will be clipped when - // scrolling will actually reveal them (because this process will not re-run due to scrolling) - while (intersectSuperlayer && ![intersectSuperlayer.delegate respondsToSelector:@selector(contentOffset)]) { - // Get our parent's tappable bounds. If the parent has an associated node, consider hitTestSlop, as it will extend its pointInside:. - CGRect parentHitRect = intersectSuperlayer.bounds; - BOOL parentClipsToBounds = NO; - - ASDisplayNode *parentNode = ASLayerToDisplayNode(intersectSuperlayer); - if (parentNode) { - UIEdgeInsets parentSlop = [parentNode hitTestSlop]; - - // if parent has a hitTestSlop as well, we need to account for the fact that events will be routed towards us in that area too. - if (!UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, parentSlop)) { - parentClipsToBounds = parentNode.clipsToBounds; - // if the parent is clipping, this will prevent us from showing the overlay outside that area. - // in this case, we will make the overlay smaller so that the special highlight to indicate the overlay - // cannot accurately display the true tappable area is shown. - if (!parentClipsToBounds) { - parentHitRect = UIEdgeInsetsInsetRect(parentHitRect, [parentNode hitTestSlop]); - } - } - } - - // Convert our current rectangle to parent coordinates, and intersect with the parent's hit rect. - CGRect intersectRectInParentCoordinates = [intersectSuperlayer convertRect:intersectRect fromLayer:intersectLayer]; - intersectRect = CGRectIntersection(parentHitRect, intersectRectInParentCoordinates); - if (!CGSizeEqualToSize(parentHitRect.size, intersectRectInParentCoordinates.size)) { - clippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates - parentRect:parentHitRect rectEdge:clippedEdges]; - if (parentClipsToBounds) { - clipsToBoundsClippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates - parentRect:parentHitRect rectEdge:clipsToBoundsClippedEdges]; - } - } - - // Advance up the tree. - intersectLayer = intersectSuperlayer; - intersectSuperlayer = intersectLayer.superlayer; - } - - CGRect finalRect = [intersectLayer convertRect:intersectRect toLayer:layer]; - UIColor *fillColor = [[UIColor greenColor] colorWithAlphaComponent:0.4]; - - // determine if edges are clipped - if (clippedEdges == UIRectEdgeNone) { - _debugHighlightOverlay.backgroundColor = fillColor; - } else { - const CGFloat borderWidth = 2.0; - UIColor *borderColor = [[UIColor orangeColor] colorWithAlphaComponent:0.8]; - UIColor *clipsBorderColor = [UIColor colorWithRed:30/255.0 green:90/255.0 blue:50/255.0 alpha:0.7]; - CGRect imgRect = CGRectMake(0, 0, 2.0 * borderWidth + 1.0, 2.0 * borderWidth + 1.0); - UIGraphicsBeginImageContext(imgRect.size); - - [fillColor setFill]; - UIRectFill(imgRect); - - [self drawEdgeIfClippedWithEdges:clippedEdges color:clipsBorderColor borderWidth:borderWidth imgRect:imgRect]; - [self drawEdgeIfClippedWithEdges:clipsToBoundsClippedEdges color:borderColor borderWidth:borderWidth imgRect:imgRect]; - - UIImage *debugHighlightImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - UIEdgeInsets edgeInsets = UIEdgeInsetsMake(borderWidth, borderWidth, borderWidth, borderWidth); - _debugHighlightOverlay.image = [debugHighlightImage resizableImageWithCapInsets:edgeInsets - resizingMode:UIImageResizingModeStretch]; - _debugHighlightOverlay.backgroundColor = nil; - } - - _debugHighlightOverlay.frame = finalRect; - } -} - -- (UIRectEdge)setEdgesOfIntersectionForChildRect:(CGRect)childRect parentRect:(CGRect)parentRect rectEdge:(UIRectEdge)rectEdge -{ - if (childRect.origin.y < parentRect.origin.y) { - rectEdge |= UIRectEdgeTop; - } - if (childRect.origin.x < parentRect.origin.x) { - rectEdge |= UIRectEdgeLeft; - } - if (CGRectGetMaxY(childRect) > CGRectGetMaxY(parentRect)) { - rectEdge |= UIRectEdgeBottom; - } - if (CGRectGetMaxX(childRect) > CGRectGetMaxX(parentRect)) { - rectEdge |= UIRectEdgeRight; - } - - return rectEdge; -} - -- (void)drawEdgeIfClippedWithEdges:(UIRectEdge)rectEdge color:(UIColor *)color borderWidth:(CGFloat)borderWidth imgRect:(CGRect)imgRect -{ - [color setFill]; - - if (rectEdge & UIRectEdgeTop) { - UIRectFill(CGRectMake(0.0, 0.0, imgRect.size.width, borderWidth)); - } - if (rectEdge & UIRectEdgeLeft) { - UIRectFill(CGRectMake(0.0, 0.0, borderWidth, imgRect.size.height)); - } - if (rectEdge & UIRectEdgeBottom) { - UIRectFill(CGRectMake(0.0, imgRect.size.height - borderWidth, imgRect.size.width, borderWidth)); - } - if (rectEdge & UIRectEdgeRight) { - UIRectFill(CGRectMake(imgRect.size.width - borderWidth, 0.0, borderWidth, imgRect.size.height)); - } -} - -+ (void)setEnableHitTestDebug:(BOOL)enable -{ - _enableHitTestDebug = enable; -} - ->>>>>>> master -@end diff --git a/AsyncDisplayKit/ASImageNode.mm.orig b/AsyncDisplayKit/ASImageNode.mm.orig deleted file mode 100644 index a5747149a9..0000000000 --- a/AsyncDisplayKit/ASImageNode.mm.orig +++ /dev/null @@ -1,694 +0,0 @@ -/* 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 "ASImageNode.h" - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#import "ASImageNode+CGExtras.h" -#import "AsyncDisplayKit+Debug.h" - -#import "ASInternalHelpers.h" -#import "ASEqualityHelpers.h" - -@interface _ASImageNodeDrawParameters : NSObject - -@property (nonatomic, retain) UIImage *image; -@property (nonatomic, assign) BOOL opaque; -@property (nonatomic, assign) CGRect bounds; -@property (nonatomic, assign) CGFloat contentsScale; -@property (nonatomic, strong) UIColor *backgroundColor; -@property (nonatomic, assign) UIViewContentMode contentMode; - -@end - -// TODO: eliminate explicit parameters with a set of keys copied from the node -@implementation _ASImageNodeDrawParameters - -- (instancetype)initWithImage:(UIImage *)image - bounds:(CGRect)bounds - opaque:(BOOL)opaque - contentsScale:(CGFloat)contentsScale - backgroundColor:(UIColor *)backgroundColor - contentMode:(UIViewContentMode)contentMode -{ - if (!(self = [self init])) - return nil; - - _image = image; - _opaque = opaque; - _bounds = bounds; - _contentsScale = contentsScale; - _backgroundColor = backgroundColor; - _contentMode = contentMode; - - return self; -} - -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%@ : %p opaque:%@ bounds:%@ contentsScale:%.2f backgroundColor:%@ contentMode:%@>", [self class], self, @(self.opaque), NSStringFromCGRect(self.bounds), self.contentsScale, self.backgroundColor, ASDisplayNodeNSStringFromUIContentMode(self.contentMode)]; -} - -@end - -@implementation ASImageNode -{ -@private - UIImage *_image; - - void (^_displayCompletionBlock)(BOOL canceled); - ASDN::RecursiveMutex _imageLock; - -#if TARGET_OS_TV - //tvOS - BOOL isDefaultState; -#endif - - // Cropping. - BOOL _cropEnabled; // Defaults to YES. - BOOL _forceUpscaling; //Defaults to NO. - CGRect _cropRect; // Defaults to CGRectMake(0.5, 0.5, 0, 0) - CGRect _cropDisplayBounds; - - ASTextNode *_debugLabelNode; -} - -@synthesize image = _image; -@synthesize imageModificationBlock = _imageModificationBlock; - -- (instancetype)init -{ - if (!(self = [super init])) - return nil; - - // TODO can this be removed? - self.contentsScale = ASScreenScale(); - self.contentMode = UIViewContentModeScaleAspectFill; - self.opaque = NO; - - _cropEnabled = YES; - _forceUpscaling = NO; - _cropRect = CGRectMake(0.5, 0.5, 0, 0); - _cropDisplayBounds = CGRectNull; - _placeholderColor = ASDisplayNodeDefaultPlaceholderColor(); - - return self; -} - -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock -{ - ASDisplayNodeAssertNotSupported(); - return nil; -} - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock -{ - ASDisplayNodeAssertNotSupported(); - return nil; -} - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - ASDN::MutexLocker l(_imageLock); - // if a preferredFrameSize is set, call the superclass to return that instead of using the image size. - if (CGSizeEqualToSize(self.preferredFrameSize, CGSizeZero) == NO) - return [super calculateSizeThatFits:constrainedSize]; - else if (_image) - return _image.size; - else - return CGSizeZero; -} - -- (void)setImage:(UIImage *)image -{ - _imageLock.lock(); - if (!ASObjectIsEqual(_image, image)) { - _image = image; - - _imageLock.unlock(); - - [self invalidateCalculatedLayout]; - if (image) { - [self setNeedsDisplay]; - - if ([ASImageNode shouldShowImageScalingOverlay]) { - ASPerformBlockOnMainThread(^{ - _debugLabelNode = [[ASTextNode alloc] init]; - _debugLabelNode.layerBacked = YES; - [self addSubnode:_debugLabelNode]; - }); - } - } else { - self.contents = nil; - } - } else { - _imageLock.unlock(); // We avoid using MutexUnlocker as it needlessly re-locks at the end of the scope. - } -} - -- (UIImage *)image -{ - ASDN::MutexLocker l(_imageLock); - return _image; -} - -- (void)setPlaceholderColor:(UIColor *)placeholderColor -{ - _placeholderColor = placeholderColor; - - // prevent placeholders if we don't have a color - self.placeholderEnabled = placeholderColor != nil; -} - -- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer -{ - return [[_ASImageNodeDrawParameters alloc] initWithImage:self.image - bounds:self.bounds - opaque:self.opaque - contentsScale:self.contentsScaleForDisplay - backgroundColor:self.backgroundColor - contentMode:self.contentMode]; -} - -- (NSDictionary *)debugLabelAttributes -{ - return @{ NSFontAttributeName: [UIFont systemFontOfSize:15.0], - NSForegroundColorAttributeName: [UIColor redColor] }; -} - -- (UIImage *)displayWithParameters:(_ASImageNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled -{ - UIImage *image = parameters.image; - if (!image) { - return nil; - } - - BOOL forceUpscaling = NO; - BOOL cropEnabled = NO; - BOOL isOpaque = parameters.opaque; - UIColor *backgroundColor = parameters.backgroundColor; - UIViewContentMode contentMode = parameters.contentMode; - CGFloat contentsScale = 0.0; - CGRect cropDisplayBounds = CGRectZero; - CGRect cropRect = CGRectZero; - asimagenode_modification_block_t imageModificationBlock; - - { - ASDN::MutexLocker l(_imageLock); - - // FIXME: There is a small risk of these values changing between the main thread creation of drawParameters, and the execution of this method. - // We should package these up into the draw parameters object. Might be easiest to create a struct for the non-objects and make it one property. - cropEnabled = _cropEnabled; - forceUpscaling = _forceUpscaling; - contentsScale = _contentsScaleForDisplay; - cropDisplayBounds = _cropDisplayBounds; - cropRect = _cropRect; - imageModificationBlock = _imageModificationBlock; - } - - BOOL hasValidCropBounds = cropEnabled && !CGRectIsNull(cropDisplayBounds) && !CGRectIsEmpty(cropDisplayBounds); - CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : parameters.bounds); - - ASDisplayNodeContextModifier preContextBlock = self.willDisplayNodeContentWithRenderingContext; - ASDisplayNodeContextModifier postContextBlock = self.didDisplayNodeContentWithRenderingContext; - - ASDisplayNodeAssert(contentsScale > 0, @"invalid contentsScale at display time"); - - // if the image is resizable, bail early since the image has likely already been configured - BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero); - if (stretchable) { - if (imageModificationBlock != NULL) { - image = imageModificationBlock(image); - } - return image; - } - - CGSize imageSize = image.size; - CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale); - CGSize boundsSizeInPixels = CGSizeMake(floorf(bounds.size.width * contentsScale), floorf(bounds.size.height * contentsScale)); - - if (_debugLabelNode) { - CGFloat pixelCountRatio = (imageSizeInPixels.width * imageSizeInPixels.height) / (boundsSizeInPixels.width * boundsSizeInPixels.height); - if (pixelCountRatio != 1.0) { - NSString *scaleString = [NSString stringWithFormat:@"%.2fx", pixelCountRatio]; - _debugLabelNode.attributedString = [[NSAttributedString alloc] initWithString:scaleString attributes:[self debugLabelAttributes]]; - _debugLabelNode.hidden = NO; - [self setNeedsLayout]; - } else { - _debugLabelNode.hidden = YES; - _debugLabelNode.attributedString = nil; - } - } - - BOOL contentModeSupported = contentMode == UIViewContentModeScaleAspectFill || - contentMode == UIViewContentModeScaleAspectFit || - contentMode == UIViewContentModeCenter; - - CGSize backingSize = CGSizeZero; - CGRect imageDrawRect = CGRectZero; - - if (boundsSizeInPixels.width * contentsScale < 1.0f || boundsSizeInPixels.height * contentsScale < 1.0f || - imageSizeInPixels.width < 1.0f || imageSizeInPixels.height < 1.0f) { - return nil; - } - - // If we're not supposed to do any cropping, just decode image at original size - if (!cropEnabled || !contentModeSupported || stretchable) { - backingSize = imageSizeInPixels; - imageDrawRect = (CGRect){.size = backingSize}; - } else { - ASCroppedImageBackingSizeAndDrawRectInBounds(imageSizeInPixels, - boundsSizeInPixels, - contentMode, - cropRect, - forceUpscaling, - &backingSize, - &imageDrawRect); - } - - if (backingSize.width <= 0.0f || backingSize.height <= 0.0f || - imageDrawRect.size.width <= 0.0f || imageDrawRect.size.height <= 0.0f) { - return nil; - } - - // Use contentsScale of 1.0 and do the contentsScale handling in boundsSizeInPixels so ASCroppedImageBackingSizeAndDrawRectInBounds - // will do its rounding on pixel instead of point boundaries - UIGraphicsBeginImageContextWithOptions(backingSize, isOpaque, 1.0); - - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context && preContextBlock) { - preContextBlock(context); - } - - // if view is opaque, fill the context with background color - if (isOpaque && backgroundColor) { - [backgroundColor setFill]; - UIRectFill({ .size = backingSize }); - } - - // iOS 9 appears to contain a thread safety regression when drawing the same CGImageRef on - // multiple threads concurrently. In fact, instead of crashing, it appears to deadlock. - // The issue is present in Mac OS X El Capitan and has been seen hanging Pro apps like Adobe Premier, - // as well as iOS games, and a small number of ASDK apps that provide the same image reference - // to many separate ASImageNodes. A workaround is to set .displaysAsynchronously = NO for the nodes - // that may get the same pointer for a given UI asset image, etc. - // FIXME: We should replace @synchronized here, probably using a global, locked NSMutableSet, and - // only if the object already exists in the set we should create a semaphore to signal waiting threads - // upon removal of the object from the set when the operation completes. - // Another option is to have ASDisplayNode+AsyncDisplay coordinate these cases, and share the decoded buffer. - // Details tracked in https://github.com/facebook/AsyncDisplayKit/issues/1068 - - @synchronized(image) { - [image drawInRect:imageDrawRect]; - } - - if (context && postContextBlock) { - postContextBlock(context); - } - - if (isCancelled()) { - UIGraphicsEndImageContext(); - return nil; - } - - UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); - - UIGraphicsEndImageContext(); - - if (imageModificationBlock != NULL) { - result = imageModificationBlock(result); - } - - return result; -} - -- (void)displayDidFinish -{ - [super displayDidFinish]; - - _imageLock.lock(); - void (^displayCompletionBlock)(BOOL canceled) = _displayCompletionBlock; - UIImage *image = _image; - _imageLock.unlock(); - - // If we've got a block to perform after displaying, do it. - if (image && displayCompletionBlock) { - - displayCompletionBlock(NO); - - _imageLock.lock(); - _displayCompletionBlock = nil; - _imageLock.unlock(); - } -} - -#pragma mark - -- (void)setNeedsDisplayWithCompletion:(void (^ _Nullable)(BOOL canceled))displayCompletionBlock -{ - if (self.displaySuspended) { - if (displayCompletionBlock) - displayCompletionBlock(YES); - return; - } - - // Stash the block and call-site queue. We'll invoke it in -displayDidFinish. - ASDN::MutexLocker l(_imageLock); - if (_displayCompletionBlock != displayCompletionBlock) { - _displayCompletionBlock = [displayCompletionBlock copy]; - } - - [self setNeedsDisplay]; -} - -#pragma mark - Cropping -- (BOOL)isCropEnabled -{ - ASDN::MutexLocker l(_imageLock); - return _cropEnabled; -} - -- (void)setCropEnabled:(BOOL)cropEnabled -{ - [self setCropEnabled:cropEnabled recropImmediately:NO inBounds:self.bounds]; -} - -- (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediately inBounds:(CGRect)cropBounds -{ - ASDN::MutexLocker l(_imageLock); - if (_cropEnabled == cropEnabled) - return; - - _cropEnabled = cropEnabled; - _cropDisplayBounds = cropBounds; - - // If we have an image to display, display it, respecting our recrop flag. - if (self.image) - { - ASPerformBlockOnMainThread(^{ - if (recropImmediately) - [self displayImmediately]; - else - [self setNeedsDisplay]; - }); - } -} - -- (CGRect)cropRect -{ - ASDN::MutexLocker l(_imageLock); - return _cropRect; -} - -- (void)setCropRect:(CGRect)cropRect -{ - ASDN::MutexLocker l(_imageLock); - if (CGRectEqualToRect(_cropRect, cropRect)) - return; - - _cropRect = cropRect; - - // TODO: this logic needs to be updated to respect cropRect. - CGSize boundsSize = self.bounds.size; - CGSize imageSize = self.image.size; - - BOOL isCroppingImage = ((boundsSize.width < imageSize.width) || (boundsSize.height < imageSize.height)); - - // Re-display if we need to. - ASPerformBlockOnMainThread(^{ - if (self.nodeLoaded && self.contentMode == UIViewContentModeScaleAspectFill && isCroppingImage) - [self setNeedsDisplay]; - }); -} - -- (BOOL)forceUpscaling -{ - ASDN::MutexLocker l(_imageLock); - return _forceUpscaling; -} - -- (void)setForceUpscaling:(BOOL)forceUpscaling -{ - ASDN::MutexLocker l(_imageLock); - _forceUpscaling = forceUpscaling; -} - -- (asimagenode_modification_block_t)imageModificationBlock -{ - ASDN::MutexLocker l(_imageLock); - return _imageModificationBlock; -} - -- (void)setImageModificationBlock:(asimagenode_modification_block_t)imageModificationBlock -{ - ASDN::MutexLocker l(_imageLock); - _imageModificationBlock = imageModificationBlock; -} - -<<<<<<< HEAD - -#if TARGET_OS_TV -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - [super touchesBegan:touches withEvent:event]; - isDefaultState = NO; - UIView *view = [self getView]; - CALayer *layer = view.layer; - - CGSize targetShadowOffset = CGSizeMake(0.0, self.bounds.size.height/8); - [layer removeAllAnimations]; - [CATransaction begin]; - [CATransaction setCompletionBlock:^{ - layer.shadowOffset = targetShadowOffset; - }]; - - CABasicAnimation *shadowOffsetAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOffset"]; - shadowOffsetAnimation.toValue = [NSValue valueWithCGSize:targetShadowOffset]; - shadowOffsetAnimation.duration = 0.4; - shadowOffsetAnimation.removedOnCompletion = NO; - shadowOffsetAnimation.fillMode = kCAFillModeForwards; - shadowOffsetAnimation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeOut"]; - [layer addAnimation:shadowOffsetAnimation forKey:@"shadowOffset"]; - [CATransaction commit]; - - CABasicAnimation *shadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"]; - shadowOpacityAnimation.toValue = [NSNumber numberWithFloat:0.45]; - shadowOpacityAnimation.duration = 0.4; - shadowOpacityAnimation.removedOnCompletion = false; - shadowOpacityAnimation.fillMode = kCAFillModeForwards; - shadowOpacityAnimation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeOut"]; - [layer addAnimation:shadowOpacityAnimation forKey:@"shadowOpacityAnimation"]; - - view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25); - - [CATransaction commit]; -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { - [super touchesMoved:touches withEvent:event]; - - if (!isDefaultState) { - UIView *view = [self getView]; - - UITouch *touch = [touches anyObject]; - // Get the specific point that was touched - // This is quite messy in it's current state so is not ready for production. The reason it is here is for others to contribute and to make it clear what is occuring. - // TODO: Clean up, and improve visuals. - CGPoint point = [touch locationInView:self.view]; - float pitch = 0; - float yaw = 0; - BOOL topHalf = NO; - if (point.y > CGRectGetHeight(self.view.frame)) { - pitch = 15; - } else if (point.y < -CGRectGetHeight(self.view.frame)) { - pitch = -15; - } else { - pitch = (point.y/CGRectGetHeight(self.view.frame))*15; - } - if (pitch < 0) { - topHalf = YES; - } - - if (point.x > CGRectGetWidth(self.view.frame)) { - yaw = 10; - } else if (point.x < -CGRectGetWidth(self.view.frame)) { - yaw = -10; - } else { - yaw = (point.x/CGRectGetWidth(self.view.frame))*10; - } - if (!topHalf) { - if (yaw > 0) { - yaw = -yaw; - } else { - yaw = fabsf(yaw); - } - } - - CATransform3D pitchTransform = CATransform3DMakeRotation([self degressToRadians:pitch],1.0,0.0,0.0); - CATransform3D yawTransform = CATransform3DMakeRotation([self degressToRadians:yaw],0.0,1.0,0.0); - CATransform3D transform = CATransform3DConcat(pitchTransform, yawTransform); - CATransform3D scaleAndTransform = CATransform3DConcat(transform, CATransform3DMakeAffineTransform(CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25))); - - [UIView animateWithDuration:0.5 animations:^{ - view.layer.transform = scaleAndTransform; - }]; - } else { - [self setDefaultState]; - } -} - - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - [super touchesEnded:touches withEvent:event]; - [self finishTouches]; -} - -- (void)finishTouches -{ - if (!isDefaultState) { - UIView *view = [self getView]; - CALayer *layer = view.layer; - - CGSize targetShadowOffset = CGSizeMake(0.0, self.bounds.size.height/8); - CATransform3D targetScaleTransform = CATransform3DMakeScale(1.2, 1.2, 1.2); - [CATransaction begin]; - [CATransaction setCompletionBlock:^{ - layer.shadowOffset = targetShadowOffset; - }]; - [CATransaction commit]; - - [UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ - view.layer.transform = targetScaleTransform; - } completion:^(BOOL finished) { - if (finished) { - [layer removeAnimationForKey:@"shadowOffset"]; - [layer removeAnimationForKey:@"shadowOpacity"]; - } - }]; - } else { - [self setDefaultState]; - } -} - -- (void)setFocusedState -{ - UIView *view = [self getView]; - CALayer *layer = view.layer; - layer.shadowOffset = CGSizeMake(2, 10); - layer.shadowColor = [UIColor blackColor].CGColor; - layer.shadowRadius = 12.0; - layer.shadowOpacity = 0.45; - layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; - view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25); -} - -- (void)setDefaultState -{ - UIView *view = [self getView]; - CALayer *layer = view.layer; - view.transform = CGAffineTransformIdentity; - layer.shadowOpacity = 0; - layer.shadowOffset = CGSizeZero; - layer.shadowRadius = 0; - layer.shadowPath = nil; - [layer removeAnimationForKey:@"shadowOffset"]; - [layer removeAnimationForKey:@"shadowOpacity"]; - isDefaultState = YES; -} - -- (UIView *)getView -{ - UIView *view = self.view; - //If we are inside a ASCellNode, then we need to apply our focus effects to the ASCellNode view/layer rather than the ASImageNode view/layer. - if (CGSizeEqualToSize(self.view.superview.frame.size, self.view.frame.size) && self.view.superview.superview) { - view = self.view.superview.superview; - } - return view; -} - -- (float)degressToRadians:(float)value -{ - return value * M_PI / 180; -} - -#endif - -======= -#pragma mark - Debug -- (void)layout -{ - [super layout]; - - if (_debugLabelNode) { - CGSize boundsSize = self.bounds.size; - CGSize debugLabelSize = [_debugLabelNode measure:boundsSize]; - CGPoint debugLabelOrigin = CGPointMake(boundsSize.width - debugLabelSize.width, - boundsSize.height - debugLabelSize.height); - _debugLabelNode.frame = (CGRect) {debugLabelOrigin, debugLabelSize}; - } -} ->>>>>>> master -@end - -#pragma mark - Extras -extern asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat borderWidth, UIColor *borderColor) -{ - return ^(UIImage *originalImage) { - UIGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale); - UIBezierPath *roundOutline = [UIBezierPath bezierPathWithOvalInRect:(CGRect){CGPointZero, originalImage.size}]; - - // Make the image round - [roundOutline addClip]; - - // Draw the original image - [originalImage drawAtPoint:CGPointZero]; - - // Draw a border on top. - if (borderWidth > 0.0) { - [borderColor setStroke]; - [roundOutline setLineWidth:borderWidth]; - [roundOutline stroke]; - } - - UIImage *modifiedImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return modifiedImage; - }; -} - -extern asimagenode_modification_block_t ASImageNodeTintColorModificationBlock(UIColor *color) -{ - return ^(UIImage *originalImage) { - UIGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale); - - // Set color and render template - [color setFill]; - UIImage *templateImage = [originalImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - [templateImage drawAtPoint:CGPointZero]; - - UIImage *modifiedImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - // if the original image was stretchy, keep it stretchy - if (!UIEdgeInsetsEqualToEdgeInsets(originalImage.capInsets, UIEdgeInsetsZero)) { - modifiedImage = [modifiedImage resizableImageWithCapInsets:originalImage.capInsets resizingMode:originalImage.resizingMode]; - } - - return modifiedImage; - }; -} diff --git a/AsyncDisplayKit/ASVideoNode.h.orig b/AsyncDisplayKit/ASVideoNode.h.orig deleted file mode 100644 index 93266021a1..0000000000 --- a/AsyncDisplayKit/ASVideoNode.h.orig +++ /dev/null @@ -1,76 +0,0 @@ -/* 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. - */ - -<<<<<<< HEAD -#import -#if TARGET_OS_IOS -======= -#import - -@class AVAsset, AVPlayer, AVPlayerItem; ->>>>>>> master -@protocol ASVideoNodeDelegate; - -NS_ASSUME_NONNULL_BEGIN - -// IMPORTANT NOTES: -// 1. Applications using ASVideoNode must link AVFoundation! (this provides the AV* classes below) -// 2. This is a relatively new component of AsyncDisplayKit. It has many useful features, but -// there is room for further expansion and optimization. Please report any issues or requests -// in an issue on GitHub: https://github.com/facebook/AsyncDisplayKit/issues - -@interface ASVideoNode : ASControlNode - -- (void)play; -- (void)pause; -- (BOOL)isPlaying; - -@property (nullable, atomic, strong, readwrite) AVAsset *asset; - -@property (nullable, atomic, strong, readonly) AVPlayer *player; -@property (nullable, atomic, strong, readonly) AVPlayerItem *currentItem; - -/** - * When shouldAutoplay is set to true, a video node will play when it has both loaded and entered the "visible" interfaceState. - * If it leaves the visible interfaceState it will pause but will resume once it has returned. - */ -@property (nonatomic, assign, readwrite) BOOL shouldAutoplay; -@property (nonatomic, assign, readwrite) BOOL shouldAutorepeat; - -@property (nonatomic, assign, readwrite) BOOL muted; - -//! Defaults to AVLayerVideoGravityResizeAspect -@property (atomic) NSString *gravity; - -//! Defaults to an ASDefaultPlayButton instance. -@property (nullable, atomic) ASButtonNode *playButton; - -@property (nullable, atomic, weak, readwrite) id delegate; - -@end - -@protocol ASVideoNodeDelegate -@optional -/** - * @abstract Delegate method invoked when the node's video has played to its end time. - * @param videoNode The video node has played to its end time. - */ -- (void)videoPlaybackDidFinish:(ASVideoNode *)videoNode; -/** - * @abstract Delegate method invoked the node is tapped. - * @param videoNode The video node that was tapped. - * @discussion The video's play state is toggled if this method is not implemented. - */ -- (void)videoNodeWasTapped:(ASVideoNode *)videoNode; -@end -<<<<<<< HEAD -#endif -======= - -NS_ASSUME_NONNULL_END ->>>>>>> master diff --git a/AsyncDisplayKit/ASVideoNode.mm.orig b/AsyncDisplayKit/ASVideoNode.mm.orig deleted file mode 100644 index 91de668282..0000000000 --- a/AsyncDisplayKit/ASVideoNode.mm.orig +++ /dev/null @@ -1,612 +0,0 @@ -/* 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. - */ -#if TARGET_OS_IOS -#import "ASVideoNode.h" -#import "ASDefaultPlayButton.h" -<<<<<<< HEAD -======= - -static BOOL ASAssetIsEqual(AVAsset *asset1, AVAsset *asset2) { - return ASObjectIsEqual(asset1, asset2) - || ([asset1 isKindOfClass:[AVURLAsset class]] - && [asset2 isKindOfClass:[AVURLAsset class]] - && ASObjectIsEqual(((AVURLAsset *)asset1).URL, ((AVURLAsset *)asset2).URL)); -} - -static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { - if ([videoGravity isEqualToString:AVLayerVideoGravityResizeAspect]) { - return UIViewContentModeScaleAspectFit; - } else if ([videoGravity isEqual:AVLayerVideoGravityResizeAspectFill]) { - return UIViewContentModeScaleAspectFill; - } else { - return UIViewContentModeScaleToFill; - } -} - ->>>>>>> master -@interface ASVideoNode () -{ - ASDN::RecursiveMutex _videoLock; - - __weak id _delegate; - - BOOL _shouldBePlaying; - - BOOL _shouldAutorepeat; - BOOL _shouldAutoplay; - - BOOL _muted; - - AVAsset *_asset; - - AVPlayerItem *_currentPlayerItem; - AVPlayer *_player; - - ASImageNode *_placeholderImageNode; // TODO: Make ASVideoNode an ASImageNode subclass; remove this. - - ASButtonNode *_playButton; - ASDisplayNode *_playerNode; - ASDisplayNode *_spinner; - NSString *_gravity; -} - -@end - -@implementation ASVideoNode - -// TODO: Support preview images with HTTP Live Streaming videos. - -#pragma mark - Construction and Layout - -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - - self.playButton = [[ASDefaultPlayButton alloc] init]; - self.gravity = AVLayerVideoGravityResizeAspect; - [self addTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; - - return self; -} - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock -{ - ASDisplayNodeAssertNotSupported(); - return nil; -} - -- (ASDisplayNode *)constructPlayerNode -{ - ASDisplayNode * playerNode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{ - ASDN::MutexLocker l(_videoLock); - - AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init]; - if (!_player) { - [self constructCurrentPlayerItemFromInitData]; - _player = [AVPlayer playerWithPlayerItem:_currentPlayerItem]; - _player.muted = _muted; - } - playerLayer.player = _player; - playerLayer.videoGravity = [self gravity]; - return playerLayer; - }]; - - return playerNode; -} - -- (void)constructCurrentPlayerItemFromInitData -{ - ASDN::MutexLocker l(_videoLock); - - ASDisplayNodeAssert(_asset, @"ASVideoNode must be initialized with an AVAsset"); - [self removePlayerItemObservers]; - - if (_asset) { - if ([_asset.tracks count]) { - _currentPlayerItem = [[AVPlayerItem alloc] initWithAsset:_asset]; - } else { - _currentPlayerItem = [[AVPlayerItem alloc] initWithURL:((AVURLAsset *)_asset).URL]; - } - } - - if (_currentPlayerItem) { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didPlayToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:_currentPlayerItem]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:_currentPlayerItem]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemNewErrorLogEntryNotification object:_currentPlayerItem]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; - } -} - -- (void)removePlayerItemObservers -{ - ASDN::MutexLocker l(_videoLock); - - if (_currentPlayerItem) { - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; - } -} - -- (void)didLoad -{ - [super didLoad]; - - ASDN::MutexLocker l(_videoLock); - - if (_shouldBePlaying) { - _playerNode = [self constructPlayerNode]; - [self insertSubnode:_playerNode atIndex:0]; - } else if (_asset) { - [self setPlaceholderImagefromAsset:_asset]; - } -} - -- (void)layout -{ - [super layout]; - - CGRect bounds = self.bounds; - - ASDN::MutexLocker l(_videoLock); - - _placeholderImageNode.frame = bounds; - _playerNode.frame = bounds; - _playButton.frame = bounds; - - CGFloat horizontalDiff = (bounds.size.width - _playButton.bounds.size.width)/2; - CGFloat verticalDiff = (bounds.size.height - _playButton.bounds.size.height)/2; - _playButton.hitTestSlop = UIEdgeInsetsMake(-verticalDiff, -horizontalDiff, -verticalDiff, -horizontalDiff); - - _spinner.bounds = CGRectMake(0, 0, 44, 44); - _spinner.position = CGPointMake(bounds.size.width/2, bounds.size.height/2); -} - -- (void)setPlaceholderImagefromAsset:(AVAsset*)asset -{ - ASPerformBlockOnBackgroundThread(^{ - ASDN::MutexLocker l(_videoLock); - - AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:_asset]; - imageGenerator.appliesPreferredTrackTransform = YES; - NSArray *times = @[[NSValue valueWithCMTime:CMTimeMake(0, 1)]]; - - [imageGenerator generateCGImagesAsynchronouslyForTimes:times completionHandler:^(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) { - - ASDN::MutexLocker l(_videoLock); - - // Unfortunately it's not possible to generate a preview image for an HTTP live stream asset, so we'll give up here - // http://stackoverflow.com/questions/32112205/m3u8-file-avassetimagegenerator-error - if (image && _placeholderImageNode.image == nil) { - [self setPlaceholderImage:[UIImage imageWithCGImage:image]]; - } - }]; - }); -} - -- (void)setPlaceholderImage:(UIImage *)image -{ - ASDN::MutexLocker l(_videoLock); - - if (_placeholderImageNode == nil) { - _placeholderImageNode = [[ASImageNode alloc] init]; - _placeholderImageNode.layerBacked = YES; - _placeholderImageNode.contentMode = ASContentModeFromVideoGravity(_gravity); - } - - _placeholderImageNode.image = image; - - dispatch_async(dispatch_get_main_queue(), ^{ - ASDN::MutexLocker l(_videoLock); - - [self insertSubnode:_placeholderImageNode atIndex:0]; - [self setNeedsLayout]; - }); -} - -- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState -{ - [super interfaceStateDidChange:newState fromState:oldState]; - - BOOL nowVisible = ASInterfaceStateIncludesVisible(newState); - BOOL wasVisible = ASInterfaceStateIncludesVisible(oldState); - - ASDN::MutexLocker l(_videoLock); - - if (!nowVisible) { - if (wasVisible) { - if (_shouldBePlaying) { - [self pause]; - _shouldBePlaying = YES; - } - [(UIActivityIndicatorView *)_spinner.view stopAnimating]; - [_spinner removeFromSupernode]; - } - } else { - if (_shouldBePlaying) { - [self play]; - } - } -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context -{ - ASDN::MutexLocker l(_videoLock); - - if (object == _currentPlayerItem && [keyPath isEqualToString:@"status"]) { - if (_currentPlayerItem.status == AVPlayerItemStatusReadyToPlay) { - if ([self.subnodes containsObject:_spinner]) { - [_spinner removeFromSupernode]; - _spinner = nil; - } - - // If we don't yet have a placeholder image update it now that we should have data available for it - if (_placeholderImageNode.image == nil) { - if (_currentPlayerItem && - _currentPlayerItem.tracks.count > 0 && - _currentPlayerItem.tracks[0].assetTrack && - _currentPlayerItem.tracks[0].assetTrack.asset) { - _asset = _currentPlayerItem.tracks[0].assetTrack.asset; - [self setPlaceholderImagefromAsset:_asset]; - [self setNeedsLayout]; - } - } - - } else if (_currentPlayerItem.status == AVPlayerItemStatusFailed) { - - } - } -} - -- (void)tapped -{ - if (self.delegate && [self.delegate respondsToSelector:@selector(videoNodeWasTapped:)]) { - [self.delegate videoNodeWasTapped:self]; - } else { - if (_shouldBePlaying) { - [self pause]; - } else { - [self play]; - } - } -} - -- (void)fetchData -{ - [super fetchData]; - - @try { - [_currentPlayerItem removeObserver:self forKeyPath:NSStringFromSelector(@selector(status))]; - } - @catch (NSException * __unused exception) { - NSLog(@"unnecessary removal in fetch data"); - } - - { - ASDN::MutexLocker l(_videoLock); - [self constructCurrentPlayerItemFromInitData]; - [_currentPlayerItem addObserver:self forKeyPath:NSStringFromSelector(@selector(status)) options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL]; - - if (_player) { - [_player replaceCurrentItemWithPlayerItem:_currentPlayerItem]; - } else { - _player = [[AVPlayer alloc] initWithPlayerItem:_currentPlayerItem]; - _player.muted = _muted; - } - } -} - -- (void)clearFetchedData -{ - [super clearFetchedData]; - - { - ASDN::MutexLocker l(_videoLock); - ((AVPlayerLayer *)_playerNode.layer).player = nil; - _player = nil; - } -} - -- (void)visibilityDidChange:(BOOL)isVisible -{ - [super visibilityDidChange:isVisible]; - - ASDN::MutexLocker l(_videoLock); - - if (_shouldAutoplay && _playerNode.isNodeLoaded) { - [self play]; - } else if (_shouldAutoplay) { - _shouldBePlaying = YES; - } - if (isVisible) { - if (_playerNode.isNodeLoaded) { - if (!_player) { - [self constructCurrentPlayerItemFromInitData]; - _player = [AVPlayer playerWithPlayerItem:_currentPlayerItem]; - _player.muted = _muted; - } - ((AVPlayerLayer *)_playerNode.layer).player = _player; - } - - if (_shouldBePlaying) { - [self play]; - } - } -} - - -#pragma mark - Video Properties - -- (void)setPlayButton:(ASButtonNode *)playButton -{ - ASDN::MutexLocker l(_videoLock); - - [_playButton removeTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; - [_playButton removeFromSupernode]; - - _playButton = playButton; - - [self addSubnode:playButton]; - - [_playButton addTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; -} - -- (ASButtonNode *)playButton -{ - ASDN::MutexLocker l(_videoLock); - - return _playButton; -} - -- (void)setAsset:(AVAsset *)asset -{ - ASDN::MutexLocker l(_videoLock); - - if (ASAssetIsEqual(asset, _asset)) { - return; - } - - _asset = asset; - - // FIXME: Adopt -setNeedsFetchData when it is available - if (self.interfaceState & ASInterfaceStateFetchData) { - [self fetchData]; - } -} - -- (AVAsset *)asset -{ - ASDN::MutexLocker l(_videoLock); - return _asset; -} - -- (AVPlayer *)player -{ - ASDN::MutexLocker l(_videoLock); - return _player; -} - -- (void)setGravity:(NSString *)gravity -{ - ASDN::MutexLocker l(_videoLock); - if (_playerNode.isNodeLoaded) { - ((AVPlayerLayer *)_playerNode.layer).videoGravity = gravity; - } - _placeholderImageNode.contentMode = ASContentModeFromVideoGravity(gravity); - _gravity = gravity; -} - -- (NSString *)gravity -{ - ASDN::MutexLocker l(_videoLock); - - return _gravity; -} - -- (BOOL)muted -{ - ASDN::MutexLocker l(_videoLock); - - return _muted; -} - -- (void)setMuted:(BOOL)muted -{ - ASDN::MutexLocker l(_videoLock); - - _player.muted = muted; - _muted = muted; -} - -#pragma mark - Video Playback - -- (void)play -{ - ASDN::MutexLocker l(_videoLock); - - if (!_spinner) { - _spinner = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{ - UIActivityIndicatorView *spinnnerView = [[UIActivityIndicatorView alloc] init]; - spinnnerView.color = [UIColor whiteColor]; - - return spinnnerView; - }]; - } - - if (!_playerNode) { - _playerNode = [self constructPlayerNode]; - - if ([self.subnodes containsObject:_playButton]) { - [self insertSubnode:_playerNode belowSubnode:_playButton]; - } else { - [self addSubnode:_playerNode]; - } - } - - [_player play]; - _shouldBePlaying = YES; - - [UIView animateWithDuration:0.15 animations:^{ - _playButton.alpha = 0.0; - }]; - - if (![self ready] && _shouldBePlaying && ASInterfaceStateIncludesVisible(self.interfaceState)) { - [self addSubnode:_spinner]; - [(UIActivityIndicatorView *)_spinner.view startAnimating]; - } -} - -- (BOOL)ready -{ - return _currentPlayerItem.status == AVPlayerItemStatusReadyToPlay; -} - -- (void)pause -{ - ASDN::MutexLocker l(_videoLock); - - [_player pause]; - [((UIActivityIndicatorView *)_spinner.view) stopAnimating]; - _shouldBePlaying = NO; - [UIView animateWithDuration:0.15 animations:^{ - _playButton.alpha = 1.0; - }]; -} - -- (BOOL)isPlaying -{ - ASDN::MutexLocker l(_videoLock); - - return (_player.rate > 0 && !_player.error); -} - - -#pragma mark - Playback observers - -- (void)didPlayToEnd:(NSNotification *)notification -{ - if ([_delegate respondsToSelector:@selector(videoPlaybackDidFinish:)]) { - [_delegate videoPlaybackDidFinish:self]; - } - [_player seekToTime:kCMTimeZero]; - - if (_shouldAutorepeat) { - [self play]; - } else { - [self pause]; - } -} - -- (void)errorWhilePlaying:(NSNotification *)notification -{ - if ([notification.name isEqualToString:AVPlayerItemFailedToPlayToEndTimeNotification]) { - NSLog(@"Failed to play video"); - } else if ([notification.name isEqualToString:AVPlayerItemNewErrorLogEntryNotification]) { - AVPlayerItem *item = (AVPlayerItem *)notification.object; - AVPlayerItemErrorLogEvent *logEvent = item.errorLog.events.lastObject; - NSLog(@"AVPlayerItem error log entry added for video with error %@ status %@", item.error, - (item.status == AVPlayerItemStatusFailed ? @"FAILED" : [NSString stringWithFormat:@"%ld", (long)item.status])); - NSLog(@"Item is %@", item); - - if (logEvent) { - NSLog(@"Log code %ld domain %@ comment %@", (long)logEvent.errorStatusCode, logEvent.errorDomain, logEvent.errorComment); - } - } -} - -- (void)willEnterForeground:(NSNotification *)notification -{ - ASDN::MutexLocker l(_videoLock); - - if (_shouldBePlaying) { - [self play]; - } -} - -- (void)didEnterBackground:(NSNotification *)notification -{ - ASDN::MutexLocker l(_videoLock); - - if (_shouldBePlaying) { - [self pause]; - _shouldBePlaying = YES; - } -} - -#pragma mark - Property Accessors for Tests - -- (ASDisplayNode *)spinner -{ - ASDN::MutexLocker l(_videoLock); - return _spinner; -} - -- (ASImageNode *)placeholderImageNode -{ - ASDN::MutexLocker l(_videoLock); - return _placeholderImageNode; -} - -- (AVPlayerItem *)currentItem -{ - ASDN::MutexLocker l(_videoLock); - return _currentPlayerItem; -} - -- (void)setCurrentItem:(AVPlayerItem *)currentItem -{ - ASDN::MutexLocker l(_videoLock); - _currentPlayerItem = currentItem; -} - -- (ASDisplayNode *)playerNode -{ - ASDN::MutexLocker l(_videoLock); - return _playerNode; -} - -- (void)setPlayerNode:(ASDisplayNode *)playerNode -{ - ASDN::MutexLocker l(_videoLock); - _playerNode = playerNode; -} - -- (void)setPlayer:(AVPlayer *)player -{ - ASDN::MutexLocker l(_videoLock); - _player = player; -} - -- (BOOL)shouldBePlaying -{ - ASDN::MutexLocker l(_videoLock); - return _shouldBePlaying; -} - -#pragma mark - Lifecycle - -- (void)dealloc -{ - [_playButton removeTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; - [self removePlayerItemObservers]; - - @try { - [_currentPlayerItem removeObserver:self forKeyPath:NSStringFromSelector(@selector(status))]; - } - @catch (NSException * __unused exception) { - NSLog(@"unnecessary removal in dealloc"); - } -} - -@end -#endif