Merge remote-tracking branch 'facebook/master' into ASVideoPlayerNode

This commit is contained in:
Erekle
2016-05-11 20:43:57 +04:00
16 changed files with 317 additions and 195 deletions

View File

@@ -342,7 +342,7 @@ NS_ASSUME_NONNULL_BEGIN
* This is a node-based UICollectionViewDataSource.
*/
#define ASCollectionViewDataSource ASCollectionDataSource
@protocol ASCollectionDataSource <ASCommonCollectionViewDataSource, NSObject>
@protocol ASCollectionDataSource <ASCommonCollectionViewDataSource>
@optional

View File

@@ -343,10 +343,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
{
// Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
// the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource
// will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to nil out
// super.dataSource in this case because calls to ASCollectionViewProxy will start failing and cause crashes.
super.dataSource = nil;
// will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to hold a strong
// reference to the old dataSource in this case because calls to ASCollectionViewProxy will start failing and cause crashes.
NS_VALID_UNTIL_END_OF_SCOPE id oldDataSource = super.dataSource;
if (asyncDataSource == nil) {
_asyncDataSource = nil;
_proxyDataSource = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
@@ -375,13 +375,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
{
// Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
// the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate
// will return as nil (ARC magic) even though the _proxyDelegate still exists. It's really important to nil out
// super.delegate in this case because calls to ASCollectionViewProxy will start failing and cause crashes.
// Order is important here, the asyncDelegate must be callable while nilling super.delegate to avoid random crashes
// in UIScrollViewAccessibility.
super.delegate = nil;
// will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to hold a strong
// reference to the old delegate in this case because calls to ASCollectionViewProxy will start failing and cause crashes.
NS_VALID_UNTIL_END_OF_SCOPE id oldDelegate = super.delegate;
if (asyncDelegate == nil) {
_asyncDelegate = nil;

View File

@@ -11,7 +11,8 @@
@class ASPagerNode;
@class ASPagerFlowLayout;
@protocol ASPagerNodeDataSource <NSObject>
#define ASPagerNodeDataSource ASPagerDataSource
@protocol ASPagerDataSource <NSObject>
/**
* This method replaces -collectionView:numberOfItemsInSection:
@@ -65,25 +66,30 @@
@end
@protocol ASPagerDelegate <ASCollectionDelegate>
@end
@interface ASPagerNode : ASCollectionNode
// Configures a default horizontal, paging flow layout with 0 inter-item spacing.
/// Configures a default horizontal, paging flow layout with 0 inter-item spacing.
- (instancetype)init;
// Initializer with custom-configured flow layout properties.
/// Initializer with custom-configured flow layout properties.
- (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout;
// Data Source is required, and uses a different protocol from ASCollectionNode.
- (void)setDataSource:(id <ASPagerNodeDataSource>)dataSource;
- (id <ASPagerNodeDataSource>)dataSource;
/// Data Source is required, and uses a different protocol from ASCollectionNode.
- (void)setDataSource:(id <ASPagerDataSource>)dataSource;
- (id <ASPagerDataSource>)dataSource;
// Delegate is optional, and uses the same protocol as ASCollectionNode.
// This includes UIScrollViewDelegate as well as most methods from UICollectionViewDelegate, like willDisplay...
@property (nonatomic, weak) id <ASCollectionDelegate> delegate;
@property (nonatomic, weak) id <ASPagerDelegate> delegate;
// The underlying ASCollectionView object.
/// The underlying ASCollectionView object.
@property (nonatomic, readonly) ASCollectionView *view;
/// Scroll the contents of the receiver to ensure that the page is visible.
- (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated;
@end

View File

@@ -16,7 +16,7 @@
{
ASPagerFlowLayout *_flowLayout;
ASPagerNodeProxy *_proxy;
__weak id <ASPagerNodeDataSource> _pagerDataSource;
__weak id <ASPagerDataSource> _pagerDataSource;
BOOL _pagerDataSourceImplementsNodeBlockAtIndex;
BOOL _pagerDataSourceImplementsConstrainedSizeForNode;
}
@@ -111,12 +111,12 @@
#pragma mark - Data Source Proxy
- (id <ASPagerNodeDataSource>)dataSource
- (id <ASPagerDataSource>)dataSource
{
return _pagerDataSource;
}
- (void)setDataSource:(id <ASPagerNodeDataSource>)pagerDataSource
- (void)setDataSource:(id <ASPagerDataSource>)pagerDataSource
{
if (pagerDataSource != _pagerDataSource) {
_pagerDataSource = pagerDataSource;

View File

@@ -268,10 +268,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
{
// Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
// the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource
// will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to nil out
// super.dataSource in this case because calls to ASTableViewProxy will start failing and cause crashes.
super.dataSource = nil;
// will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to hold a strong
// reference to the old dataSource in this case because calls to ASTableViewProxy will start failing and cause crashes.
NS_VALID_UNTIL_END_OF_SCOPE id oldDataSource = self.dataSource;
if (asyncDataSource == nil) {
_asyncDataSource = nil;
@@ -299,13 +298,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
{
// Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
// the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate
// will return as nil (ARC magic) even though the _proxyDelegate still exists. It's really important to nil out
// super.delegate in this case because calls to ASTableViewProxy will start failing and cause crashes.
// Order is important here, the asyncDelegate must be callable while nilling super.delegate to avoid random crashes
// in UIScrollViewAccessibility.
super.delegate = nil;
// will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to hold a strong
// reference to the old delegate in this case because calls to ASTableViewProxy will start failing and cause crashes.
NS_VALID_UNTIL_END_OF_SCOPE id oldDelegate = super.delegate;
if (asyncDelegate == nil) {
_asyncDelegate = nil;

View File

@@ -34,19 +34,19 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) {
@interface ASTextNode : ASControlNode
/**
@abstract The attributed string to show.
@abstract The styled text displayed by the node.
@discussion Defaults to nil, no text is shown.
For inline image attachments, add an attribute of key NSAttachmentAttributeName, with a value of an NSTextAttachment.
*/
@property (nullable, nonatomic, copy) NSAttributedString *attributedString;
@property (nullable, nonatomic, copy) NSAttributedString *attributedText;
#pragma mark - Truncation
/**
@abstract The attributedString to use when the text must be truncated.
@abstract The attributedText to use when the text must be truncated.
@discussion Defaults to a localized ellipsis character.
*/
@property (nullable, nonatomic, copy) NSAttributedString *truncationAttributedString;
@property (nullable, nonatomic, copy) NSAttributedString *truncationAttributedText;
/**
@summary The second attributed string appended for truncation.
@@ -270,4 +270,28 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) {
@end
/**
* @abstract Text node deprecated properties
*/
@interface ASTextNode (Deprecated)
/**
The attributedString and attributedText properties are equivalent, but attributedText is now the standard API
name in order to match UILabel and ASEditableTextNode.
@see attributedText
*/
@property (nullable, nonatomic, copy) NSAttributedString *attributedString;
/**
The truncationAttributedString and truncationAttributedText properties are equivalent, but attributedText is now the
standard API name in order to match UILabel and ASEditableTextNode.
@see truncationAttributedText
*/
@property (nullable, nonatomic, copy) NSAttributedString *truncationAttributedString;
@end
NS_ASSUME_NONNULL_END

View File

@@ -67,7 +67,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
NSArray *_exclusionPaths;
NSAttributedString *_composedTruncationString;
NSAttributedString *_composedTruncationText;
NSString *_highlightedLinkAttributeName;
id _highlightedLinkAttributeValue;
@@ -105,7 +105,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
self.needsDisplayOnBoundsChange = YES;
_truncationMode = NSLineBreakByWordWrapping;
_composedTruncationString = DefaultTruncationAttributedString();
_composedTruncationText = DefaultTruncationAttributedString();
// The common case is for a text node to be non-opaque and blended over some background.
self.opaque = NO;
@@ -158,8 +158,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (NSString *)description
{
NSString *plainString = [[_attributedString string] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSString *truncationString = [_composedTruncationString string];
NSString *plainString = [[_attributedText string] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSString *truncationString = [_composedTruncationText string];
if (plainString.length > 50)
plainString = [[plainString substringToIndex:50] stringByAppendingString:@"\u2026"];
return [NSString stringWithFormat:@"<%@: %p; text = \"%@\"; truncation string = \"%@\"; frame = %@; renderer = %p>", self.class, self, plainString, truncationString, self.nodeLoaded ? NSStringFromCGRect(self.layer.frame) : nil, _renderer];
@@ -237,8 +237,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (ASTextKitAttributes)_rendererAttributes
{
return {
.attributedString = _attributedString,
.truncationAttributedString = _composedTruncationString,
.attributedString = _attributedText,
.truncationAttributedString = _composedTruncationText,
.lineBreakMode = _truncationMode,
.maximumNumberOfLines = _maximumNumberOfLines,
.exclusionPaths = _exclusionPaths,
@@ -340,10 +340,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
[self setNeedsDisplay];
CGSize size = [[self _renderer] size];
if (self.attributedString.length > 0) {
if (_attributedText.length > 0) {
CGFloat screenScale = ASScreenScale();
self.ascender = round([[_attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * screenScale)/screenScale;
self.descender = round([[_attributedString attribute:NSFontAttributeName atIndex:_attributedString.length - 1 effectiveRange:NULL] descender] * screenScale)/screenScale;
self.ascender = round([[_attributedText attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * screenScale)/screenScale;
self.descender = round([[_attributedText attribute:NSFontAttributeName atIndex:_attributedText.length - 1 effectiveRange:NULL] descender] * screenScale)/screenScale;
if (_renderer.currentScaleFactor > 0 && _renderer.currentScaleFactor < 1.0) {
// while not perfect, this is a good estimate of what the ascender of the scaled font will be.
self.ascender *= _renderer.currentScaleFactor;
@@ -355,28 +355,28 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
#pragma mark - Modifying User Text
- (void)setAttributedString:(NSAttributedString *)attributedString
- (void)setAttributedText:(NSAttributedString *)attributedText
{
if (attributedString == nil) {
attributedString = [[NSAttributedString alloc] initWithString:@"" attributes:nil];
if (attributedText == nil) {
attributedText = [[NSAttributedString alloc] initWithString:@"" attributes:nil];
}
if (ASObjectIsEqual(attributedString, _attributedString)) {
if (ASObjectIsEqual(attributedText, _attributedText)) {
return;
}
_attributedString = ASCleanseAttributedStringOfCoreTextAttributes(attributedString);
_attributedText = ASCleanseAttributedStringOfCoreTextAttributes(attributedText);
if (_attributedString.length > 0) {
if (_attributedText.length > 0) {
CGFloat screenScale = ASScreenScale();
self.ascender = round([[_attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * screenScale)/screenScale;
self.descender = round([[_attributedString attribute:NSFontAttributeName atIndex:_attributedString.length - 1 effectiveRange:NULL] descender] * screenScale)/screenScale;
self.ascender = round([[_attributedText attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * screenScale)/screenScale;
self.descender = round([[_attributedText attribute:NSFontAttributeName atIndex:_attributedText.length - 1 effectiveRange:NULL] descender] * screenScale)/screenScale;
}
// Sync the truncation string with attributes from the updated _attributedString
// Without this, the size calculation of the text with truncation applied will
// not take into account the attributes of attributedString in the last line
[self _updateComposedTruncationString];
// not take into account the attributes of attributedText in the last line
[self _updateComposedTruncationText];
// We need an entirely new renderer
[self _invalidateRenderer];
@@ -386,10 +386,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
[self setNeedsDisplay];
self.accessibilityLabel = _attributedString.string;
self.accessibilityLabel = _attributedText.string;
// We're an accessibility element by default if there is a string.
self.isAccessibilityElement = _attributedString.length != 0;
self.isAccessibilityElement = _attributedText.length != 0;
}
#pragma mark - Text Layout
@@ -470,7 +470,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
{
ASTextKitRenderer *renderer = [self _renderer];
NSRange visibleRange = renderer.visibleRanges[0];
NSAttributedString *attributedString = _attributedString;
NSAttributedString *attributedString = _attributedText;
NSRange clampedRange = NSIntersectionRange(visibleRange, NSMakeRange(0, attributedString.length));
// Check in a 9-point region around the actual touch point so we make sure
@@ -1023,14 +1023,14 @@ static NSAttributedString *DefaultTruncationAttributedString()
return defaultTruncationAttributedString;
}
- (void)setTruncationAttributedString:(NSAttributedString *)truncationAttributedString
- (void)setTruncationAttributedText:(NSAttributedString *)truncationAttributedText
{
if (ASObjectIsEqual(_truncationAttributedString, truncationAttributedString)) {
if (ASObjectIsEqual(_truncationAttributedText, truncationAttributedText)) {
return;
}
_truncationAttributedString = [truncationAttributedString copy];
[self _invalidateTruncationString];
_truncationAttributedText = [truncationAttributedText copy];
[self _invalidateTruncationText];
}
- (void)setAdditionalTruncationMessage:(NSAttributedString *)additionalTruncationMessage
@@ -1040,7 +1040,7 @@ static NSAttributedString *DefaultTruncationAttributedString()
}
_additionalTruncationMessage = [additionalTruncationMessage copy];
[self _invalidateTruncationString];
[self _invalidateTruncationText];
}
- (void)setTruncationMode:(NSLineBreakMode)truncationMode
@@ -1055,7 +1055,7 @@ static NSAttributedString *DefaultTruncationAttributedString()
- (BOOL)isTruncated
{
NSRange visibleRange = [self _renderer].visibleRanges[0];
return visibleRange.length < _attributedString.length;
return visibleRange.length < _attributedText.length;
}
- (void)setPointSizeScaleFactors:(NSArray *)pointSizeScaleFactors
@@ -1082,14 +1082,14 @@ static NSAttributedString *DefaultTruncationAttributedString()
#pragma mark - Truncation Message
- (void)_updateComposedTruncationString
- (void)_updateComposedTruncationText
{
_composedTruncationString = [self _prepareTruncationStringForDrawing:[self _composedTruncationString]];
_composedTruncationText = [self _prepareTruncationStringForDrawing:[self _composedTruncationText]];
}
- (void)_invalidateTruncationString
- (void)_invalidateTruncationText
{
[self _updateComposedTruncationString];
[self _updateComposedTruncationText];
[self _invalidateRenderer];
[self setNeedsDisplay];
}
@@ -1111,7 +1111,7 @@ static NSAttributedString *DefaultTruncationAttributedString()
NSUInteger additionalTruncationMessageLength = _additionalTruncationMessage.length;
// We get the location of the truncation token, then add the length of the
// truncation attributed string +1 for the space between.
NSRange range = NSMakeRange(truncationTokenIndex + _truncationAttributedString.length + 1, additionalTruncationMessageLength);
NSRange range = NSMakeRange(truncationTokenIndex + _truncationAttributedText.length + 1, additionalTruncationMessageLength);
return range;
}
@@ -1120,24 +1120,24 @@ static NSAttributedString *DefaultTruncationAttributedString()
* additional truncation message and a truncation attributed string, they will
* be properly composed.
*/
- (NSAttributedString *)_composedTruncationString
- (NSAttributedString *)_composedTruncationText
{
//If we have neither return the default
if (!_additionalTruncationMessage && !_truncationAttributedString) {
return _composedTruncationString;
if (!_additionalTruncationMessage && !_truncationAttributedText) {
return _composedTruncationText;
}
// Short circuit if we only have one or the other.
if (!_additionalTruncationMessage) {
return _truncationAttributedString;
return _truncationAttributedText;
}
if (!_truncationAttributedString) {
if (!_truncationAttributedText) {
return _additionalTruncationMessage;
}
// If we've reached this point, both _additionalTruncationMessage and
// _truncationAttributedString are present. Compose them.
NSMutableAttributedString *newComposedTruncationString = [[NSMutableAttributedString alloc] initWithAttributedString:_truncationAttributedString];
NSMutableAttributedString *newComposedTruncationString = [[NSMutableAttributedString alloc] initWithAttributedString:_truncationAttributedText];
[newComposedTruncationString replaceCharactersInRange:NSMakeRange(newComposedTruncationString.length, 0) withString:@" "];
[newComposedTruncationString appendAttributedString:_additionalTruncationMessage];
return newComposedTruncationString;
@@ -1153,9 +1153,9 @@ static NSAttributedString *DefaultTruncationAttributedString()
truncationString = ASCleanseAttributedStringOfCoreTextAttributes(truncationString);
NSMutableAttributedString *truncationMutableString = [truncationString mutableCopy];
// Grab the attributes from the full string
if (_attributedString.length > 0) {
NSAttributedString *originalString = _attributedString;
NSInteger originalStringLength = _attributedString.length;
if (_attributedText.length > 0) {
NSAttributedString *originalString = _truncationAttributedText;
NSInteger originalStringLength = _truncationAttributedText.length;
// Add any of the original string's attributes to the truncation string,
// but don't overwrite any of the truncation string's attributes
NSDictionary *originalStringAttributes = [originalString attributesAtIndex:originalStringLength-1 effectiveRange:NULL];
@@ -1170,3 +1170,27 @@ static NSAttributedString *DefaultTruncationAttributedString()
}
@end
@implementation ASTextNode (Deprecated)
- (void)setAttributedString:(NSAttributedString *)attributedString
{
self.attributedText = attributedString;
}
- (NSAttributedString *)attributedString
{
return self.attributedText;
}
- (void)setTruncationAttributedString:(NSAttributedString *)truncationAttributedString
{
self.truncationAttributedText = truncationAttributedString;
}
- (NSAttributedString *)truncationAttributedString
{
return self.truncationAttributedText;
}
@end

View File

@@ -64,9 +64,9 @@ static NSString * const kStatus = @"status";
ASImageNode *_placeholderImageNode; // TODO: Make ASVideoNode an ASImageNode subclass; remove this.
ASButtonNode *_playButton;
ASButtonNode *_playButtonNode;
ASDisplayNode *_playerNode;
ASDisplayNode *_spinner;
ASDisplayNode *_spinnerNode;
NSString *_gravity;
}
@@ -185,6 +185,45 @@ static NSString * const kStatus = @"status";
[notificationCenter removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:playerItem];
}
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
// All subnodes should taking the whole node frame
CGSize maxSize = constrainedSize.max;
if (!CGSizeEqualToSize(self.preferredFrameSize, CGSizeZero)) {
maxSize = self.preferredFrameSize;
}
// Prevent crashes through if infinite width or height
if (isinf(maxSize.width) || isinf(maxSize.height)) {
ASDisplayNodeAssert(NO, @"Infinite width or height in ASVideoNode");
maxSize = CGSizeZero;
}
// Stretch out play button, placeholder image player node to the max size
NSMutableArray *children = [NSMutableArray array];
if (_playButtonNode) {
_playButtonNode.preferredFrameSize = maxSize;
[children addObject:_playButtonNode];
}
if (_placeholderImageNode) {
_placeholderImageNode.preferredFrameSize = maxSize;
[children addObject:_placeholderImageNode];
}
if (_playerNode) {
_playerNode.preferredFrameSize = maxSize;
[children addObject:_playerNode];
}
// Center spinner node
if (_spinnerNode) {
ASCenterLayoutSpec *centerLayoutSpec = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionDefault child:_spinnerNode];
centerLayoutSpec.sizeRange = ASRelativeSizeRangeMakeWithExactCGSize(maxSize);
[children addObject:_spinnerNode];
}
return [ASStaticLayoutSpec staticLayoutSpecWithChildren:children];
}
- (void)layout
{
[super layout];
@@ -192,17 +231,9 @@ static NSString * const kStatus = @"status";
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);
CGFloat horizontalDiff = (CGRectGetWidth(bounds) - CGRectGetWidth(_playButtonNode.bounds))/2;
CGFloat verticalDiff = (CGRectGetHeight(bounds) - CGRectGetHeight(_playButtonNode.bounds))/2;
_playButtonNode.hitTestSlop = UIEdgeInsetsMake(-verticalDiff, -horizontalDiff, -verticalDiff, -horizontalDiff);
}
- (void)generatePlaceholderImage
@@ -372,6 +403,7 @@ static NSString * const kStatus = @"status";
#pragma mark - Video Properties
- (void)setPlayerState:(ASVideoNodePlayerState)playerState
{
ASDN::MutexLocker l(_videoLock);
@@ -387,28 +419,26 @@ static NSString * const kStatus = @"status";
}
_playerState = playerState;
}
- (void)setPlayButton:(ASButtonNode *)playButton
{
ASDN::MutexLocker l(_videoLock);
[_playButton removeTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside];
[_playButton removeFromSupernode];
[_playButtonNode removeTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside];
[_playButtonNode removeFromSupernode];
_playButtonNode = playButton;
[_playButtonNode addTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside];
_playButton = playButton;
[self addSubnode:playButton];
[_playButton addTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside];
[self setNeedsLayout];
}
- (ASButtonNode *)playButton
{
ASDN::MutexLocker l(_videoLock);
return _playButton;
return _playButtonNode;
}
- (void)setAsset:(AVAsset *)asset
@@ -473,14 +503,12 @@ static NSString * const kStatus = @"status";
- (NSString *)gravity
{
ASDN::MutexLocker l(_videoLock);
return _gravity;
}
- (BOOL)muted
{
ASDN::MutexLocker l(_videoLock);
return _muted;
}
@@ -509,11 +537,13 @@ static NSString * const kStatus = @"status";
if (_playerNode == nil) {
_playerNode = [self constructPlayerNode];
if (_playButton.supernode == self) {
[self insertSubnode:_playerNode belowSubnode:_playButton];
if (_playButtonNode.supernode == self) {
[self insertSubnode:_playerNode belowSubnode:_playButtonNode];
} else {
[self addSubnode:_playerNode];
}
[self setNeedsLayout];
}
@@ -521,7 +551,7 @@ static NSString * const kStatus = @"status";
_shouldBePlaying = YES;
[UIView animateWithDuration:0.15 animations:^{
_playButton.alpha = 0.0;
_playButtonNode.alpha = 0.0;
}];
if (![self ready]) {
[self showSpinner];
@@ -540,28 +570,29 @@ static NSString * const kStatus = @"status";
{
ASDN::MutexLocker l(_videoLock);
if (!_spinner) {
_spinner = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{
if (!_spinnerNode) {
_spinnerNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{
UIActivityIndicatorView *spinnnerView = [[UIActivityIndicatorView alloc] init];
spinnnerView.color = [UIColor whiteColor];
return spinnnerView;
}];
[self addSubnode:_spinner];
_spinnerNode.preferredFrameSize = CGSizeMake(44.0, 44.0);
[self addSubnode:_spinnerNode];
[self setNeedsLayout];
}
[(UIActivityIndicatorView *)_spinner.view startAnimating];
[(UIActivityIndicatorView *)_spinnerNode.view startAnimating];
}
- (void)removeSpinner
{
ASDN::MutexLocker l(_videoLock);
if (!_spinner) {
if (!_spinnerNode) {
return;
}
[_spinner removeFromSupernode];
_spinner = nil;
[_spinnerNode removeFromSupernode];
_spinnerNode = nil;
}
- (void)pause
@@ -575,7 +606,7 @@ static NSString * const kStatus = @"status";
[self removeSpinner];
_shouldBePlaying = NO;
[UIView animateWithDuration:0.15 animations:^{
_playButton.alpha = 1.0;
_playButtonNode.alpha = 1.0;
}];
}
@@ -636,7 +667,7 @@ static NSString * const kStatus = @"status";
- (ASDisplayNode *)spinner
{
ASDN::MutexLocker l(_videoLock);
return _spinner;
return _spinnerNode;
}
- (ASImageNode *)placeholderImageNode
@@ -672,6 +703,8 @@ static NSString * const kStatus = @"status";
{
ASDN::MutexLocker l(_videoLock);
_playerNode = playerNode;
[self setNeedsLayout];
}
- (void)setPlayer:(AVPlayer *)player
@@ -700,7 +733,7 @@ static NSString * const kStatus = @"status";
{
[_player removeTimeObserver:_timeObserver];
_timeObserver = nil;
[_playButton removeTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside];
[_playButtonNode removeTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside];
[self removePlayerItemObservers:_currentPlayerItem];
}

View File

@@ -77,7 +77,7 @@
- (BOOL)interceptsSelector:(SEL)selector
{
return (
// handled by ASPagerNodeDataSource node<->cell machinery
// handled by ASPagerDataSource node<->cell machinery
selector == @selector(collectionView:nodeForItemAtIndexPath:) ||
selector == @selector(collectionView:nodeBlockForItemAtIndexPath:) ||
selector == @selector(collectionView:numberOfItemsInSection:) ||
@@ -125,7 +125,15 @@
if (_target) {
return [_target respondsToSelector:aSelector] ? _target : nil;
} else {
[_interceptor proxyTargetHasDeallocated:self];
// The _interceptor needs to be nilled out in this scenario. For that a strong reference needs to be created
// to be able to nil out the _interceptor but still let it know that the proxy target has deallocated
// We have to hold a strong reference to the interceptor as we have to nil it out and call the proxyTargetHasDeallocated
// The reason that the interceptor needs to be nilled out is that there maybe a change of a infinite loop, for example
// if a method will be called in the proxyTargetHasDeallocated: that again would trigger a whole new forwarding cycle
id <ASDelegateProxyInterceptor> interceptor = _interceptor;
_interceptor = nil;
[interceptor proxyTargetHasDeallocated:self];
return nil;
}
}

View File

@@ -82,8 +82,8 @@ static void _transactionGroupRunLoopObserverCallback(CFRunLoopObserverRef observ
ASDisplayNodeAssertMainThread();
if ([_containerLayers count]) {
NSHashTable *containerLayersToCommit = [_containerLayers copy];
[_containerLayers removeAllObjects];
NSHashTable *containerLayersToCommit = _containerLayers;
_containerLayers = [NSHashTable hashTableWithOptions:NSPointerFunctionsObjectPointerPersonality];
for (CALayer *containerLayer in containerLayersToCommit) {
// Note that the act of committing a transaction may open a new transaction,

View File

@@ -137,25 +137,30 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
- (void)_calculateSize
{
[self truncater];
// if we have no scale factors or an unconstrained width, there is no reason to try to adjust the font size
if (isinf(_constrainedSize.width) == NO && [_attributes.pointSizeScaleFactors count] > 0) {
_currentScaleFactor = [[self fontSizeAdjuster] scaleFactor];
}
// Force glyph generation and layout, which may not have happened yet (and isn't triggered by
// -usedRectForTextContainer:).
__block NSTextStorage *scaledTextStorage = nil;
BOOL isScaled = [self isScaled];
[[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
if (isScaled) {
if (isScaled) {
// apply the string scale before truncating or else we may truncate the string after we've done the work to shrink it.
[[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
NSMutableAttributedString *scaledString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage];
[ASTextKitFontSizeAdjuster adjustFontSizeForAttributeString:scaledString withScaleFactor:_currentScaleFactor];
scaledTextStorage = [[NSTextStorage alloc] initWithAttributedString:scaledString];
[textStorage removeLayoutManager:layoutManager];
[scaledTextStorage addLayoutManager:layoutManager];
}
}];
}
[[self truncater] truncate];
// Force glyph generation and layout, which may not have happened yet (and isn't triggered by
// -usedRectForTextContainer:).
[[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
[layoutManager ensureLayoutForTextContainer:textContainer];
}];
@@ -251,7 +256,9 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
- (std::vector<NSRange>)visibleRanges
{
return [self truncater].visibleRanges;
ASTextKitTailTruncater *truncater = [self truncater];
[truncater truncate];
return truncater.visibleRanges;
}
@end

View File

@@ -30,8 +30,6 @@
_context = context;
_truncationAttributedString = truncationAttributedString;
_avoidTailTruncationSet = avoidTailTruncationSet;
[self _truncate];
}
return self;
}
@@ -153,7 +151,7 @@
}
}
- (void)_truncate
- (void)truncate
{
[_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
NSUInteger originalStringLength = textStorage.length;

View File

@@ -33,4 +33,9 @@
truncationAttributedString:(NSAttributedString *)truncationAttributedString
avoidTailTruncationSet:(NSCharacterSet *)avoidTailTruncationSet;
/**
* Actually do the truncation.
*/
- (void)truncate;
@end

View File

@@ -53,6 +53,7 @@
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
truncationAttributedString:nil
avoidTailTruncationSet:nil];
[tailTruncater truncate];
XCTAssert(NSEqualRanges(textKitVisibleRange, tailTruncater.visibleRanges[0]));
}
@@ -71,6 +72,7 @@
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
truncationAttributedString:[self _simpleTruncationAttributedString]
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@""]];
[tailTruncater truncate];
__block NSString *drawnString;
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
drawnString = textStorage.string;
@@ -95,7 +97,7 @@
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
truncationAttributedString:[self _simpleTruncationAttributedString]
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]];
(void)tailTruncater;
[tailTruncater truncate];
__block NSString *drawnString;
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
drawnString = textStorage.string;
@@ -120,8 +122,7 @@
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
truncationAttributedString:[self _simpleTruncationAttributedString]
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]];
// So Xcode doesn't yell at me for an unused var...
(void)tailTruncater;
[tailTruncater truncate];
__block NSString *drawnString;
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
drawnString = textStorage.string;
@@ -144,9 +145,9 @@
layoutManagerDelegate:nil
textStorageCreationBlock:nil];
XCTAssertNoThrow([[ASTextKitTailTruncater alloc] initWithContext:context
XCTAssertNoThrow([[[ASTextKitTailTruncater alloc] initWithContext:context
truncationAttributedString:[self _simpleTruncationAttributedString]
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]]);
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]] truncate]);
}
@end

View File

@@ -44,7 +44,7 @@ static BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CGFloat delta)
@interface ASTextNodeTests : XCTestCase
@property (nonatomic, readwrite, strong) ASTextNode *textNode;
@property (nonatomic, readwrite, copy) NSAttributedString *attributedString;
@property (nonatomic, readwrite, copy) NSAttributedString *attributedText;
@end
@@ -80,8 +80,8 @@ static BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CGFloat delta)
[mas addAttribute:NSParagraphStyleAttributeName value:lastLinePara
range:NSMakeRange(mas.length - 1, 1)];
_attributedString = mas;
_textNode.attributedString = _attributedString;
_attributedText = mas;
_textNode.attributedText = _attributedText;
}
#pragma mark - ASTextNode
@@ -97,8 +97,8 @@ static BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CGFloat delta)
- (void)testSettingTruncationMessage
{
NSAttributedString *truncation = [[NSAttributedString alloc] initWithString:@"..." attributes:nil];
_textNode.truncationAttributedString = truncation;
XCTAssertTrue([_textNode.truncationAttributedString isEqualToAttributedString:truncation], @"Failed to set truncation message");
_textNode.truncationAttributedText = truncation;
XCTAssertTrue([_textNode.truncationAttributedText isEqualToAttributedString:truncation], @"Failed to set truncation message");
}
- (void)testSettingAdditionalTruncationMessage
@@ -142,11 +142,11 @@ static BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CGFloat delta)
- (void)testAccessibility
{
_textNode.attributedString = _attributedString;
_textNode.attributedText = _attributedText;
XCTAssertTrue(_textNode.isAccessibilityElement, @"Should be an accessibility element");
XCTAssertTrue(_textNode.accessibilityTraits == UIAccessibilityTraitStaticText, @"Should have static text accessibility trait, instead has %llu", _textNode.accessibilityTraits);
XCTAssertTrue([_textNode.accessibilityLabel isEqualToString:_attributedString.string], @"Accessibility label is incorrectly set to \n%@\n when it should be \n%@\n", _textNode.accessibilityLabel, _attributedString.string);
XCTAssertTrue([_textNode.accessibilityLabel isEqualToString:_attributedText.string], @"Accessibility label is incorrectly set to \n%@\n when it should be \n%@\n", _textNode.accessibilityLabel, _attributedText.string);
}
- (void)testLinkAttribute
@@ -156,7 +156,7 @@ static BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CGFloat delta)
NSString *linkString = @"Link";
NSRange linkRange = NSMakeRange(0, linkString.length);
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:linkString attributes:@{ linkAttributeName : linkAttributeValue}];
_textNode.attributedString = attributedString;
_textNode.attributedText = attributedString;
_textNode.linkAttributeNames = @[linkAttributeName];
ASTextNodeTestDelegate *delegate = [ASTextNodeTestDelegate new];
@@ -178,7 +178,7 @@ static BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CGFloat delta)
NSString *linkString = @"Link notalink";
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:linkString];
[attributedString addAttribute:linkAttributeName value:linkAttributeValue range:NSMakeRange(0, 4)];
_textNode.attributedString = attributedString;
_textNode.attributedText = attributedString;
_textNode.linkAttributeNames = @[linkAttributeName];
ASTextNodeTestDelegate *delegate = [ASTextNodeTestDelegate new];

View File

@@ -10,101 +10,125 @@
*/
#import "ViewController.h"
#import "ASLayoutSpec.h"
#import "ASStaticLayoutSpec.h"
@interface ViewController()<ASVideoNodeDelegate>
@property (nonatomic, strong) ASDisplayNode *rootNode;
@property (nonatomic, strong) ASVideoNode *guitarVideoNode;
@end
@implementation ViewController
#pragma mark - UIViewController
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Root node for the view controller
_rootNode = [ASDisplayNode new];
_rootNode.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubnode:self.guitarVideoNode];
ASVideoNode *guitarVideoNode = self.guitarVideoNode;
[_rootNode addSubnode:self.guitarVideoNode];
ASVideoNode *nicCageVideo = [self nicCageVideo];
[self.view addSubnode:nicCageVideo];
ASVideoNode *nicCageVideoNode = self.nicCageVideoNode;
[_rootNode addSubnode:nicCageVideoNode];
ASVideoNode *simonVideo = [self simonVideo];
[self.view addSubnode:simonVideo];
// Video node with custom play button
ASVideoNode *simonVideoNode = self.simonVideoNode;
simonVideoNode.playButton = self.playButton;
[_rootNode addSubnode:simonVideoNode];
_rootNode.layoutSpecBlock = ^ASLayoutSpec *(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {
guitarVideoNode.layoutPosition = CGPointMake(0, 0);
guitarVideoNode.preferredFrameSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height/3);
nicCageVideoNode.layoutPosition = CGPointMake([UIScreen mainScreen].bounds.size.width/2, [UIScreen mainScreen].bounds.size.height/3);
nicCageVideoNode.preferredFrameSize = CGSizeMake([UIScreen mainScreen].bounds.size.width/2, [UIScreen mainScreen].bounds.size.height/3);
simonVideoNode.layoutPosition = CGPointMake(0, [UIScreen mainScreen].bounds.size.height - ([UIScreen mainScreen].bounds.size.height/3));
simonVideoNode.preferredFrameSize = CGSizeMake([UIScreen mainScreen].bounds.size.width/2, [UIScreen mainScreen].bounds.size.height/3);
return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[guitarVideoNode, nicCageVideoNode, simonVideoNode]];
};
[self.view addSubnode:_rootNode];
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
// After all subviews are layed out we have to measure it and move the root node to the right place
CGSize viewSize = self.view.bounds.size;
[self.rootNode measureWithSizeRange:ASSizeRangeMake(viewSize, viewSize)];
[self.rootNode setNeedsLayout];
}
#pragma mark - Getter / Setter
- (ASVideoNode *)guitarVideoNode;
{
if (_guitarVideoNode) {
return _guitarVideoNode;
}
_guitarVideoNode = [[ASVideoNode alloc] init];
_guitarVideoNode.asset = [AVAsset assetWithURL:[NSURL URLWithString:@"https://files.parsetfss.com/8a8a3b0c-619e-4e4d-b1d5-1b5ba9bf2b42/tfss-3045b261-7e93-4492-b7e5-5d6358376c9f-editedLiveAndDie.mov"]];
_guitarVideoNode.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height/3);
_guitarVideoNode.gravity = AVLayerVideoGravityResizeAspectFill;
_guitarVideoNode.backgroundColor = [UIColor lightGrayColor];
_guitarVideoNode.periodicTimeObserverTimescale = 1; //Default is 100
_guitarVideoNode.delegate = self;
return _guitarVideoNode;
}
- (ASVideoNode *)nicCageVideo;
- (ASVideoNode *)nicCageVideoNode;
{
ASVideoNode *nicCageVideo = [[ASVideoNode alloc] init];
ASVideoNode *nicCageVideoNode = [[ASVideoNode alloc] init];
nicCageVideoNode.delegate = self;
nicCageVideoNode.asset = [AVAsset assetWithURL:[NSURL URLWithString:@"https://files.parsetfss.com/8a8a3b0c-619e-4e4d-b1d5-1b5ba9bf2b42/tfss-753fe655-86bb-46da-89b7-aa59c60e49c0-niccage.mp4"]];
nicCageVideoNode.gravity = AVLayerVideoGravityResize;
nicCageVideoNode.backgroundColor = [UIColor lightGrayColor];
nicCageVideoNode.shouldAutorepeat = YES;
nicCageVideoNode.shouldAutoplay = YES;
nicCageVideoNode.muted = YES;
nicCageVideo.delegate = self;
nicCageVideo.asset = [AVAsset assetWithURL:[NSURL URLWithString:@"https://files.parsetfss.com/8a8a3b0c-619e-4e4d-b1d5-1b5ba9bf2b42/tfss-753fe655-86bb-46da-89b7-aa59c60e49c0-niccage.mp4"]];
nicCageVideo.frame = CGRectMake([UIScreen mainScreen].bounds.size.width/2, [UIScreen mainScreen].bounds.size.height/3, [UIScreen mainScreen].bounds.size.width/2, [UIScreen mainScreen].bounds.size.height/3);
nicCageVideo.gravity = AVLayerVideoGravityResize;
nicCageVideo.backgroundColor = [UIColor lightGrayColor];
nicCageVideo.shouldAutorepeat = YES;
nicCageVideo.shouldAutoplay = YES;
nicCageVideo.muted = YES;
return nicCageVideo;
return nicCageVideoNode;
}
- (ASVideoNode *)simonVideo;
- (ASVideoNode *)simonVideoNode
{
ASVideoNode *simonVideo = [[ASVideoNode alloc] init];
ASVideoNode *simonVideoNode = [[ASVideoNode alloc] init];
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"simon" ofType:@"mp4"]];
simonVideo.asset = [AVAsset assetWithURL:url];
simonVideoNode.asset = [AVAsset assetWithURL:url];
simonVideoNode.gravity = AVLayerVideoGravityResizeAspect;
simonVideoNode.backgroundColor = [UIColor lightGrayColor];
simonVideoNode.shouldAutorepeat = YES;
simonVideoNode.shouldAutoplay = YES;
simonVideoNode.muted = YES;
simonVideo.frame = CGRectMake(0, [UIScreen mainScreen].bounds.size.height - ([UIScreen mainScreen].bounds.size.height/3), [UIScreen mainScreen].bounds.size.width/2, [UIScreen mainScreen].bounds.size.height/3);
simonVideo.gravity = AVLayerVideoGravityResizeAspect;
simonVideo.backgroundColor = [UIColor lightGrayColor];
simonVideo.shouldAutorepeat = YES;
simonVideo.shouldAutoplay = YES;
simonVideo.muted = YES;
return simonVideo;
return simonVideoNode;
}
- (ASButtonNode *)playButton;
{
ASButtonNode *playButton = [[ASButtonNode alloc] init];
ASButtonNode *playButtonNode = [[ASButtonNode alloc] init];
UIImage *image = [UIImage imageNamed:@"playButton@2x.png"];
[playButton setImage:image forState:ASControlStateNormal];
[playButton measure:CGSizeMake(50, 50)];
playButton.bounds = CGRectMake(0, 0, playButton.calculatedSize.width, playButton.calculatedSize.height);
playButton.position = CGPointMake([UIScreen mainScreen].bounds.size.width/4, ([UIScreen mainScreen].bounds.size.height/3)/2);
[playButton setImage:[UIImage imageNamed:@"playButtonSelected@2x.png"] forState:ASControlStateHighlighted];
[playButtonNode setImage:image forState:ASControlStateNormal];
[playButtonNode setImage:[UIImage imageNamed:@"playButtonSelected@2x.png"] forState:ASControlStateHighlighted];
return playButton;
// Change placement of play button if necessary
//playButtonNode.contentHorizontalAlignment = ASHorizontalAlignmentStart;
//playButtonNode.contentVerticalAlignment = ASVerticalAlignmentCenter;
return playButtonNode;
}
#pragma mark - Actions
- (void)videoNodeWasTapped:(ASVideoNode *)videoNode
{
if (videoNode == self.guitarVideoNode) {
@@ -125,6 +149,7 @@
}
#pragma mark - ASVideoNodeDelegate
- (void)videoNode:(ASVideoNode *)videoNode willChangePlayerState:(ASVideoNodePlayerState)state toState:(ASVideoNodePlayerState)toSate
{
//Ignore nicCageVideo