From 06d4573b14ff1ea008ffaeadbbd049ed52971516 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Thu, 21 Apr 2016 08:59:14 +0100 Subject: [PATCH 1/5] [tvOS] Branch out tvOS specific code into it's own categories --- AsyncDisplayKit.xcodeproj/project.pbxproj | 32 ++++ AsyncDisplayKit/ASButtonNode.mm | 4 + AsyncDisplayKit/ASControlNode+tvOS.h | 15 ++ AsyncDisplayKit/ASControlNode+tvOS.m | 88 +++++++++++ AsyncDisplayKit/ASControlNode.mm | 76 --------- AsyncDisplayKit/ASEditableTextNode.mm | 3 + AsyncDisplayKit/ASImageNode+tvOS.h | 15 ++ AsyncDisplayKit/ASImageNode+tvOS.m | 179 ++++++++++++++++++++++ AsyncDisplayKit/ASImageNode.mm | 168 -------------------- AsyncDisplayKit/ASVideoNode.h | 3 +- 10 files changed, 337 insertions(+), 246 deletions(-) create mode 100644 AsyncDisplayKit/ASControlNode+tvOS.h create mode 100644 AsyncDisplayKit/ASControlNode+tvOS.m create mode 100644 AsyncDisplayKit/ASImageNode+tvOS.h create mode 100644 AsyncDisplayKit/ASImageNode+tvOS.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 5c12f1cac3..d7da12c111 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -304,6 +304,14 @@ 7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */; }; 81EE384F1C8E94F000456208 /* ASRunLoopQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; 81EE38501C8E94F000456208 /* ASRunLoopQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */; }; + 92074A611CC8BA1900918F75 /* ASImageNode+tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 92074A5F1CC8BA1900918F75 /* ASImageNode+tvOS.h */; }; + 92074A621CC8BA1900918F75 /* ASImageNode+tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 92074A5F1CC8BA1900918F75 /* ASImageNode+tvOS.h */; }; + 92074A631CC8BA1900918F75 /* ASImageNode+tvOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 92074A601CC8BA1900918F75 /* ASImageNode+tvOS.m */; }; + 92074A641CC8BA1900918F75 /* ASImageNode+tvOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 92074A601CC8BA1900918F75 /* ASImageNode+tvOS.m */; }; + 92074A671CC8BADA00918F75 /* ASControlNode+tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 92074A651CC8BADA00918F75 /* ASControlNode+tvOS.h */; }; + 92074A681CC8BADA00918F75 /* ASControlNode+tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 92074A651CC8BADA00918F75 /* ASControlNode+tvOS.h */; }; + 92074A691CC8BADA00918F75 /* ASControlNode+tvOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 92074A661CC8BADA00918F75 /* ASControlNode+tvOS.m */; }; + 92074A6A1CC8BADA00918F75 /* ASControlNode+tvOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 92074A661CC8BADA00918F75 /* ASControlNode+tvOS.m */; }; 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; 92DD2FE61BF4D05E0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; }; @@ -787,6 +795,10 @@ 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRelativeLayoutSpecSnapshotTests.mm; sourceTree = ""; }; 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRunLoopQueue.h; path = ../ASRunLoopQueue.h; sourceTree = ""; }; 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRunLoopQueue.mm; path = ../ASRunLoopQueue.mm; sourceTree = ""; }; + 92074A5F1CC8BA1900918F75 /* ASImageNode+tvOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASImageNode+tvOS.h"; sourceTree = ""; }; + 92074A601CC8BA1900918F75 /* ASImageNode+tvOS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASImageNode+tvOS.m"; sourceTree = ""; }; + 92074A651CC8BADA00918F75 /* ASControlNode+tvOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASControlNode+tvOS.h"; sourceTree = ""; }; + 92074A661CC8BADA00918F75 /* ASControlNode+tvOS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASControlNode+tvOS.m"; sourceTree = ""; }; 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMapNode.h; sourceTree = ""; }; 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMapNode.mm; sourceTree = ""; }; 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; @@ -1073,6 +1085,7 @@ 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */, 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */, DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */, + 92074A5E1CC8B9DD00918F75 /* tvOS */, 058D09E1195D050800B7D73C /* Details */, 058D0A01195D050800B7D73C /* Private */, AC6456051B0A333200CF11B8 /* Layout */, @@ -1350,6 +1363,17 @@ name = "Data Controller"; sourceTree = ""; }; + 92074A5E1CC8B9DD00918F75 /* tvOS */ = { + isa = PBXGroup; + children = ( + 92074A5F1CC8BA1900918F75 /* ASImageNode+tvOS.h */, + 92074A601CC8BA1900918F75 /* ASImageNode+tvOS.m */, + 92074A651CC8BADA00918F75 /* ASControlNode+tvOS.h */, + 92074A661CC8BADA00918F75 /* ASControlNode+tvOS.m */, + ); + name = tvOS; + sourceTree = ""; + }; AC6456051B0A333200CF11B8 /* Layout */ = { isa = PBXGroup; children = ( @@ -1484,6 +1508,7 @@ 058D0A4A195D05CB00B7D73C /* ASDisplayNode.h in Headers */, AC7A2C171BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */, 058D0A4D195D05CB00B7D73C /* ASDisplayNodeExtras.h in Headers */, + 92074A671CC8BADA00918F75 /* ASControlNode+tvOS.h in Headers */, 68355B3B1CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h in Headers */, 68B0277A1C1A79CC0041016B /* ASDisplayNode+Beta.h in Headers */, 767E7F8D1C9019130066C000 /* AsyncDisplayKit+Debug.h in Headers */, @@ -1566,6 +1591,7 @@ 058D0A66195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.h in Headers */, 205F0E0F1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h in Headers */, 058D0A6F195D05EC00B7D73C /* UIView+ASConvenience.h in Headers */, + 92074A611CC8BA1900918F75 /* ASImageNode+tvOS.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1609,6 +1635,7 @@ B35062131B010EFD0018CF92 /* ASBasicImageDownloader.h in Headers */, B35062461B010EFD0018CF92 /* ASBasicImageDownloaderInternal.h in Headers */, B35062151B010EFD0018CF92 /* ASBatchContext.h in Headers */, + 92074A681CC8BADA00918F75 /* ASControlNode+tvOS.h in Headers */, 044285081BAA63FE00D16268 /* ASBatchFetching.h in Headers */, AC026B701BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */, B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */, @@ -1619,6 +1646,7 @@ 254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */, 509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */, CC3B20841C3F76D600798563 /* ASPendingStateController.h in Headers */, + 92074A621CC8BA1900918F75 /* ASImageNode+tvOS.h in Headers */, B35061F71B010EFD0018CF92 /* ASCollectionViewProtocols.h in Headers */, DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */, B35061FA1B010EFD0018CF92 /* ASControlNode+Subclasses.h in Headers */, @@ -1980,6 +2008,7 @@ 698548651CA9E025008A345F /* ASEnvironment.m in Sources */, ACF6ED251B17843500DA7C62 /* ASLayout.mm in Sources */, DB55C2631C6408D6004EDCF5 /* _ASTransitionContext.m in Sources */, + 92074A631CC8BA1900918F75 /* ASImageNode+tvOS.m in Sources */, 251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */, ACF6ED271B17843500DA7C62 /* ASLayoutSpec.mm in Sources */, 257754B01BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm in Sources */, @@ -2015,6 +2044,7 @@ ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */, 257754A61BEE44CD00737CA5 /* ASTextKitAttributes.mm in Sources */, 81EE38501C8E94F000456208 /* ASRunLoopQueue.mm in Sources */, + 92074A691CC8BADA00918F75 /* ASControlNode+tvOS.m in Sources */, ACF6ED321B17843500DA7C62 /* ASStaticLayoutSpec.mm in Sources */, AC026B6B1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */, 68355B311CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */, @@ -2134,6 +2164,7 @@ CC3B208C1C3F7A5400798563 /* ASWeakSet.m in Sources */, B350621C1B010EFD0018CF92 /* ASFlowLayoutController.mm in Sources */, B350621E1B010EFD0018CF92 /* ASHighlightOverlayLayer.mm in Sources */, + 92074A641CC8BA1900918F75 /* ASImageNode+tvOS.m in Sources */, B35062541B010EFD0018CF92 /* ASImageNode+CGExtras.m in Sources */, 68355B401CB57A69001D4E68 /* ASImageContainerProtocolCategories.m in Sources */, B35062031B010EFD0018CF92 /* ASImageNode.mm in Sources */, @@ -2169,6 +2200,7 @@ DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */, AC026B6C1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */, 34EFC7741B701D0A00AD841F /* ASStaticLayoutSpec.mm in Sources */, + 92074A6A1CC8BADA00918F75 /* ASControlNode+tvOS.m in Sources */, DB78412E1C6BCE1600A9E2B4 /* _ASTransitionContext.m in Sources */, B350620B1B010EFD0018CF92 /* ASTableView.mm in Sources */, B350620E1B010EFD0018CF92 /* ASTextNode.mm in Sources */, diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index 5d875a3318..951e8bf7b6 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -69,7 +69,11 @@ { if (!_titleNode) { _titleNode = [[ASTextNode alloc] init]; +#if TARGET_OS_IOS + // tvOS needs access to the underlying view + // of the button node to add a touch handler. [_titleNode setLayerBacked:YES]; +#endif [_titleNode setFlexShrink:YES]; } return _titleNode; diff --git a/AsyncDisplayKit/ASControlNode+tvOS.h b/AsyncDisplayKit/ASControlNode+tvOS.h new file mode 100644 index 0000000000..c0d8f5e751 --- /dev/null +++ b/AsyncDisplayKit/ASControlNode+tvOS.h @@ -0,0 +1,15 @@ +// +// ASControlNode+tvOS.h +// AsyncDisplayKit +// +// Created by Aaron Schubert on 21/04/2016. +// Copyright © 2016 Facebook. All rights reserved. +// + +#if TARGET_OS_TV +#import + +@interface ASControlNode (tvOS) + +@end +#endif diff --git a/AsyncDisplayKit/ASControlNode+tvOS.m b/AsyncDisplayKit/ASControlNode+tvOS.m new file mode 100644 index 0000000000..bca3f1dc33 --- /dev/null +++ b/AsyncDisplayKit/ASControlNode+tvOS.m @@ -0,0 +1,88 @@ +// +// ASControlNode+tvOS.m +// AsyncDisplayKit +// +// Created by Aaron Schubert on 21/04/2016. +// Copyright © 2016 Facebook. All rights reserved. +// +#if TARGET_OS_TV +#import "ASControlNode+tvOS.h" + +@implementation ASControlNode (tvOS) + +#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); +} +@end +#endif diff --git a/AsyncDisplayKit/ASControlNode.mm b/AsyncDisplayKit/ASControlNode.mm index 8a912a7eaa..7d8f54a997 100644 --- a/AsyncDisplayKit/ASControlNode.mm +++ b/AsyncDisplayKit/ASControlNode.mm @@ -468,82 +468,6 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v { } -#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 diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index ba0b0b1f99..e81c086b5e 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -36,6 +36,8 @@ @implementation ASPanningOverriddenUITextView +#if TARGET_OS_IOS + // tvOS doesn't support self.scrollsToTop - (BOOL)scrollEnabled { return _shouldBlockPanGesture; @@ -48,6 +50,7 @@ [super setScrollEnabled:YES]; } +#endif - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { diff --git a/AsyncDisplayKit/ASImageNode+tvOS.h b/AsyncDisplayKit/ASImageNode+tvOS.h new file mode 100644 index 0000000000..adc4127bd0 --- /dev/null +++ b/AsyncDisplayKit/ASImageNode+tvOS.h @@ -0,0 +1,15 @@ +// +// ASImageNode+tvOS.h +// AsyncDisplayKit +// +// Created by Aaron Schubert on 21/04/2016. +// Copyright © 2016 Facebook. All rights reserved. +// + +#if TARGET_OS_TV +#import + +@interface ASImageNode (tvOS) +@property (nonatomic) BOOL isDefaultState; +@end +#endif diff --git a/AsyncDisplayKit/ASImageNode+tvOS.m b/AsyncDisplayKit/ASImageNode+tvOS.m new file mode 100644 index 0000000000..a7a210589d --- /dev/null +++ b/AsyncDisplayKit/ASImageNode+tvOS.m @@ -0,0 +1,179 @@ +// +// ASImageNode+tvOS.m +// AsyncDisplayKit +// +// Created by Aaron Schubert on 21/04/2016. +// Copyright © 2016 Facebook. All rights reserved. +// + +#if TARGET_OS_TV +#import "ASImageNode+tvOS.h" + +@implementation ASImageNode (tvOS) + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event +{ + [super touchesBegan:touches withEvent:event]; + self.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 (!self.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 (!self.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"]; + self.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; +} + +@end +#endif diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index 2c82dd9446..97fc563ebb 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -459,174 +459,6 @@ _imageModificationBlock = imageModificationBlock; } - -#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 { diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h index 663ac2def6..4ea0006877 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/AsyncDisplayKit/ASVideoNode.h @@ -64,6 +64,5 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)videoNodeWasTapped:(ASVideoNode *)videoNode; @end -#endif - NS_ASSUME_NONNULL_END +#endif From 39a6aa3437dacad865017a76587a5550db53d8f4 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Thu, 21 Apr 2016 09:02:31 +0100 Subject: [PATCH 2/5] [tvOS] Improve naming of method --- AsyncDisplayKit/ASControlNode+tvOS.m | 4 ++-- AsyncDisplayKit/ASControlNode.h | 2 +- AsyncDisplayKit/ASImageNode+tvOS.m | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/AsyncDisplayKit/ASControlNode+tvOS.m b/AsyncDisplayKit/ASControlNode+tvOS.m index bca3f1dc33..97c3975bb1 100644 --- a/AsyncDisplayKit/ASControlNode+tvOS.m +++ b/AsyncDisplayKit/ASControlNode+tvOS.m @@ -45,7 +45,7 @@ } else{ //Not focused [coordinator addCoordinatedAnimations:^{ - [self setDefaultState]; + [self setDefaultFocusAppearance]; } completion:nil]; } } @@ -74,7 +74,7 @@ layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; } -- (void)setDefaultState +- (void)setDefaultFocusAppearance { CALayer *layer = self.layer; layer.shadowOffset = CGSizeZero; diff --git a/AsyncDisplayKit/ASControlNode.h b/AsyncDisplayKit/ASControlNode.h index 24e47b3e72..b330a47ad2 100644 --- a/AsyncDisplayKit/ASControlNode.h +++ b/AsyncDisplayKit/ASControlNode.h @@ -126,7 +126,7 @@ typedef NS_OPTIONS(NSUInteger, ASControlState) { /** @abstract How the node looks when it isn't focused. Exposed here so that subclasses can override. */ -- (void)setDefaultState; +- (void)setDefaultFocusAppearance; #endif @end diff --git a/AsyncDisplayKit/ASImageNode+tvOS.m b/AsyncDisplayKit/ASImageNode+tvOS.m index a7a210589d..b7055f07fc 100644 --- a/AsyncDisplayKit/ASImageNode+tvOS.m +++ b/AsyncDisplayKit/ASImageNode+tvOS.m @@ -96,7 +96,7 @@ view.layer.transform = scaleAndTransform; }]; } else { - [self setDefaultState]; + [self setDefaultFocusAppearance]; } } @@ -130,7 +130,7 @@ } }]; } else { - [self setDefaultState]; + [self setDefaultFocusAppearance]; } } @@ -146,7 +146,7 @@ view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25); } -- (void)setDefaultState +- (void)setDefaultFocusAppearance { UIView *view = [self getView]; CALayer *layer = view.layer; From e2fa2f8192ddce3f6ab6dfe871d5d52756eadbc1 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Thu, 21 Apr 2016 09:23:47 +0100 Subject: [PATCH 3/5] [tvOS] Address comments on previous PR. --- AsyncDisplayKit/ASControlNode.mm | 3 ++- AsyncDisplayKit/ASImageNode+tvOS.h | 2 +- AsyncDisplayKit/ASImageNode+tvOS.m | 35 ++++++++++++++++++------------ AsyncDisplayKit/ASImageNode.h | 9 ++++++++ AsyncDisplayKit/ASImageNode.mm | 5 ----- 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/AsyncDisplayKit/ASControlNode.mm b/AsyncDisplayKit/ASControlNode.mm index 7d8f54a997..e81a7805df 100644 --- a/AsyncDisplayKit/ASControlNode.mm +++ b/AsyncDisplayKit/ASControlNode.mm @@ -96,7 +96,8 @@ static BOOL _enableHitTestDebug = NO; #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. + // On tvOS all controls, 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)]; diff --git a/AsyncDisplayKit/ASImageNode+tvOS.h b/AsyncDisplayKit/ASImageNode+tvOS.h index adc4127bd0..9a06a09242 100644 --- a/AsyncDisplayKit/ASImageNode+tvOS.h +++ b/AsyncDisplayKit/ASImageNode+tvOS.h @@ -10,6 +10,6 @@ #import @interface ASImageNode (tvOS) -@property (nonatomic) BOOL isDefaultState; @end #endif + diff --git a/AsyncDisplayKit/ASImageNode+tvOS.m b/AsyncDisplayKit/ASImageNode+tvOS.m index b7055f07fc..29181d0604 100644 --- a/AsyncDisplayKit/ASImageNode+tvOS.m +++ b/AsyncDisplayKit/ASImageNode+tvOS.m @@ -8,13 +8,14 @@ #if TARGET_OS_TV #import "ASImageNode+tvOS.h" +#import @implementation ASImageNode (tvOS) - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesBegan:touches withEvent:event]; - self.isDefaultState = NO; + self.isDefaultFocusAppearance = NO; UIView *view = [self getView]; CALayer *layer = view.layer; @@ -47,16 +48,27 @@ [CATransaction commit]; } -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event +{ [super touchesMoved:touches withEvent:event]; - if (!self.isDefaultState) { + // TODO: Clean up, and improve visuals. + + if (!self.isDefaultFocusAppearance) { + // This view may correspond to either self.view + // or our superview if we are in a ASCellNode 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. + + // 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. + + // We get the touch location in self.view because + // we are operating in that coordinate system. + // BUT we apply our transforms to *view since we want to apply + // the transforms to the root view (L: 107) CGPoint point = [touch locationInView:self.view]; float pitch = 0; float yaw = 0; @@ -87,8 +99,8 @@ } } - 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 pitchTransform = CATransform3DMakeRotation(GLKMathDegreesToRadians(pitch),1.0,0.0,0.0); + CATransform3D yawTransform = CATransform3DMakeRotation(GLKMathDegreesToRadians(yaw),0.0,1.0,0.0); CATransform3D transform = CATransform3DConcat(pitchTransform, yawTransform); CATransform3D scaleAndTransform = CATransform3DConcat(transform, CATransform3DMakeAffineTransform(CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25))); @@ -109,7 +121,7 @@ - (void)finishTouches { - if (!self.isDefaultState) { + if (!self.isDefaultFocusAppearance) { UIView *view = [self getView]; CALayer *layer = view.layer; @@ -157,7 +169,7 @@ layer.shadowPath = nil; [layer removeAnimationForKey:@"shadowOffset"]; [layer removeAnimationForKey:@"shadowOpacity"]; - self.isDefaultState = YES; + self.isDefaultFocusAppearance = YES; } - (UIView *)getView @@ -170,10 +182,5 @@ return view; } -- (float)degressToRadians:(float)value -{ - return value * M_PI / 180; -} - @end #endif diff --git a/AsyncDisplayKit/ASImageNode.h b/AsyncDisplayKit/ASImageNode.h index 8ce01b6397..c71e4bcf5d 100644 --- a/AsyncDisplayKit/ASImageNode.h +++ b/AsyncDisplayKit/ASImageNode.h @@ -110,6 +110,15 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image); */ - (void)setNeedsDisplayWithCompletion:(void (^ _Nullable)(BOOL canceled))displayCompletionBlock; +#if TARGET_OS_TV +/** + * A bool to track if the current appearance of the node + * is the default focus appearance. + * Exposed here so the category methods can set it. + */ +@property (nonatomic, assign) BOOL isDefaultFocusAppearance; +#endif + @end diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index 97fc563ebb..3e9b9e7757 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -73,11 +73,6 @@ void (^_displayCompletionBlock)(BOOL canceled); ASDN::RecursiveMutex _imageLock; - -#if TARGET_OS_TV - //tvOS - BOOL isDefaultState; -#endif // Cropping. BOOL _cropEnabled; // Defaults to YES. From 7b0a8f5af32a35e3f83297fa3ea3cb8dbb0118df Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Thu, 21 Apr 2016 09:33:32 +0100 Subject: [PATCH 4/5] [tvOS] Improve -getView by making use of helper method --- AsyncDisplayKit/ASImageNode+tvOS.m | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASImageNode+tvOS.m b/AsyncDisplayKit/ASImageNode+tvOS.m index 29181d0604..ac7647aab6 100644 --- a/AsyncDisplayKit/ASImageNode+tvOS.m +++ b/AsyncDisplayKit/ASImageNode+tvOS.m @@ -9,6 +9,7 @@ #if TARGET_OS_TV #import "ASImageNode+tvOS.h" #import +#import "ASDisplayNodeExtras.h" @implementation ASImageNode (tvOS) @@ -175,9 +176,11 @@ - (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; + UIView *rootView = ASDisplayNodeUltimateParentOfNode(self).view + // TODO: This needs to be re-visited to handle all possibilities. + // 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(rootView.frame.size, view.frame.size) && rootView) { + view = rootView; } return view; } From ebf548fead8749fa853c922a93804835da39f898 Mon Sep 17 00:00:00 2001 From: Aaron Schubert Date: Thu, 21 Apr 2016 09:39:01 +0100 Subject: [PATCH 5/5] [tvOS] Make -getView a single line --- AsyncDisplayKit/ASImageNode+tvOS.m | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/AsyncDisplayKit/ASImageNode+tvOS.m b/AsyncDisplayKit/ASImageNode+tvOS.m index ac7647aab6..886f4ea29e 100644 --- a/AsyncDisplayKit/ASImageNode+tvOS.m +++ b/AsyncDisplayKit/ASImageNode+tvOS.m @@ -175,14 +175,9 @@ - (UIView *)getView { - UIView *view = self.view; - UIView *rootView = ASDisplayNodeUltimateParentOfNode(self).view // TODO: This needs to be re-visited to handle all possibilities. // 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(rootView.frame.size, view.frame.size) && rootView) { - view = rootView; - } - return view; + return ASDisplayNodeUltimateParentOfNode(self).view; } @end