Merge branch 'master' into releases/p6.10

This commit is contained in:
Hannah Trosi
2016-08-15 15:17:42 -07:00
40 changed files with 2140 additions and 381 deletions

View File

@@ -14,7 +14,6 @@
#import "ASDisplayNode+Subclasses.h"
#import "ASBackgroundLayoutSpec.h"
#import "ASInsetLayoutSpec.h"
#import "ASDisplayNode+Beta.h"
#import "ASStaticLayoutSpec.h"
@interface ASButtonNode ()
@@ -56,7 +55,7 @@
- (instancetype)init
{
if (self = [super init]) {
self.usesImplicitHierarchyManagement = YES;
self.automaticallyManagesSubnodes = YES;
_contentSpacing = 8.0;
_laysOutHorizontally = YES;

View File

@@ -17,6 +17,7 @@
#import "ASEnvironmentInternal.h"
#import "ASInternalHelpers.h"
#import "ASCellNode+Internal.h"
#import "AsyncDisplayKit+Debug.h"
#pragma mark - _ASCollectionPendingState
@@ -171,6 +172,12 @@
[self.view clearFetchedData];
}
- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState
{
[super interfaceStateDidChange:newState fromState:oldState];
[ASRangeController layoutDebugOverlayIfNeeded];
}
#if ASRangeControllerLoggingEnabled
- (void)visibleStateDidChange:(BOOL)isVisible
{

View File

@@ -18,7 +18,6 @@
#import "ASCollectionViewFlowLayoutInspector.h"
#import "ASDisplayNodeExtras.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import "ASDisplayNode+Beta.h"
#import "ASInternalHelpers.h"
#import "UICollectionViewLayout+ASConvenience.h"
#import "ASRangeController.h"
@@ -1076,6 +1075,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
return [_dataController nodeAtIndexPath:indexPath];
}
- (NSString *)nameForRangeControllerDataSource
{
return self.asyncDataSource ? NSStringFromClass([self.asyncDataSource class]) : NSStringFromClass([self class]);
}
#pragma mark - ASRangeControllerDelegate
- (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController

View File

@@ -20,9 +20,6 @@ ASDISPLAYNODE_EXTERN_C_END
@interface ASDisplayNode (Beta)
+ (BOOL)usesImplicitHierarchyManagement;
+ (void)setUsesImplicitHierarchyManagement:(BOOL)enabled;
/**
* ASTableView and ASCollectionView now throw exceptions on invalid updates
* like their UIKit counterparts. If YES, these classes will log messages
@@ -63,7 +60,6 @@ ASDISPLAYNODE_EXTERN_C_END
/** @name Layout Transitioning */
@property (nonatomic) BOOL usesImplicitHierarchyManagement;
/**
* @abstract Currently used by ASNetworkImageNode and ASMultiplexImageNode to allow their placeholders to stay if they are loading an image from the network.

View File

@@ -749,6 +749,23 @@ NS_ASSUME_NONNULL_BEGIN
@interface ASDisplayNode (LayoutTransitioning)
/**
* @abstract The amount of time it takes to complete the default transition animation. Default is 0.2.
*/
@property (nonatomic, assign) NSTimeInterval defaultLayoutTransitionDuration;
/**
* @abstract The amount of time (measured in seconds) to wait before beginning the default transition animation.
* Default is 0.0.
*/
@property (nonatomic, assign) NSTimeInterval defaultLayoutTransitionDelay;
/**
* @abstract A mask of options indicating how you want to perform the default transition animations.
* For a list of valid constants, see UIViewAnimationOptions.
*/
@property (nonatomic, assign) UIViewAnimationOptions defaultLayoutTransitionOptions;
/**
* @discussion A place to perform your animation. New nodes have been inserted here. You can also use this time to re-order the hierarchy.
*/
@@ -796,8 +813,24 @@ NS_ASSUME_NONNULL_BEGIN
@end
/*
ASDisplayNode participates in ASAsyncTransactions, so you can determine when your subnodes are done rendering.
See: -(void)asyncdisplaykit_asyncTransactionContainerStateDidChange in ASDisplayNodeSubclass.h
* ASDisplayNode support for automatic subnode management.
*/
@interface ASDisplayNode (AutomaticSubnodeManagement)
/**
* @abstract A boolean that shows whether the node automatically inserts and removes nodes based on the presence or
* absence of the node and its subnodes is completely determined in its layoutSpecThatFits: method.
*
* @discussion If flag is YES the node no longer require addSubnode: or removeFromSupernode method calls. The presence
* or absence of subnodes is completely determined in its layoutSpecThatFits: method.
*/
@property (nonatomic, assign) BOOL automaticallyManagesSubnodes;
@end
/*
* ASDisplayNode participates in ASAsyncTransactions, so you can determine when your subnodes are done rendering.
* See: -(void)asyncdisplaykit_asyncTransactionContainerStateDidChange in ASDisplayNodeSubclass.h
*/
@interface ASDisplayNode (ASDisplayNodeAsyncTransactionContainer) <ASDisplayNodeAsyncTransactionContainer>
@end
@@ -812,7 +845,9 @@ NS_ASSUME_NONNULL_BEGIN
- (void)addSubnode:(nonnull ASDisplayNode *)node;
@end
/** CALayer(AsyncDisplayKit) defines convenience method for adding sub-ASDisplayNode to a CALayer. */
/*
* CALayer(AsyncDisplayKit) defines convenience method for adding sub-ASDisplayNode to a CALayer.
*/
@interface CALayer (AsyncDisplayKit)
/**
* Convenience method, equivalent to [layer addSublayer:node.layer].
@@ -873,6 +908,17 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)cancelLayoutTransitionsInProgress ASDISPLAYNODE_DEPRECATED;
/**
* @abstract A boolean that shows whether the node automatically inserts and removes nodes based on the presence or
* absence of the node and its subnodes is completely determined in its layoutSpecThatFits: method.
*
* @discussion If flag is YES the node no longer require addSubnode: or removeFromSupernode method calls. The presence
* or absence of subnodes is completely determined in its layoutSpecThatFits: method.
*
* @deprecated Deprecated in version 2.0: Use automaticallyManagesSubnodes
*/
@property (nonatomic, assign) BOOL usesImplicitHierarchyManagement ASDISPLAYNODE_DEPRECATED;
- (void)reclaimMemory ASDISPLAYNODE_DEPRECATED;
- (void)recursivelyReclaimMemory ASDISPLAYNODE_DEPRECATED;
@property (nonatomic, assign) BOOL placeholderFadesOut ASDISPLAYNODE_DEPRECATED;

View File

@@ -77,18 +77,6 @@ NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"AS
@synthesize isFinalLayoutable = _isFinalLayoutable;
@synthesize threadSafeBounds = _threadSafeBounds;
static BOOL usesImplicitHierarchyManagement = NO;
+ (BOOL)usesImplicitHierarchyManagement
{
return usesImplicitHierarchyManagement;
}
+ (void)setUsesImplicitHierarchyManagement:(BOOL)enabled
{
usesImplicitHierarchyManagement = enabled;
}
static BOOL suppressesInvalidCollectionUpdateExceptions = YES;
+ (BOOL)suppressesInvalidCollectionUpdateExceptions
@@ -291,6 +279,10 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
_environmentState = ASEnvironmentStateMakeDefault();
_defaultLayoutTransitionDuration = 0.2;
_defaultLayoutTransitionDelay = 0.0;
_defaultLayoutTransitionOptions = UIViewAnimationOptionBeginFromCurrentState;
_flags.canClearContentsOfLayer = YES;
_flags.canCallNeedsDisplayOfLayer = NO;
}
@@ -699,6 +691,20 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
return !self.isNodeLoaded;
}
#pragma mark - Automatic Hierarchy
- (BOOL)automaticallyManagesSubnodes
{
ASDN::MutexLocker l(__instanceLock__);
return _automaticallyManagesSubnodes;
}
- (void)setAutomaticallyManagesSubnodes:(BOOL)automaticallyManagesSubnodes
{
ASDN::MutexLocker l(__instanceLock__);
_automaticallyManagesSubnodes = automaticallyManagesSubnodes;
}
#pragma mark - Layout Transition
- (void)transitionLayoutAnimated:(BOOL)animated measurementCompletion:(void (^)())completion
@@ -747,11 +753,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
ASLayoutableSetCurrentContext(ASLayoutableContextMake(transitionID, NO));
ASDN::MutexLocker l(__instanceLock__);
BOOL disableImplicitHierarchyManagement = self.usesImplicitHierarchyManagement == NO;
self.usesImplicitHierarchyManagement = YES; // Temporary flag for 1.9.x
BOOL automaticallyManagesSubnodesDisabled = (self.automaticallyManagesSubnodes == NO);
self.automaticallyManagesSubnodes = YES; // Temporary flag for 1.9.x
newLayout = [self calculateLayoutThatFits:constrainedSize];
if (disableImplicitHierarchyManagement) {
self.usesImplicitHierarchyManagement = NO; // Temporary flag for 1.9.x
if (automaticallyManagesSubnodesDisabled) {
self.automaticallyManagesSubnodes = NO; // Temporary flag for 1.9.x
}
ASLayoutableClearCurrentContext();
@@ -816,18 +822,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
}
}
- (BOOL)usesImplicitHierarchyManagement
{
ASDN::MutexLocker l(__instanceLock__);
return _usesImplicitHierarchyManagement ? : [[self class] usesImplicitHierarchyManagement];
}
- (void)setUsesImplicitHierarchyManagement:(BOOL)value
{
ASDN::MutexLocker l(__instanceLock__);
_usesImplicitHierarchyManagement = value;
}
- (BOOL)_isTransitionInProgress
{
ASDN::MutexLocker l(__instanceLock__);
@@ -857,14 +851,118 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
#pragma mark Layout Transition API
- (void)setDefaultLayoutTransitionDuration:(NSTimeInterval)defaultLayoutTransitionDuration
{
ASDN::MutexLocker l(__instanceLock__);
_defaultLayoutTransitionDuration = defaultLayoutTransitionDuration;
}
- (NSTimeInterval)defaultLayoutTransitionDuration
{
ASDN::MutexLocker l(__instanceLock__);
return _defaultLayoutTransitionDuration;
}
- (void)setDefaultLayoutTransitionDelay:(NSTimeInterval)defaultLayoutTransitionDelay
{
ASDN::MutexLocker l(__instanceLock__);
_defaultLayoutTransitionDelay = defaultLayoutTransitionDelay;
}
- (NSTimeInterval)defaultLayoutTransitionDelay
{
ASDN::MutexLocker l(__instanceLock__);
return _defaultLayoutTransitionDelay;
}
- (void)setDefaultLayoutTransitionOptions:(UIViewAnimationOptions)defaultLayoutTransitionOptions
{
ASDN::MutexLocker l(__instanceLock__);
_defaultLayoutTransitionOptions = defaultLayoutTransitionOptions;
}
- (UIViewAnimationOptions)defaultLayoutTransitionOptions
{
ASDN::MutexLocker l(__instanceLock__);
return _defaultLayoutTransitionOptions;
}
/*
* Hook for subclasse to perform an animation based on the given ASContextTransitioning. By default this just layouts
* applies all subnodes without animation and calls completes the transition on the context.
* Hook for subclasse to perform an animation based on the given ASContextTransitioning. By default a fade in and out
* animation is provided.
*/
- (void)animateLayoutTransition:(id<ASContextTransitioning>)context
{
[self __layoutSublayouts];
[context completeTransition:YES];
ASDisplayNode *node = self;
NSAssert(node.isNodeLoaded == YES, @"Invalid node state");
NSAssert([context isAnimated] == YES, @"Can't animate a non-animatable context");
NSArray<ASDisplayNode *> *removedSubnodes = [context removedSubnodes];
NSMutableArray<UIView *> *removedViews = [NSMutableArray array];
NSMutableArray<ASDisplayNode *> *insertedSubnodes = [[context insertedSubnodes] mutableCopy];
NSMutableArray<ASDisplayNode *> *movedSubnodes = [NSMutableArray array];
for (ASDisplayNode *subnode in [context subnodesForKey:ASTransitionContextToLayoutKey]) {
if ([insertedSubnodes containsObject:subnode] == NO) {
// This is an existing subnode, check if it is resized, moved or both
CGRect fromFrame = [context initialFrameForNode:subnode];
CGRect toFrame = [context finalFrameForNode:subnode];
if (CGSizeEqualToSize(fromFrame.size, toFrame.size) == NO) {
// To crossfade resized subnodes, show a snapshot of it on top.
// The node itself can then be treated as a newly-inserted one.
UIView *snapshotView = [subnode.view snapshotViewAfterScreenUpdates:YES];
snapshotView.frame = [context initialFrameForNode:subnode];
snapshotView.alpha = 1;
[node.view insertSubview:snapshotView aboveSubview:subnode.view];
[removedViews addObject:snapshotView];
[insertedSubnodes addObject:subnode];
}
if (CGPointEqualToPoint(fromFrame.origin, toFrame.origin) == NO) {
[movedSubnodes addObject:subnode];
}
}
}
for (ASDisplayNode *insertedSubnode in insertedSubnodes) {
insertedSubnode.frame = [context finalFrameForNode:insertedSubnode];
insertedSubnode.alpha = 0;
}
[UIView animateWithDuration:self.defaultLayoutTransitionDuration delay:self.defaultLayoutTransitionDelay options:self.defaultLayoutTransitionOptions animations:^{
// Fade removed subnodes and views out
for (ASDisplayNode *removedSubnode in removedSubnodes) {
removedSubnode.alpha = 0;
}
for (UIView *removedView in removedViews) {
removedView.alpha = 0;
}
// Fade inserted subnodes in
for (ASDisplayNode *insertedSubnode in insertedSubnodes) {
insertedSubnode.alpha = 1;
}
// Update frame of self and moved subnodes
CGSize fromSize = [context layoutForKey:ASTransitionContextFromLayoutKey].size;
CGSize toSize = [context layoutForKey:ASTransitionContextToLayoutKey].size;
BOOL isResized = (CGSizeEqualToSize(fromSize, toSize) == NO);
if (isResized == YES) {
CGPoint position = node.frame.origin;
node.frame = CGRectMake(position.x, position.y, toSize.width, toSize.height);
}
for (ASDisplayNode *movedSubnode in movedSubnodes) {
movedSubnode.frame = [context finalFrameForNode:movedSubnode];
}
} completion:^(BOOL finished) {
for (UIView *removedView in removedViews) {
[removedView removeFromSuperview];
}
// Subnode removals are automatically performed
[context completeTransition:finished];
}];
}
/*
@@ -911,8 +1009,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
*/
- (void)_completeLayoutTransition:(ASLayoutTransition *)layoutTransition
{
// Layout transition is not supported for non implicit hierarchy managed nodes yet
if (layoutTransition == nil || self.usesImplicitHierarchyManagement == NO) {
// Layout transition is not supported for nodes that are not have automatic subnode management enabled
if (layoutTransition == nil || self.automaticallyManagesSubnodes == NO) {
return;
}
@@ -1078,7 +1176,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
ASDN::MutexLocker l(__instanceLock__);
// FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout
// but implicit hierarchy management would require us to modify the node tree
// but automatic subnode management would require us to modify the node tree
// in the background on a loaded node, which isn't currently supported.
if (_pendingViewState.hasSetNeedsLayout) {
[self __setNeedsLayout];
@@ -1874,7 +1972,7 @@ static NSInteger incrementIfFound(NSInteger i) {
// If a node was added to a supernode, the supernode could be in a layout pending state. All of the hierarchy state
// properties related to the transition need to be copied over as well as propagated down the subtree.
// This is especially important as with Implicit Hierarchy Management adding subnodes can happen while a transition
// This is especially important as with automatic subnode management, adding subnodes can happen while a transition
// is in fly
if (ASHierarchyStateIncludesLayoutPending(stateToEnterOrExit)) {
int32_t pendingTransitionId = newSupernode.pendingTransitionID;
@@ -2593,7 +2691,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
ASDisplayNodeAssertTrue(layout.size.width >= 0.0);
ASDisplayNodeAssertTrue(layout.size.height >= 0.0);
if (layoutTransition == nil || self.usesImplicitHierarchyManagement == NO) {
if (layoutTransition == nil || self.automaticallyManagesSubnodes == NO) {
return;
}
@@ -3186,6 +3284,16 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode";
[self cancelLayoutTransition];
}
- (BOOL)usesImplicitHierarchyManagement
{
return self.automaticallyManagesSubnodes;
}
- (void)setUsesImplicitHierarchyManagement:(BOOL)enabled
{
self.automaticallyManagesSubnodes = enabled;
}
- (void)setPlaceholderFadesOut:(BOOL)placeholderFadesOut
{
self.placeholderFadeDuration = placeholderFadesOut ? 0.1 : 0.0;

View File

@@ -16,6 +16,7 @@
#import "ASDisplayNode+Subclasses.h"
#import "ASInternalHelpers.h"
#import "ASCellNode+Internal.h"
#import "AsyncDisplayKit+Debug.h"
#pragma mark - _ASTablePendingState
@@ -125,6 +126,12 @@
[self.view clearFetchedData];
}
- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState
{
[super interfaceStateDidChange:newState fromState:oldState];
[ASRangeController layoutDebugOverlayIfNeeded];
}
#if ASRangeControllerLoggingEnabled
- (void)visibleStateDidChange:(BOOL)isVisible
{

View File

@@ -17,7 +17,6 @@
#import "ASChangeSetDataController.h"
#import "ASDelegateProxy.h"
#import "ASDisplayNodeExtras.h"
#import "ASDisplayNode+Beta.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import "ASInternalHelpers.h"
#import "ASLayout.h"
@@ -937,6 +936,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
return ASInterfaceStateForDisplayNode(self.tableNode, self.window);
}
- (NSString *)nameForRangeControllerDataSource
{
return self.asyncDataSource ? NSStringFromClass([self.asyncDataSource class]) : NSStringFromClass([self class]);
}
#pragma mark - ASRangeControllerDelegate
- (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController

View File

@@ -31,6 +31,14 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nullable, nonatomic, copy) NSTextStorage * (^textStorageCreationBlock)(NSAttributedString *_Nullable attributedString);
/**
@abstract Text margins for text laid out in the text node.
@discussion defaults to UIEdgeInsetsZero.
This property can be useful for handling text which does not fit within the view by default. An example: like UILabel,
ASTextNode will clip the left and right of the string "judar" if it's rendered in an italicised font.
*/
@property (nonatomic, assign) UIEdgeInsets textContainerInset;
@end
NS_ASSUME_NONNULL_END

View File

@@ -48,6 +48,8 @@ struct ASTextNodeDrawParameter {
UIColor *_cachedShadowUIColor;
CGFloat _shadowOpacity;
CGFloat _shadowRadius;
UIEdgeInsets _textContainerInset;
NSArray *_exclusionPaths;
@@ -213,7 +215,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
ASDN::MutexLocker l(__instanceLock__);
if (_renderer == nil) {
CGSize constrainedSize = _constrainedSize.width != -INFINITY ? _constrainedSize : bounds.size;
CGSize constrainedSize;
if (_constrainedSize.width != -INFINITY) {
constrainedSize = _constrainedSize;
} else {
constrainedSize = bounds.size;
constrainedSize.width -= (_textContainerInset.left + _textContainerInset.right);
constrainedSize.height -= (_textContainerInset.top + _textContainerInset.bottom);
}
_renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes]
constrainedSize:constrainedSize];
}
@@ -279,6 +289,24 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
#pragma mark - Layout and Sizing
- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset
{
ASDN::MutexLocker l(__instanceLock__);
BOOL needsUpdate = !UIEdgeInsetsEqualToEdgeInsets(textContainerInset, _textContainerInset);
if (needsUpdate) {
_textContainerInset = textContainerInset;
[self invalidateCalculatedLayout];
[self setNeedsLayout];
}
}
- (UIEdgeInsets)textContainerInset
{
ASDN::MutexLocker l(__instanceLock__);
return _textContainerInset;
}
- (BOOL)_needInvalidateRendererForBoundsSize:(CGSize)boundsSize
{
ASDN::MutexLocker l(__instanceLock__);
@@ -291,6 +319,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
// a new one. However, there are common cases where the constrained size doesn't need to be the same as calculated.
CGSize rendererConstrainedSize = _renderer.constrainedSize;
//inset bounds
boundsSize.width -= _textContainerInset.left + _textContainerInset.right;
boundsSize.height -= _textContainerInset.top + _textContainerInset.bottom;
if (CGSizeEqualToSize(boundsSize, rendererConstrainedSize)) {
return NO;
} else {
@@ -321,9 +353,14 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
if (layout != nil) {
ASDN::MutexLocker l(__instanceLock__);
if (CGSizeEqualToSize(_constrainedSize, layout.size) == NO) {
_constrainedSize = layout.size;
_renderer.constrainedSize = layout.size;
CGSize layoutSize = layout.size;
//Apply textContainerInset
layoutSize.width -= (_textContainerInset.left + _textContainerInset.right);
layoutSize.height -= (_textContainerInset.top + _textContainerInset.bottom);
if (CGSizeEqualToSize(_constrainedSize, layoutSize) == NO) {
_constrainedSize = layoutSize;
_renderer.constrainedSize = layoutSize;
}
}
}
@@ -335,6 +372,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
ASDN::MutexLocker l(__instanceLock__);
//remove textContainerInset
constrainedSize.width -= (_textContainerInset.left + _textContainerInset.right);
constrainedSize.height -= (_textContainerInset.top + _textContainerInset.bottom);
_constrainedSize = constrainedSize;
// Instead of invalidating the renderer, in case this is a new call with a different constrained size,
@@ -353,6 +394,11 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
self.descender *= _renderer.currentScaleFactor;
}
}
//add textContainerInset
size.width += (_textContainerInset.left + _textContainerInset.right);
size.height += (_textContainerInset.top + _textContainerInset.bottom);
return size;
}
@@ -466,6 +512,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
CGContextSaveGState(context);
CGContextTranslateCTM(context, _textContainerInset.left, _textContainerInset.top);
ASTextKitRenderer *renderer = [self _rendererWithBounds:drawParameterBounds];
UIEdgeInsets shadowPadding = [self shadowPaddingWithRenderer:renderer];
CGPoint boundsOrigin = drawParameterBounds.origin;

View File

@@ -12,7 +12,7 @@
#import <AsyncDisplayKit/ASButtonNode.h>
#import <AsyncDisplayKit/ASNetworkImageNode.h>
@class AVAsset, AVPlayer, AVPlayerItem, AVVideoComposition, AVAudioMix;
@class AVAsset, AVPlayer, AVPlayerLayer, AVPlayerItem, AVVideoComposition, AVAudioMix;
@protocol ASVideoNodeDelegate;
typedef enum {
@@ -51,6 +51,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nullable, nonatomic, strong, readwrite) AVAudioMix *audioMix;
@property (nullable, nonatomic, strong, readonly) AVPlayer *player;
@property (nullable, nonatomic, strong, readonly) AVPlayerLayer *playerLayer;
@property (nullable, nonatomic, strong, readonly) AVPlayerItem *currentItem;

View File

@@ -37,6 +37,7 @@ static void *ASVideoNodeContext = &ASVideoNodeContext;
static NSString * const kPlaybackLikelyToKeepUpKey = @"playbackLikelyToKeepUp";
static NSString * const kplaybackBufferEmpty = @"playbackBufferEmpty";
static NSString * const kStatus = @"status";
static NSString * const kRate = @"rate";
@interface ASVideoNode ()
{
@@ -208,6 +209,25 @@ static NSString * const kStatus = @"status";
[notificationCenter removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:playerItem];
}
- (void)addPlayerObservers:(AVPlayer *)player
{
if (player == nil) {
return;
}
[player addObserver:self forKeyPath:kRate options:NSKeyValueObservingOptionNew context:ASVideoNodeContext];
}
- (void) removePlayerObservers:(AVPlayer *)player
{
@try {
[player removeObserver:self forKeyPath:kRate context:ASVideoNodeContext];
}
@catch (NSException * __unused exception) {
NSLog(@"Unnecessary KVO removal");
}
}
- (void)layout
{
[super layout];
@@ -267,6 +287,7 @@ static NSString * const kStatus = @"status";
AVAssetImageGenerator *previewImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];
previewImageGenerator.appliesPreferredTrackTransform = YES;
previewImageGenerator.videoComposition = _videoComposition;
[previewImageGenerator generateCGImagesAsynchronouslyForTimes:@[[NSValue valueWithCMTime:imageTime]]
completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) {
@@ -291,29 +312,47 @@ static NSString * const kStatus = @"status";
{
ASDN::MutexLocker l(__instanceLock__);
if (object != _currentPlayerItem) {
return;
}
if ([keyPath isEqualToString:kStatus]) {
if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) {
self.playerState = ASVideoNodePlayerStateReadyToPlay;
// If we don't yet have a placeholder image update it now that we should have data available for it
if (self.image == nil && self.URL == nil) {
[self generatePlaceholderImage];
if (object == _currentPlayerItem) {
if ([keyPath isEqualToString:kStatus]) {
if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) {
if (self.playerState != ASVideoNodePlayerStatePlaying) {
self.playerState = ASVideoNodePlayerStateReadyToPlay;
}
// If we don't yet have a placeholder image update it now that we should have data available for it
if (self.image == nil && self.URL == nil) {
[self generatePlaceholderImage];
}
}
} else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUpKey]) {
BOOL likelyToKeepUp = [change[NSKeyValueChangeNewKey] boolValue];
if (likelyToKeepUp && self.playerState == ASVideoNodePlayerStatePlaying) {
return;
}
if (!likelyToKeepUp) {
self.playerState = ASVideoNodePlayerStateLoading;
} else if (self.playerState != ASVideoNodePlayerStateFinished) {
self.playerState = ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying;
}
if (_shouldBePlaying && (_shouldAggressivelyRecoverFromStall || likelyToKeepUp) && ASInterfaceStateIncludesVisible(self.interfaceState)) {
if (self.playerState == ASVideoNodePlayerStateLoading && _delegateFlags.delegateVideoNodeDidRecoverFromStall) {
[_delegate videoNodeDidRecoverFromStall:self];
}
[self play]; // autoresume after buffer catches up
}
} else if ([keyPath isEqualToString:kplaybackBufferEmpty]) {
if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == true && ASInterfaceStateIncludesVisible(self.interfaceState)) {
self.playerState = ASVideoNodePlayerStateLoading;
}
}
} else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUpKey]) {
self.playerState = ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying;
if (_shouldBePlaying && (_shouldAggressivelyRecoverFromStall || [change[NSKeyValueChangeNewKey] boolValue]) && ASInterfaceStateIncludesVisible(self.interfaceState)) {
if (self.playerState == ASVideoNodePlayerStateLoading && _delegateFlags.delegateVideoNodeDidRecoverFromStall) {
[_delegate videoNodeDidRecoverFromStall:self];
} else if (object == _player) {
if ([keyPath isEqualToString:kRate]) {
if ([change[NSKeyValueChangeNewKey] floatValue] == 0.0) {
if (self.playerState == ASVideoNodePlayerStatePlaying) {
self.playerState = ASVideoNodePlayerStatePaused;
}
} else {
self.playerState = ASVideoNodePlayerStatePlaying;
}
[self play]; // autoresume after buffer catches up
}
} else if ([keyPath isEqualToString:kplaybackBufferEmpty]) {
if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == true && ASInterfaceStateIncludesVisible(self.interfaceState)) {
self.playerState = ASVideoNodePlayerStateLoading;
}
}
}
@@ -345,14 +384,11 @@ static NSString * const kStatus = @"status";
ASDN::MutexLocker l(__instanceLock__);
AVAsset *asset = self.asset;
// Return immediately if the asset is nil;
if (asset == nil || self.playerState == ASVideoNodePlayerStateInitialLoading) {
if (asset == nil || self.playerState != ASVideoNodePlayerStateUnknown) {
return;
}
// FIXME: Nothing appears to prevent this method from sending the delegate notification / calling load on the asset
// multiple times, even after the asset is fully loaded and ready to play. There should probably be a playerState
// for NotLoaded or such, besides Unknown, so this can be easily checked before proceeding.
self.playerState = ASVideoNodePlayerStateInitialLoading;
self.playerState = ASVideoNodePlayerStateLoading;
if (_delegateFlags.delegateVideoNodeDidStartInitialLoading) {
[_delegate videoNodeDidStartInitialLoading:self];
}
@@ -396,6 +432,7 @@ static NSString * const kStatus = @"status";
self.player = nil;
self.currentItem = nil;
self.playerState = ASVideoNodePlayerStateUnknown;
}
}
@@ -514,6 +551,12 @@ static NSString * const kStatus = @"status";
return _player;
}
- (AVPlayerLayer *)playerLayer
{
ASDN::MutexLocker l(__instanceLock__);
return (AVPlayerLayer *)_playerNode.layer;
}
- (id<ASVideoNodeDelegate>)delegate{
return _delegate;
}
@@ -603,12 +646,6 @@ static NSString * const kStatus = @"status";
[_player play];
_shouldBePlaying = YES;
if (![self ready]) {
self.playerState = ASVideoNodePlayerStateLoading;
} else {
self.playerState = ASVideoNodePlayerStatePlaying;
}
}
- (BOOL)ready
@@ -622,7 +659,6 @@ static NSString * const kStatus = @"status";
if (![self isStateChangeValid:ASVideoNodePlayerStatePaused]) {
return;
}
self.playerState = ASVideoNodePlayerStatePaused;
[_player pause];
_shouldBePlaying = NO;
}
@@ -731,9 +767,16 @@ static NSString * const kStatus = @"status";
- (void)setPlayer:(AVPlayer *)player
{
ASDN::MutexLocker l(__instanceLock__);
[self removePlayerObservers:_player];
_player = player;
player.muted = _muted;
((AVPlayerLayer *)_playerNode.layer).player = player;
if (player != nil) {
[self addPlayerObservers:player];
}
}
- (BOOL)shouldBePlaying
@@ -755,6 +798,7 @@ static NSString * const kStatus = @"status";
[_player removeTimeObserver:_timeObserver];
_timeObserver = nil;
[self removePlayerItemObservers:_currentPlayerItem];
[self removePlayerObservers:_player];
}
@end

View File

@@ -78,7 +78,7 @@ typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitWindowSizeBlock)(C
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil AS_UNAVAILABLE("ASViewController requires using -initWithNode:");
- (instancetype)initWithCoder:(NSCoder *)aDecoder AS_UNAVAILABLE("ASViewController requires using -initWithNode:");
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder AS_UNAVAILABLE("ASViewController requires using -initWithNode:");
@end

View File

@@ -12,6 +12,7 @@
#import "ASControlNode.h"
#import "ASImageNode.h"
#import "ASRangeController.h"
@interface ASImageNode (Debugging)
@@ -29,16 +30,40 @@
@interface ASControlNode (Debugging)
/**
Class method to enable a visualization overlay of the tappable area on the ASControlNode. For app debugging purposes only.
NOTE: GESTURE RECOGNIZERS, (including tap gesture recognizers on a control node) WILL NOT BE VISUALIZED!!!
Overlay = translucent GREEN color,
edges that are clipped by the tappable area of any parent (their bounds + hitTestSlop) in the hierarchy = DARK GREEN BORDERED EDGE,
edges that are clipped by clipToBounds = YES of any parent in the hierarchy = ORANGE BORDERED EDGE (may still receive touches beyond
overlay rect, but can't be visualized).
@param enable Specify YES to make this debug feature enabled when messaging the ASControlNode class.
* Class method to enable a visualization overlay of the tappable area on the ASControlNode. For app debugging purposes only.
* NOTE: GESTURE RECOGNIZERS, (including tap gesture recognizers on a control node) WILL NOT BE VISUALIZED!!!
* Overlay = translucent GREEN color,
* edges that are clipped by the tappable area of any parent (their bounds + hitTestSlop) in the hierarchy = DARK GREEN BORDERED EDGE,
* edges that are clipped by clipToBounds = YES of any parent in the hierarchy = ORANGE BORDERED EDGE (may still receive touches beyond
* overlay rect, but can't be visualized).
* @param enable Specify YES to make this debug feature enabled when messaging the ASControlNode class.
*/
+ (void)setEnableHitTestDebug:(BOOL)enable;
+ (BOOL)enableHitTestDebug;
@end
@interface ASRangeController (Debugging)
/**
* Class method to enable a visualization overlay of the all ASRangeController's tuning parameters. For dev purposes only.
* To use, message ASRangeController in the AppDelegate --> [ASRangeController setShouldShowRangeDebugOverlay:YES];
* @param enable Specify YES to make this debug feature enabled when messaging the ASRangeController class.
*/
+ (void)setShouldShowRangeDebugOverlay:(BOOL)show;
+ (BOOL)shouldShowRangeDebugOverlay;
+ (void)layoutDebugOverlayIfNeeded;
- (void)addRangeControllerToRangeDebugOverlay;
- (void)updateRangeController:(ASRangeController *)controller
withScrollableDirections:(ASScrollDirection)scrollableDirections
scrollDirection:(ASScrollDirection)direction
rangeMode:(ASLayoutRangeMode)mode
displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters
fetchDataTuningParameters:(ASRangeTuningParameters)fetchDataTuningParameters
interfaceState:(ASInterfaceState)interfaceState;
@end

View File

@@ -13,6 +13,16 @@
#import "AsyncDisplayKit+Debug.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNodeExtras.h"
#import "ASWeakSet.h"
#import "UIImage+ASConvenience.h"
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/CGRect+ASConvenience.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASTextNode.h>
#import <AsyncDisplayKit/ASRangeController.h>
#pragma mark - ASImageNode (Debugging)
static BOOL __shouldShowImageScalingOverlay = NO;
@@ -30,6 +40,8 @@ static BOOL __shouldShowImageScalingOverlay = NO;
@end
#pragma mark - ASControlNode (DebuggingInternal)
static BOOL __enableHitTestDebug = NO;
@interface ASControlNode (DebuggingInternal)
@@ -191,3 +203,551 @@ static BOOL __enableHitTestDebug = NO;
}
@end
#pragma mark - ASRangeController (Debugging)
@interface _ASRangeDebugOverlayView : UIView
+ (instancetype)sharedInstance;
- (void)addRangeController:(ASRangeController *)rangeController;
- (void)updateRangeController:(ASRangeController *)controller
withScrollableDirections:(ASScrollDirection)scrollableDirections
scrollDirection:(ASScrollDirection)direction
rangeMode:(ASLayoutRangeMode)mode
displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters
fetchDataTuningParameters:(ASRangeTuningParameters)fetchDataTuningParameters
interfaceState:(ASInterfaceState)interfaceState;
@end
@interface _ASRangeDebugBarView : UIView
@property (nonatomic, weak) ASRangeController *rangeController;
@property (nonatomic, assign) BOOL destroyOnLayout;
@property (nonatomic, strong) NSString *debugString;
- (instancetype)initWithRangeController:(ASRangeController *)rangeController;
- (void)updateWithVisibleRatio:(CGFloat)visibleRatio
displayRatio:(CGFloat)displayRatio
leadingDisplayRatio:(CGFloat)leadingDisplayRatio
fetchDataRatio:(CGFloat)fetchDataRatio
leadingFetchDataRatio:(CGFloat)leadingFetchDataRatio
direction:(ASScrollDirection)direction;
@end
static BOOL __shouldShowRangeDebugOverlay = NO;
@implementation ASRangeController (Debugging)
+ (void)setShouldShowRangeDebugOverlay:(BOOL)show
{
__shouldShowRangeDebugOverlay = show;
}
+ (BOOL)shouldShowRangeDebugOverlay
{
return __shouldShowRangeDebugOverlay;
}
+ (void)layoutDebugOverlayIfNeeded
{
[[_ASRangeDebugOverlayView sharedInstance] setNeedsLayout];
}
- (void)addRangeControllerToRangeDebugOverlay
{
[[_ASRangeDebugOverlayView sharedInstance] addRangeController:self];
}
- (void)updateRangeController:(ASRangeController *)controller
withScrollableDirections:(ASScrollDirection)scrollableDirections
scrollDirection:(ASScrollDirection)direction
rangeMode:(ASLayoutRangeMode)mode
displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters
fetchDataTuningParameters:(ASRangeTuningParameters)fetchDataTuningParameters
interfaceState:(ASInterfaceState)interfaceState
{
[[_ASRangeDebugOverlayView sharedInstance] updateRangeController:controller
withScrollableDirections:scrollableDirections
scrollDirection:direction
rangeMode:mode
displayTuningParameters:displayTuningParameters
fetchDataTuningParameters:fetchDataTuningParameters
interfaceState:interfaceState];
}
@end
#pragma mark _ASRangeDebugOverlayView
@interface _ASRangeDebugOverlayView () <UIGestureRecognizerDelegate>
@end
@implementation _ASRangeDebugOverlayView
{
NSMutableArray *_rangeControllerViews;
NSInteger _newControllerCount;
NSInteger _removeControllerCount;
BOOL _animating;
}
+ (UIWindow *)keyWindow
{
// hack to work around app extensions not having UIApplication...not sure of a better way to do this?
return [[NSClassFromString(@"UIApplication") sharedApplication] keyWindow];
}
+ (instancetype)sharedInstance
{
static _ASRangeDebugOverlayView *__rangeDebugOverlay = nil;
if (!__rangeDebugOverlay && [ASRangeController shouldShowRangeDebugOverlay]) {
__rangeDebugOverlay = [[self alloc] initWithFrame:CGRectZero];
[[self keyWindow] addSubview:__rangeDebugOverlay];
}
return __rangeDebugOverlay;
}
#define OVERLAY_INSET 10
#define OVERLAY_SCALE 3
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_rangeControllerViews = [[NSMutableArray alloc] init];
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4];
self.layer.zPosition = 1000;
self.clipsToBounds = YES;
CGSize windowSize = [[[self class] keyWindow] bounds].size;
self.frame = CGRectMake(windowSize.width - (windowSize.width / OVERLAY_SCALE) - OVERLAY_INSET, windowSize.height - OVERLAY_INSET,
windowSize.width / OVERLAY_SCALE, 0.0);
UIPanGestureRecognizer *panGR = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(rangeDebugOverlayWasPanned:)];
[self addGestureRecognizer:panGR];
}
return self;
}
#define BAR_THICKNESS 24
- (void)layoutSubviews
{
[super layoutSubviews];
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
[self layoutToFitAllBarsExcept:0];
} completion:^(BOOL finished) {
}];
}
- (void)layoutToFitAllBarsExcept:(NSInteger)barsToClip
{
CGSize boundsSize = self.bounds.size;
CGFloat totalHeight = 0.0;
CGRect barRect = CGRectMake(0, boundsSize.height - BAR_THICKNESS, self.bounds.size.width, BAR_THICKNESS);
NSMutableArray *displayedBars = [NSMutableArray array];
for (_ASRangeDebugBarView *barView in [_rangeControllerViews copy]) {
barView.frame = barRect;
ASInterfaceState interfaceState = [barView.rangeController.dataSource interfaceStateForRangeController:barView.rangeController];
if (!(interfaceState & (ASInterfaceStateVisible))) {
if (barView.destroyOnLayout && barView.alpha == 0.0) {
[_rangeControllerViews removeObjectIdenticalTo:barView];
[barView removeFromSuperview];
} else {
barView.alpha = 0.0;
}
} else {
assert(!barView.destroyOnLayout); // In this case we should not have a visible interfaceState
barView.alpha = 1.0;
totalHeight += BAR_THICKNESS;
barRect.origin.y -= BAR_THICKNESS;
[displayedBars addObject:barView];
}
}
if (totalHeight > 0) {
totalHeight -= (BAR_THICKNESS * barsToClip);
}
if (barsToClip == 0) {
CGRect overlayFrame = self.frame;
CGFloat heightChange = (overlayFrame.size.height - totalHeight);
overlayFrame.origin.y += heightChange;
overlayFrame.size.height = totalHeight;
self.frame = overlayFrame;
for (_ASRangeDebugBarView *barView in displayedBars) {
[self offsetYOrigin:-heightChange forView:barView];
}
}
}
- (void)setOrigin:(CGPoint)origin forView:(UIView *)view
{
CGRect newFrame = view.frame;
newFrame.origin = origin;
view.frame = newFrame;
}
- (void)offsetYOrigin:(CGFloat)offset forView:(UIView *)view
{
CGRect newFrame = view.frame;
newFrame.origin = CGPointMake(newFrame.origin.x, newFrame.origin.y + offset);
view.frame = newFrame;
}
- (void)addRangeController:(ASRangeController *)rangeController
{
for (_ASRangeDebugBarView *rangeView in _rangeControllerViews) {
if (rangeView.rangeController == rangeController) {
return;
}
}
_ASRangeDebugBarView *rangeView = [[_ASRangeDebugBarView alloc] initWithRangeController:rangeController];
[_rangeControllerViews addObject:rangeView];
[self addSubview:rangeView];
if (!_animating) {
[self layoutToFitAllBarsExcept:1];
}
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
_animating = YES;
[self layoutToFitAllBarsExcept:0];
} completion:^(BOOL finished) {
_animating = NO;
}];
}
- (void)updateRangeController:(ASRangeController *)controller
withScrollableDirections:(ASScrollDirection)scrollableDirections
scrollDirection:(ASScrollDirection)scrollDirection
rangeMode:(ASLayoutRangeMode)rangeMode
displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters
fetchDataTuningParameters:(ASRangeTuningParameters)fetchDataTuningParameters
interfaceState:(ASInterfaceState)interfaceState;
{
_ASRangeDebugBarView *viewToUpdate = [self barViewForRangeController:controller];
CGRect boundsRect = self.bounds;
CGRect visibleRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, ASRangeTuningParametersZero, scrollableDirections, scrollDirection);
CGRect displayRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, displayTuningParameters, scrollableDirections, scrollDirection);
CGRect fetchDataRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, fetchDataTuningParameters, scrollableDirections, scrollDirection);
// figure out which is biggest and assume that is full bounds
BOOL displayRangeLargerThanFetch = NO;
CGFloat visibleRatio = 0;
CGFloat displayRatio = 0;
CGFloat fetchDataRatio = 0;
CGFloat leadingDisplayTuningRatio = 0;
CGFloat leadingFetchDataTuningRatio = 0;
if (!((displayTuningParameters.leadingBufferScreenfuls + displayTuningParameters.trailingBufferScreenfuls) == 0)) {
leadingDisplayTuningRatio = displayTuningParameters.leadingBufferScreenfuls / (displayTuningParameters.leadingBufferScreenfuls + displayTuningParameters.trailingBufferScreenfuls);
}
if (!((fetchDataTuningParameters.leadingBufferScreenfuls + fetchDataTuningParameters.trailingBufferScreenfuls) == 0)) {
leadingFetchDataTuningRatio = fetchDataTuningParameters.leadingBufferScreenfuls / (fetchDataTuningParameters.leadingBufferScreenfuls + fetchDataTuningParameters.trailingBufferScreenfuls);
}
if (ASScrollDirectionContainsVerticalDirection(scrollDirection)) {
if (displayRect.size.height >= fetchDataRect.size.height) {
displayRangeLargerThanFetch = YES;
} else {
displayRangeLargerThanFetch = NO;
}
if (displayRangeLargerThanFetch) {
visibleRatio = visibleRect.size.height / displayRect.size.height;
displayRatio = 1.0;
fetchDataRatio = fetchDataRect.size.height / displayRect.size.height;
} else {
visibleRatio = visibleRect.size.height / fetchDataRect.size.height;
displayRatio = displayRect.size.height / fetchDataRect.size.height;
fetchDataRatio = 1.0;
}
} else {
if (displayRect.size.width >= fetchDataRect.size.width) {
displayRangeLargerThanFetch = YES;
} else {
displayRangeLargerThanFetch = NO;
}
if (displayRangeLargerThanFetch) {
visibleRatio = visibleRect.size.width / displayRect.size.width;
displayRatio = 1.0;
fetchDataRatio = fetchDataRect.size.width / displayRect.size.width;
} else {
visibleRatio = visibleRect.size.width / fetchDataRect.size.width;
displayRatio = displayRect.size.width / fetchDataRect.size.width;
fetchDataRatio = 1.0;
}
}
[viewToUpdate updateWithVisibleRatio:visibleRatio
displayRatio:displayRatio
leadingDisplayRatio:leadingDisplayTuningRatio
fetchDataRatio:fetchDataRatio
leadingFetchDataRatio:leadingFetchDataTuningRatio
direction:scrollDirection];
[self setNeedsLayout];
}
- (_ASRangeDebugBarView *)barViewForRangeController:(ASRangeController *)controller
{
_ASRangeDebugBarView *rangeControllerBarView = nil;
for (_ASRangeDebugBarView *rangeView in [[_rangeControllerViews reverseObjectEnumerator] allObjects]) {
// remove barView if its rangeController has been deleted
if (!rangeView.rangeController) {
rangeView.destroyOnLayout = YES;
[self setNeedsLayout];
}
ASInterfaceState interfaceState = [rangeView.rangeController.dataSource interfaceStateForRangeController:rangeView.rangeController];
if (!(interfaceState & (ASInterfaceStateVisible | ASInterfaceStateDisplay))) {
[self setNeedsLayout];
}
if ([rangeView.rangeController isEqual:controller]) {
rangeControllerBarView = rangeView;
}
}
return rangeControllerBarView;
}
#define MIN_VISIBLE_INSET 40
- (void)rangeDebugOverlayWasPanned:(UIPanGestureRecognizer *)recognizer
{
CGPoint translation = [recognizer translationInView:recognizer.view];
CGFloat newCenterX = recognizer.view.center.x + translation.x;
CGFloat newCenterY = recognizer.view.center.y + translation.y;
CGSize boundsSize = recognizer.view.bounds.size;
CGSize superBoundsSize = recognizer.view.superview.bounds.size;
CGFloat minAllowableX = -boundsSize.width / 2.0 + MIN_VISIBLE_INSET;
CGFloat maxAllowableX = superBoundsSize.width + boundsSize.width / 2.0 - MIN_VISIBLE_INSET;
if (newCenterX > maxAllowableX) {
newCenterX = maxAllowableX;
} else if (newCenterX < minAllowableX) {
newCenterX = minAllowableX;
}
CGFloat minAllowableY = -boundsSize.height / 2.0 + MIN_VISIBLE_INSET;
CGFloat maxAllowableY = superBoundsSize.height + boundsSize.height / 2.0 - MIN_VISIBLE_INSET;
if (newCenterY > maxAllowableY) {
newCenterY = maxAllowableY;
} else if (newCenterY < minAllowableY) {
newCenterY = minAllowableY;
}
recognizer.view.center = CGPointMake(newCenterX, newCenterY);
[recognizer setTranslation:CGPointMake(0, 0) inView:recognizer.view];
}
@end
#pragma mark _ASRangeDebugBarView
@implementation _ASRangeDebugBarView
{
ASTextNode *_debugText;
ASTextNode *_leftDebugText;
ASTextNode *_rightDebugText;
ASImageNode *_visibleRect;
ASImageNode *_displayRect;
ASImageNode *_fetchDataRect;
CGFloat _visibleRatio;
CGFloat _displayRatio;
CGFloat _fetchDataRatio;
CGFloat _leadingDisplayRatio;
CGFloat _leadingFetchDataRatio;
ASScrollDirection _scrollDirection;
BOOL _firstLayoutOfRects;
}
- (instancetype)initWithRangeController:(ASRangeController *)rangeController
{
self = [super initWithFrame:CGRectZero];
if (self) {
_firstLayoutOfRects = YES;
_rangeController = rangeController;
_debugText = [self createDebugTextNode];
_leftDebugText = [self createDebugTextNode];
_rightDebugText = [self createDebugTextNode];
_fetchDataRect = [self createRangeNodeWithColor:[UIColor orangeColor]];
_displayRect = [self createRangeNodeWithColor:[UIColor yellowColor]];
_visibleRect = [self createRangeNodeWithColor:[UIColor greenColor]];
}
return self;
}
#define HORIZONTAL_INSET 10
- (void)layoutSubviews
{
[super layoutSubviews];
CGSize boundsSize = self.bounds.size;
CGFloat subCellHeight = 9.0;
[self setBarDebugLabelsWithSize:subCellHeight];
[self setBarSubviewOrder];
CGRect rect = CGRectIntegral(CGRectMake(0, 0, boundsSize.width, floorf(boundsSize.height / 2.0)));
rect.size = [_debugText measure:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
rect.origin.x = (boundsSize.width - rect.size.width) / 2.0;
_debugText.frame = rect;
rect.origin.y += rect.size.height;
rect.origin.x = 0;
rect.size = CGSizeMake(HORIZONTAL_INSET, boundsSize.height / 2.0);
_leftDebugText.frame = rect;
rect.origin.x = boundsSize.width - HORIZONTAL_INSET;
_rightDebugText.frame = rect;
CGFloat visibleDimension = (boundsSize.width - 2 * HORIZONTAL_INSET) * _visibleRatio;
CGFloat displayDimension = (boundsSize.width - 2 * HORIZONTAL_INSET) * _displayRatio;
CGFloat fetchDataDimension = (boundsSize.width - 2 * HORIZONTAL_INSET) * _fetchDataRatio;
CGFloat visiblePoint = 0;
CGFloat displayPoint = 0;
CGFloat fetchDataPoint = 0;
BOOL displayLargerThanFetchData = (_displayRatio == 1.0) ? YES : NO;
if (ASScrollDirectionContainsLeft(_scrollDirection) || ASScrollDirectionContainsUp(_scrollDirection)) {
if (displayLargerThanFetchData) {
visiblePoint = (displayDimension - visibleDimension) * _leadingDisplayRatio;
fetchDataPoint = visiblePoint - (fetchDataDimension - visibleDimension) * _leadingFetchDataRatio;
} else {
visiblePoint = (fetchDataDimension - visibleDimension) * _leadingFetchDataRatio;
displayPoint = visiblePoint - (displayDimension - visibleDimension) * _leadingDisplayRatio;
}
} else if (ASScrollDirectionContainsRight(_scrollDirection) || ASScrollDirectionContainsDown(_scrollDirection)) {
if (displayLargerThanFetchData) {
visiblePoint = (displayDimension - visibleDimension) * (1 - _leadingDisplayRatio);
fetchDataPoint = visiblePoint - (fetchDataDimension - visibleDimension) * (1 - _leadingFetchDataRatio);
} else {
visiblePoint = (fetchDataDimension - visibleDimension) * (1 - _leadingFetchDataRatio);
displayPoint = visiblePoint - (displayDimension - visibleDimension) * (1 - _leadingDisplayRatio);
}
}
BOOL animate = !_firstLayoutOfRects;
[UIView animateWithDuration:animate ? 0.3 : 0.0 delay:0.0 options:UIViewAnimationOptionLayoutSubviews animations:^{
_visibleRect.frame = CGRectMake(HORIZONTAL_INSET + visiblePoint, rect.origin.y, visibleDimension, subCellHeight);
_displayRect.frame = CGRectMake(HORIZONTAL_INSET + displayPoint, rect.origin.y, displayDimension, subCellHeight);
_fetchDataRect.frame = CGRectMake(HORIZONTAL_INSET + fetchDataPoint, rect.origin.y, fetchDataDimension, subCellHeight);
} completion:^(BOOL finished) {}];
if (!animate) {
_visibleRect.alpha = _displayRect.alpha = _fetchDataRect.alpha = 0;
[UIView animateWithDuration:0.3 animations:^{
_visibleRect.alpha = _displayRect.alpha = _fetchDataRect.alpha = 1;
}];
}
_firstLayoutOfRects = NO;
}
- (void)updateWithVisibleRatio:(CGFloat)visibleRatio
displayRatio:(CGFloat)displayRatio
leadingDisplayRatio:(CGFloat)leadingDisplayRatio
fetchDataRatio:(CGFloat)fetchDataRatio
leadingFetchDataRatio:(CGFloat)leadingFetchDataRatio
direction:(ASScrollDirection)scrollDirection
{
_visibleRatio = visibleRatio;
_displayRatio = displayRatio;
_leadingDisplayRatio = leadingDisplayRatio;
_fetchDataRatio = fetchDataRatio;
_leadingFetchDataRatio = leadingFetchDataRatio;
_scrollDirection = scrollDirection;
[self setNeedsLayout];
}
- (void)setBarSubviewOrder
{
if (_fetchDataRatio == 1.0) {
[self sendSubviewToBack:_fetchDataRect.view];
} else {
[self sendSubviewToBack:_displayRect.view];
}
[self bringSubviewToFront:_visibleRect.view];
}
- (void)setBarDebugLabelsWithSize:(CGFloat)size
{
if (!_debugString) {
_debugString = [[_rangeController dataSource] nameForRangeControllerDataSource];
}
if (_debugString) {
_debugText.attributedString = [_ASRangeDebugBarView whiteAttributedStringFromString:_debugString withSize:size];
}
if (ASScrollDirectionContainsVerticalDirection(_scrollDirection)) {
_leftDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"▲" withSize:size];
_rightDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"▼" withSize:size];
} else if (ASScrollDirectionContainsHorizontalDirection(_scrollDirection)) {
_leftDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"◀︎" withSize:size];
_rightDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"▶︎" withSize:size];
}
_leftDebugText.hidden = (_scrollDirection != ASScrollDirectionLeft && _scrollDirection != ASScrollDirectionUp);
_rightDebugText.hidden = (_scrollDirection != ASScrollDirectionRight && _scrollDirection != ASScrollDirectionDown);
}
- (ASTextNode *)createDebugTextNode
{
ASTextNode *label = [[ASTextNode alloc] init];
[self addSubnode:label];
return label;
}
#define RANGE_BAR_CORNER_RADIUS 3
#define RANGE_BAR_BORDER_WIDTH 1
- (ASImageNode *)createRangeNodeWithColor:(UIColor *)color
{
ASImageNode *rangeBarImageNode = [[ASImageNode alloc] init];
rangeBarImageNode.image = [UIImage as_resizableRoundedImageWithCornerRadius:RANGE_BAR_CORNER_RADIUS
cornerColor:[UIColor clearColor]
fillColor:[color colorWithAlphaComponent:0.5]
borderColor:[[UIColor blackColor] colorWithAlphaComponent:0.9]
borderWidth:RANGE_BAR_BORDER_WIDTH
roundedCorners:UIRectCornerAllCorners
scale:[[UIScreen mainScreen] scale]];
[self addSubnode:rangeBarImageNode];
return rangeBarImageNode;
}
+ (NSAttributedString *)whiteAttributedStringFromString:(NSString *)string withSize:(CGFloat)size
{
NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor whiteColor],
NSFontAttributeName : [UIFont systemFontOfSize:size]};
return [[NSAttributedString alloc] initWithString:string attributes:attributes];
}
@end

View File

@@ -49,8 +49,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
std::vector<NSInteger> _itemCountsFromDataSource; // Main thread only.
ASMainSerialQueue *_mainSerialQueue;
NSMutableArray *_pendingEditCommandBlocks; // To be run on the main thread. Handles begin/endUpdates tracking.
dispatch_queue_t _editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes.
dispatch_group_t _editingTransactionGroup; // Group of all edit transaction blocks. Useful for waiting.
@@ -62,8 +61,6 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
BOOL _delegateDidDeleteSections;
}
@property (nonatomic, assign) NSUInteger batchUpdateCounter;
@end
@implementation ASDataController
@@ -87,15 +84,11 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
_mainSerialQueue = [[ASMainSerialQueue alloc] init];
_pendingEditCommandBlocks = [NSMutableArray array];
const char *queueName = [[NSString stringWithFormat:@"org.AsyncDisplayKit.ASDataController.editingTransactionQueue:%p", self] cStringUsingEncoding:NSASCIIStringEncoding];
_editingTransactionQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL);
dispatch_queue_set_specific(_editingTransactionQueue, &kASDataControllerEditingQueueKey, &kASDataControllerEditingQueueContext, NULL);
_editingTransactionGroup = dispatch_group_create();
_batchUpdateCounter = 0;
return self;
}
@@ -395,60 +388,55 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
- (void)_reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions synchronously:(BOOL)synchronously completion:(void (^)())completion
{
ASDisplayNodeAssertMainThread();
_initialReloadDataHasBeenCalled = YES;
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
[self invalidateDataSourceItemCounts];
NSUInteger sectionCount = [self itemCountsFromDataSource].size();
NSIndexSet *sectionIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)];
NSArray<ASIndexedNodeContext *> *contexts = [self _populateFromDataSourceWithSectionIndexSet:sectionIndexSet];
[self invalidateDataSourceItemCounts];
NSUInteger sectionCount = [self itemCountsFromDataSource].size();
NSIndexSet *sectionIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)];
NSArray<ASIndexedNodeContext *> *contexts = [self _populateFromDataSourceWithSectionIndexSet:sectionIndexSet];
// Allow subclasses to perform setup before going into the edit transaction
[self prepareForReloadData];
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
LOG(@"Edit Transaction - reloadData");
// Allow subclasses to perform setup before going into the edit transaction
[self prepareForReloadData];
// Remove everything that existed before the reload, now that we're ready to insert replacements
NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind];
NSUInteger editingNodesSectionCount = editingNodes.count;
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
LOG(@"Edit Transaction - reloadData");
// Remove everything that existed before the reload, now that we're ready to insert replacements
NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind];
NSUInteger editingNodesSectionCount = editingNodes.count;
if (editingNodesSectionCount) {
NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodesSectionCount)];
[self _deleteNodesAtIndexPaths:ASIndexPathsForTwoDimensionalArray(editingNodes) withAnimationOptions:animationOptions];
[self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
}
[self willReloadData];
// Insert empty sections
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
for (int i = 0; i < sectionCount; i++) {
[sections addObject:[[NSMutableArray alloc] init]];
}
[self _insertSections:sections atIndexSet:sectionIndexSet withAnimationOptions:animationOptions];
[self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions];
if (completion) {
[_mainSerialQueue performBlockOnMainThread:completion];
}
});
if (synchronously) {
[self waitUntilAllUpdatesAreCommitted];
if (editingNodesSectionCount) {
NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodesSectionCount)];
[self _deleteNodesAtIndexPaths:ASIndexPathsForTwoDimensionalArray(editingNodes) withAnimationOptions:animationOptions];
[self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
}
}];
[self willReloadData];
// Insert empty sections
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
for (int i = 0; i < sectionCount; i++) {
[sections addObject:[[NSMutableArray alloc] init]];
}
[self _insertSections:sections atIndexSet:sectionIndexSet withAnimationOptions:animationOptions];
[self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions];
if (completion) {
[_mainSerialQueue performBlockOnMainThread:completion];
}
});
if (synchronously) {
[self waitUntilAllUpdatesAreCommitted];
}
}
- (void)waitUntilAllUpdatesAreCommitted
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert(_batchUpdateCounter == 0, @"Should not be called between beginUpdate or endUpdate");
// This should never be called in a batch update, return immediately therefore
if (_batchUpdateCounter > 0) { return; }
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
@@ -516,10 +504,20 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
- (void)beginUpdates
{
ASDisplayNodeAssertMainThread();
// TODO: make this -waitUntilAllUpdatesAreCommitted?
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
// Begin queuing up edit calls that happen on the main thread.
// This will prevent further operations from being scheduled on _editingTransactionQueue.
_batchUpdateCounter++;
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[_mainSerialQueue performBlockOnMainThread:^{
// Deep copy _completedNodes to _externalCompletedNodes.
// Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate.
_externalCompletedNodes = ASTwoDimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]);
LOG(@"beginUpdates - begin updates call to delegate");
[_delegate dataControllerBeginUpdates:self];
}];
});
}
- (void)endUpdates
@@ -529,118 +527,72 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion
{
_batchUpdateCounter--;
LOG(@"endUpdatesWithCompletion - beginning");
ASDisplayNodeAssertMainThread();
if (_batchUpdateCounter == 0) {
LOG(@"endUpdatesWithCompletion - beginning");
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[_mainSerialQueue performBlockOnMainThread:^{
// Deep copy _completedNodes to _externalCompletedNodes.
// Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate.
_externalCompletedNodes = ASTwoDimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]);
LOG(@"endUpdatesWithCompletion - begin updates call to delegate");
[_delegate dataControllerBeginUpdates:self];
}];
});
// Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates.
// Each subsequent command in the queue will also wait on the full asynchronous completion of the prior command's edit transaction.
LOG(@"endUpdatesWithCompletion - %zd blocks to run", _pendingEditCommandBlocks.count);
NSUInteger i = 0;
for (dispatch_block_t block in _pendingEditCommandBlocks) {
LOG(@"endUpdatesWithCompletion - running block #%zd", i);
block();
i += 1;
}
[_pendingEditCommandBlocks removeAllObjects];
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[_mainSerialQueue performBlockOnMainThread:^{
// Now that the transaction is done, _completedNodes can be accessed externally again.
_externalCompletedNodes = nil;
LOG(@"endUpdatesWithCompletion - calling delegate end");
[_delegate dataController:self endUpdatesAnimated:animated completion:completion];
}];
});
}
}
/**
* Queues the given operation until an `endUpdates` synchronize update is completed.
*
* If this method is called outside of a begin/endUpdates batch update, the block is
* executed immediately.
*/
- (void)performEditCommandWithBlock:(void (^)(void))block
{
// This method needs to block the thread and synchronously perform the operation if we are not
// queuing commands for begin/endUpdates. If we are queuing, it needs to return immediately.
if (!_initialReloadDataHasBeenCalled) {
return;
}
if (block == nil) {
return;
}
// If we have never performed a reload, there is no value in executing edit operations as the initial
// reload will directly re-query the latest state of the datasource - so completely skip the block in this case.
if (_batchUpdateCounter == 0) {
block();
} else {
[_pendingEditCommandBlocks addObject:block];
}
// Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates.
// Each subsequent command in the queue will also wait on the full asynchronous completion of the prior command's edit transaction.
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[_mainSerialQueue performBlockOnMainThread:^{
// Now that the transaction is done, _completedNodes can be accessed externally again.
_externalCompletedNodes = nil;
LOG(@"endUpdatesWithCompletion - calling delegate end");
[_delegate dataController:self endUpdatesAnimated:animated completion:completion];
}];
});
}
#pragma mark - Section Editing (External API)
- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssertMainThread();
LOG(@"Edit Command - insertSections: %@", sections);
if (!_initialReloadDataHasBeenCalled) {
return;
}
LOG(@"Edit Command - insertSections: %@", sections);
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
NSArray<ASIndexedNodeContext *> *contexts = [self _populateFromDataSourceWithSectionIndexSet:sections];
[self prepareForInsertSections:sections];
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[self willInsertSections:sections];
LOG(@"Edit Transaction - insertSections: %@", sections);
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count];
for (NSUInteger i = 0; i < sections.count; i++) {
[sectionArray addObject:[NSMutableArray array]];
}
[self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions];
NSArray<ASIndexedNodeContext *> *contexts = [self _populateFromDataSourceWithSectionIndexSet:sections];
[self prepareForInsertSections:sections];
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[self willInsertSections:sections];
LOG(@"Edit Transaction - insertSections: %@", sections);
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count];
for (NSUInteger i = 0; i < sections.count; i++) {
[sectionArray addObject:[NSMutableArray array]];
}
[self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions];
[self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions];
});
}];
[self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions];
});
}
- (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
LOG(@"Edit Command - deleteSections: %@", sections);
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[self willDeleteSections:sections];
ASDisplayNodeAssertMainThread();
LOG(@"Edit Command - deleteSections: %@", sections);
if (!_initialReloadDataHasBeenCalled) {
return;
}
// remove elements
LOG(@"Edit Transaction - deleteSections: %@", sections);
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
[self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions];
});
}];
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[self willDeleteSections:sections];
// remove elements
LOG(@"Edit Transaction - deleteSections: %@", sections);
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
[self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions];
});
}
- (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
@@ -650,33 +602,35 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
LOG(@"Edit Command - moveSection");
ASDisplayNodeAssertMainThread();
LOG(@"Edit Command - moveSection");
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[self willMoveSection:section toSection:newSection];
if (!_initialReloadDataHasBeenCalled) {
return;
}
// remove elements
LOG(@"Edit Transaction - moveSection");
NSMutableArray *editingRows = _editingNodes[ASDataControllerRowNodeKind];
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(editingRows, [NSIndexSet indexSetWithIndex:section]);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(editingRows, indexPaths);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[self willMoveSection:section toSection:newSection];
// update the section of indexpaths
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
for (NSIndexPath *indexPath in indexPaths) {
NSIndexPath *updatedIndexPath = [NSIndexPath indexPathForItem:indexPath.item inSection:newSection];
[updatedIndexPaths addObject:updatedIndexPath];
}
// remove elements
LOG(@"Edit Transaction - moveSection");
NSMutableArray *editingRows = _editingNodes[ASDataControllerRowNodeKind];
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(editingRows, [NSIndexSet indexSetWithIndex:section]);
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(editingRows, indexPaths);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
// Don't re-calculate size for moving
[self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions];
});
}];
// update the section of indexpaths
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
for (NSIndexPath *indexPath in indexPaths) {
NSIndexPath *updatedIndexPath = [NSIndexPath indexPathForItem:indexPath.item inSection:newSection];
[updatedIndexPaths addObject:updatedIndexPath];
}
// Don't re-calculate size for moving
[self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions];
});
}
@@ -736,59 +690,64 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
LOG(@"Edit Command - insertRows: %@", indexPaths);
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
ASDisplayNodeAssertMainThread();
if (!_initialReloadDataHasBeenCalled) {
return;
}
// Sort indexPath to avoid messing up the index when inserting in several batches
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
NSMutableArray<ASIndexedNodeContext *> *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
LOG(@"Edit Command - insertRows: %@", indexPaths);
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
for (NSIndexPath *indexPath in sortedIndexPaths) {
ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath];
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath];
[contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock
indexPath:indexPath
constrainedSize:constrainedSize
environmentTraitCollection:environmentTraitCollection]];
}
// Sort indexPath to avoid messing up the index when inserting in several batches
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
NSMutableArray<ASIndexedNodeContext *> *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
[self prepareForInsertRowsAtIndexPaths:indexPaths];
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
for (NSIndexPath *indexPath in sortedIndexPaths) {
ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath];
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath];
[contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock
indexPath:indexPath
constrainedSize:constrainedSize
environmentTraitCollection:environmentTraitCollection]];
}
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[self willInsertRowsAtIndexPaths:indexPaths];
[self prepareForInsertRowsAtIndexPaths:indexPaths];
LOG(@"Edit Transaction - insertRows: %@", indexPaths);
[self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions];
});
}];
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[self willInsertRowsAtIndexPaths:indexPaths];
LOG(@"Edit Transaction - insertRows: %@", indexPaths);
[self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions];
});
}
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
LOG(@"Edit Command - deleteRows: %@", indexPaths);
ASDisplayNodeAssertMainThread();
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
// Sort indexPath in order to avoid messing up the index when deleting in several batches.
// FIXME: Shouldn't deletes be sorted in descending order?
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
if (!_initialReloadDataHasBeenCalled) {
return;
}
[self prepareForDeleteRowsAtIndexPaths:sortedIndexPaths];
LOG(@"Edit Command - deleteRows: %@", indexPaths);
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[self willDeleteRowsAtIndexPaths:sortedIndexPaths];
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
// Sort indexPath in order to avoid messing up the index when deleting in several batches.
// FIXME: Shouldn't deletes be sorted in descending order?
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
LOG(@"Edit Transaction - deleteRows: %@", indexPaths);
[self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions];
});
}];
[self prepareForDeleteRowsAtIndexPaths:sortedIndexPaths];
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[self willDeleteRowsAtIndexPaths:sortedIndexPaths];
LOG(@"Edit Transaction - deleteRows: %@", indexPaths);
[self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions];
});
}
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
@@ -798,22 +757,24 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
- (void)relayoutAllNodes
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
LOG(@"Edit Command - relayoutRows");
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
ASDisplayNodeAssertMainThread();
if (!_initialReloadDataHasBeenCalled) {
return;
}
// Can't relayout right away because _completedNodes may not be up-to-date,
// i.e there might be some nodes that were measured using the old constrained size but haven't been added to _completedNodes
// (see _layoutNodes:atIndexPaths:withAnimationOptions:).
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[_mainSerialQueue performBlockOnMainThread:^{
for (NSString *kind in _completedNodes) {
[self _relayoutNodesOfKind:kind];
}
}];
});
}];
LOG(@"Edit Command - relayoutRows");
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
// Can't relayout right away because _completedNodes may not be up-to-date,
// i.e there might be some nodes that were measured using the old constrained size but haven't been added to _completedNodes
// (see _layoutNodes:atIndexPaths:withAnimationOptions:).
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
[_mainSerialQueue performBlockOnMainThread:^{
for (NSString *kind in _completedNodes) {
[self _relayoutNodesOfKind:kind];
}
}];
});
}
- (void)_relayoutNodesOfKind:(NSString *)kind
@@ -840,22 +801,24 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
{
[self performEditCommandWithBlock:^{
ASDisplayNodeAssertMainThread();
LOG(@"Edit Command - moveRow: %@ > %@", indexPath, newIndexPath);
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath);
NSArray *indexPaths = @[indexPath];
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
ASDisplayNodeAssertMainThread();
if (!_initialReloadDataHasBeenCalled) {
return;
}
// Don't re-calculate size for moving
NSArray *newIndexPaths = @[newIndexPath];
[self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions];
});
}];
LOG(@"Edit Command - moveRow: %@ > %@", indexPath, newIndexPath);
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath);
NSArray *indexPaths = @[indexPath];
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths);
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
// Don't re-calculate size for moving
NSArray *newIndexPaths = @[newIndexPath];
[self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions];
});
}
#pragma mark - Data Querying (Subclass API)

View File

@@ -134,6 +134,8 @@ NS_ASSUME_NONNULL_BEGIN
- (NSArray<NSArray <ASCellNode *> *> *)completedNodes;
- (NSString *)nameForRangeControllerDataSource;
@end
/**

View File

@@ -11,13 +11,16 @@
#import "ASRangeController.h"
#import "ASAssert.h"
#import "ASWeakSet.h"
#import "ASCellNode.h"
#import "ASDisplayNodeExtras.h"
#import "ASDisplayNodeInternal.h"
#import "ASMultidimensionalArrayUtils.h"
#import "ASInternalHelpers.h"
#import "ASMultiDimensionalArrayUtils.h"
#import "ASWeakSet.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import "ASCellNode.h"
#import "AsyncDisplayKit+Debug.h"
#define AS_RANGECONTROLLER_LOG_UPDATE_FREQ 0
@@ -64,6 +67,10 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
#endif
if ([ASRangeController shouldShowRangeDebugOverlay]) {
[self addRangeControllerToRangeDebugOverlay];
}
return self;
}
@@ -335,6 +342,25 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
}
}
// TODO: This code is for debugging only, but would be great to clean up with a delegate method implementation.
if ([ASRangeController shouldShowRangeDebugOverlay]) {
ASScrollDirection scrollableDirections = ASScrollDirectionUp | ASScrollDirectionDown;
if ([_dataSource isKindOfClass:NSClassFromString(@"ASCollectionView")]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
scrollableDirections = (ASScrollDirection)[_dataSource performSelector:@selector(scrollableDirections)];
#pragma clang diagnostic pop
}
[self updateRangeController:self
withScrollableDirections:scrollableDirections
scrollDirection:scrollDirection
rangeMode:rangeMode
displayTuningParameters:parametersDisplay
fetchDataTuningParameters:parametersFetchData
interfaceState:selfInterfaceState];
}
_rangeIsValid = YES;
#if ASRangeControllerLoggingEnabled

View File

@@ -344,7 +344,7 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo
// The node is loaded but we're not on main.
// We will call [self __setNeedsLayout] when we apply
// the pending state. We need to call it on main if the node is loaded
// to support implicit hierarchy management.
// to support automatic subnode management.
[ASDisplayNodeGetPendingState(self) setNeedsLayout];
} else {
// The node is not loaded and we're not on main.

View File

@@ -124,7 +124,10 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
// Main thread only
_ASTransitionContext *_pendingLayoutTransitionContext;
BOOL _usesImplicitHierarchyManagement;
BOOL _automaticallyManagesSubnodes;
NSTimeInterval _defaultLayoutTransitionDuration;
NSTimeInterval _defaultLayoutTransitionDelay;
UIViewAnimationOptions _defaultLayoutTransitionOptions;
int32_t _pendingTransitionID;
ASLayoutTransition *_pendingLayoutTransition;

View File

@@ -15,6 +15,7 @@
#import "NSIndexSet+ASHelpers.h"
#import "ASAssert.h"
#import "ASDisplayNode+Beta.h"
#import <unordered_map>
#define ASFailUpdateValidation(...)\

View File

@@ -45,7 +45,7 @@
}];
#pragma clang diagnostic pop
[self waitForExpectationsWithTimeout:3 handler:nil];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
@end

View File

@@ -47,28 +47,13 @@
@implementation ASDisplayNodeImplicitHierarchyTests
- (void)setUp {
[super setUp];
[ASDisplayNode setUsesImplicitHierarchyManagement:YES];
}
- (void)tearDown {
[ASDisplayNode setUsesImplicitHierarchyManagement:NO];
[super tearDown];
}
- (void)testFeatureFlag
{
XCTAssert([ASDisplayNode usesImplicitHierarchyManagement]);
ASDisplayNode *node = [[ASDisplayNode alloc] init];
XCTAssert(node.usesImplicitHierarchyManagement);
[ASDisplayNode setUsesImplicitHierarchyManagement:NO];
XCTAssertFalse([ASDisplayNode usesImplicitHierarchyManagement]);
XCTAssertFalse(node.usesImplicitHierarchyManagement);
node.usesImplicitHierarchyManagement = YES;
XCTAssert(node.usesImplicitHierarchyManagement);
XCTAssertFalse(node.automaticallyManagesSubnodes);
node.automaticallyManagesSubnodes = YES;
XCTAssertTrue(node.automaticallyManagesSubnodes);
}
- (void)testInitialNodeInsertionWithOrdering
@@ -80,6 +65,7 @@
ASDisplayNode *node5 = [[ASDisplayNode alloc] init];
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
node.automaticallyManagesSubnodes = YES;
node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) {
ASStaticLayoutSpec *staticLayout = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[node4]];
@@ -106,6 +92,7 @@
ASDisplayNode *node3 = [[ASDisplayNode alloc] init];
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
node.automaticallyManagesSubnodes = YES;
node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize){
ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode;
if ([strongNode.layoutState isEqualToNumber:@1]) {
@@ -136,6 +123,7 @@
ASDisplayNode *node2 = [[ASDisplayNode alloc] init];
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
node.automaticallyManagesSubnodes = YES;
node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) {
ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode;
if ([strongNode.layoutState isEqualToNumber:@1]) {
@@ -179,6 +167,7 @@
ASDisplayNode *node2 = [[ASDisplayNode alloc] init];
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
node.automaticallyManagesSubnodes = YES;
node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) {
ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode;
@@ -190,6 +179,8 @@
};
// Intentionally trigger view creation
[node view];
[node1 view];
[node2 view];
XCTestExpectation *expectation = [self expectationWithDescription:@"Fix IHM layout transition also if one node is already loaded"];

View File

@@ -0,0 +1,35 @@
//
// ASTextNodeSnapshotTests.m
// AsyncDisplayKit
//
// Created by Garrett Moon on 8/12/16.
// 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 "ASSnapshotTestCase.h"
#import <AsyncDisplayKit/AsyncDisplayKit.h>
@interface ASTextNodeSnapshotTests : ASSnapshotTestCase
@end
@implementation ASTextNodeSnapshotTests
- (void)testTextContainerInset
{
// trivial test case to ensure ASSnapshotTestCase works
ASTextNode *textNode = [[ASTextNode alloc] init];
textNode.attributedText = [[NSAttributedString alloc] initWithString:@"judar"
attributes:@{NSFontAttributeName : [UIFont italicSystemFontOfSize:24]}];
[textNode measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))];
textNode.frame = CGRectMake(0, 0, textNode.calculatedSize.width, textNode.calculatedSize.height);
textNode.textContainerInset = UIEdgeInsetsMake(0, 2, 0, 2);
ASSnapshotVerifyNode(textNode, nil);
}
@end

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1,5 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '7.0'
target 'Sample' do
pod 'AsyncDisplayKit', :path => '../..'
end

View File

@@ -0,0 +1,368 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */; };
05E2128719D4DB510098F589 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128619D4DB510098F589 /* main.m */; };
05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128919D4DB510098F589 /* AppDelegate.m */; };
05E2128D19D4DB510098F589 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128C19D4DB510098F589 /* ViewController.m */; };
6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; };
6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; };
DFE855DDBC731242D3515B58 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C284F7E957985CA251284B05 /* libPods-Sample.a */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = "<group>"; };
05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; };
05E2128519D4DB510098F589 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
05E2128619D4DB510098F589 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
05E2128819D4DB510098F589 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
05E2128919D4DB510098F589 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
05E2128B19D4DB510098F589 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
05E2128C19D4DB510098F589 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
1C47DEC3F9D2BD9AD5F5CD67 /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = "<group>"; };
6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; };
6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; };
79ED4D85CC60068C341CFD77 /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = "<group>"; };
C284F7E957985CA251284B05 /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
05E2127E19D4DB510098F589 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DFE855DDBC731242D3515B58 /* libPods-Sample.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
05E2127819D4DB510098F589 = {
isa = PBXGroup;
children = (
05E2128319D4DB510098F589 /* Sample */,
05E2128219D4DB510098F589 /* Products */,
1A943BF0259746F18D6E423F /* Frameworks */,
1AE410B73DA5C3BD087ACDD7 /* Pods */,
);
indentWidth = 2;
sourceTree = "<group>";
tabWidth = 2;
usesTabs = 0;
};
05E2128219D4DB510098F589 /* Products */ = {
isa = PBXGroup;
children = (
05E2128119D4DB510098F589 /* Sample.app */,
);
name = Products;
sourceTree = "<group>";
};
05E2128319D4DB510098F589 /* Sample */ = {
isa = PBXGroup;
children = (
05E2128819D4DB510098F589 /* AppDelegate.h */,
05E2128919D4DB510098F589 /* AppDelegate.m */,
05E2128B19D4DB510098F589 /* ViewController.h */,
05E2128C19D4DB510098F589 /* ViewController.m */,
05E2128419D4DB510098F589 /* Supporting Files */,
);
path = Sample;
sourceTree = "<group>";
};
05E2128419D4DB510098F589 /* Supporting Files */ = {
isa = PBXGroup;
children = (
0585427F19D4DBE100606EA6 /* Default-568h@2x.png */,
6C2C82AA19EE274300767484 /* Default-667h@2x.png */,
6C2C82AB19EE274300767484 /* Default-736h@3x.png */,
05E2128519D4DB510098F589 /* Info.plist */,
05E2128619D4DB510098F589 /* main.m */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
1A943BF0259746F18D6E423F /* Frameworks */ = {
isa = PBXGroup;
children = (
C284F7E957985CA251284B05 /* libPods-Sample.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
1AE410B73DA5C3BD087ACDD7 /* Pods */ = {
isa = PBXGroup;
children = (
79ED4D85CC60068C341CFD77 /* Pods-Sample.debug.xcconfig */,
1C47DEC3F9D2BD9AD5F5CD67 /* Pods-Sample.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
05E2128019D4DB510098F589 /* Sample */ = {
isa = PBXNativeTarget;
buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */;
buildPhases = (
E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */,
05E2127D19D4DB510098F589 /* Sources */,
05E2127E19D4DB510098F589 /* Frameworks */,
05E2127F19D4DB510098F589 /* Resources */,
F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */,
6E05308BEF86AD80AEB4EEE7 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Sample;
productName = Sample;
productReference = 05E2128119D4DB510098F589 /* Sample.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
05E2127919D4DB510098F589 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0710;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
05E2128019D4DB510098F589 = {
CreatedOnToolsVersion = 6.0.1;
};
};
};
buildConfigurationList = 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 05E2127819D4DB510098F589;
productRefGroup = 05E2128219D4DB510098F589 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
05E2128019D4DB510098F589 /* Sample */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
05E2127F19D4DB510098F589 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */,
6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */,
6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
6E05308BEF86AD80AEB4EEE7 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
05E2127D19D4DB510098F589 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
05E2128D19D4DB510098F589 /* ViewController.m in Sources */,
05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */,
05E2128719D4DB510098F589 /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
05E212A219D4DB510098F589 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
05E212A319D4DB510098F589 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
05E212A519D4DB510098F589 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 79ED4D85CC60068C341CFD77 /* Pods-Sample.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
INFOPLIST_FILE = Sample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 7.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
05E212A619D4DB510098F589 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1C47DEC3F9D2BD9AD5F5CD67 /* Pods-Sample.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
INFOPLIST_FILE = Sample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 7.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
05E212A219D4DB510098F589 /* Debug */,
05E212A319D4DB510098F589 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
05E212A519D4DB510098F589 /* Debug */,
05E212A619D4DB510098F589 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 05E2127919D4DB510098F589 /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:Sample.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0710"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "05E2128019D4DB510098F589"
BuildableName = "Sample.app"
BlueprintName = "Sample"
ReferencedContainer = "container:Sample.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "05E2128019D4DB510098F589"
BuildableName = "Sample.app"
BlueprintName = "Sample"
ReferencedContainer = "container:Sample.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "05E2128019D4DB510098F589"
BuildableName = "Sample.app"
BlueprintName = "Sample"
ReferencedContainer = "container:Sample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "05E2128019D4DB510098F589"
BuildableName = "Sample.app"
BlueprintName = "Sample"
ReferencedContainer = "container:Sample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,24 @@
//
// AppDelegate.h
// Sample
//
// 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.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

View File

@@ -0,0 +1,33 @@
//
// AppDelegate.m
// Sample
//
// 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.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "AppDelegate.h"
#import "ViewController.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
self.window.rootViewController = [[ViewController alloc] init];
[self.window makeKeyAndVisible];
return YES;
}
@end

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,22 @@
//
// ViewController.h
// Sample
//
// 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.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end

View File

@@ -0,0 +1,199 @@
//
// ViewController.m
// Sample
//
// 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.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "ViewController.h"
#import <AsyncDisplayKit/AsyncDisplayKit.h>
#pragma mark - TransitionNode
#define USE_CUSTOM_LAYOUT_TRANSITION 0
@interface TransitionNode : ASDisplayNode
@property (nonatomic, assign) BOOL enabled;
@property (nonatomic, strong) ASButtonNode *buttonNode;
@property (nonatomic, strong) ASTextNode *textNodeOne;
@property (nonatomic, strong) ASTextNode *textNodeTwo;
@end
@implementation TransitionNode
#pragma mark - Lifecycle
- (instancetype)init
{
self = [super init];
if (self == nil) { return self; }
self.usesImplicitHierarchyManagement = YES;
// Define the layout transition duration for the default transition
self.defaultLayoutTransitionDuration = 1.0;
_enabled = NO;
// Setup text nodes
_textNodeOne = [[ASTextNode alloc] init];
_textNodeOne.attributedText = [[NSAttributedString alloc] initWithString:@"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled"];
_textNodeTwo = [[ASTextNode alloc] init];
_textNodeTwo.attributedText = [[NSAttributedString alloc] initWithString:@"It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English."];
// Setup button
NSString *buttonTitle = @"Start Layout Transition";
UIFont *buttonFont = [UIFont systemFontOfSize:16.0];
UIColor *buttonColor = [UIColor blueColor];
_buttonNode = [[ASButtonNode alloc] init];
[_buttonNode setTitle:buttonTitle withFont:buttonFont withColor:buttonColor forState:ASControlStateNormal];
// Note: Currently we have to set all the button properties to the same one as for ASControlStateNormal. Otherwise
// if the button is involved in the layout transition it would break the transition as it does a layout pass
// while changing the title. This needs and will be fixed in the future!
[_buttonNode setTitle:buttonTitle withFont:buttonFont withColor:buttonColor forState:ASControlStateHighlighted];
// Some debug colors
_textNodeOne.backgroundColor = [UIColor orangeColor];
_textNodeTwo.backgroundColor = [UIColor greenColor];
return self;
}
- (void)didLoad
{
[super didLoad];
[self.buttonNode addTarget:self action:@selector(buttonPressed:) forControlEvents:ASControlNodeEventTouchDown];
}
#pragma mark - Actions
- (void)buttonPressed:(id)sender
{
self.enabled = !self.enabled;
[self transitionLayoutAnimated:YES measurementCompletion:nil];
}
#pragma mark - Layout
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
ASTextNode *nextTextNode = self.enabled ? self.textNodeTwo : self.textNodeOne;
nextTextNode.flexGrow = YES;
nextTextNode.flexShrink = YES;
ASStackLayoutSpec *horizontalStackLayout = [ASStackLayoutSpec horizontalStackLayoutSpec];
horizontalStackLayout.children = @[nextTextNode];
self.buttonNode.alignSelf = ASStackLayoutAlignSelfCenter;
ASStackLayoutSpec *verticalStackLayout = [ASStackLayoutSpec verticalStackLayoutSpec];
verticalStackLayout.spacing = 10.0;
verticalStackLayout.children = @[horizontalStackLayout, self.buttonNode];
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(15.0, 15.0, 15.0, 15.0) child:verticalStackLayout];
}
#pragma mark - Transition
#if USE_CUSTOM_LAYOUT_TRANSITION
- (void)animateLayoutTransition:(id<ASContextTransitioning>)context
{
ASDisplayNode *fromNode = [[context removedSubnodes] objectAtIndex:0];
ASDisplayNode *toNode = [[context insertedSubnodes] objectAtIndex:0];
ASButtonNode *buttonNode = nil;
for (ASDisplayNode *node in [context subnodesForKey:ASTransitionContextToLayoutKey]) {
if ([node isKindOfClass:[ASButtonNode class]]) {
buttonNode = (ASButtonNode *)node;
break;
}
}
CGRect toNodeFrame = [context finalFrameForNode:toNode];
toNodeFrame.origin.x += (self.enabled ? toNodeFrame.size.width : -toNodeFrame.size.width);
toNode.frame = toNodeFrame;
toNode.alpha = 0.0;
CGRect fromNodeFrame = fromNode.frame;
fromNodeFrame.origin.x += (self.enabled ? -fromNodeFrame.size.width : fromNodeFrame.size.width);
// We will use the same transition duration as the default transition
[UIView animateWithDuration:self.defaultLayoutTransitionDuration animations:^{
toNode.frame = [context finalFrameForNode:toNode];
toNode.alpha = 1.0;
fromNode.frame = fromNodeFrame;
fromNode.alpha = 0.0;
// Update frame of self
CGSize fromSize = [context layoutForKey:ASTransitionContextFromLayoutKey].size;
CGSize toSize = [context layoutForKey:ASTransitionContextToLayoutKey].size;
BOOL isResized = (CGSizeEqualToSize(fromSize, toSize) == NO);
if (isResized == YES) {
CGPoint position = self.frame.origin;
self.frame = CGRectMake(position.x, position.y, toSize.width, toSize.height);
}
buttonNode.frame = [context finalFrameForNode:buttonNode];
} completion:^(BOOL finished) {
[context completeTransition:finished];
}];
}
#endif
@end
#pragma mark - ViewController
@interface ViewController ()
@property (nonatomic, strong) TransitionNode *transitionNode;
@end
@implementation ViewController
#pragma mark - UIViewController
- (void)viewDidLoad
{
[super viewDidLoad];
_transitionNode = [TransitionNode new];
[self.view addSubnode:_transitionNode];
// Some debug colors
_transitionNode.backgroundColor = [UIColor grayColor];
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
CGSize size = [self.transitionNode measure:self.view.frame.size];
self.transitionNode.frame = CGRectMake(0, 20, size.width, size.height);
}
@end

View File

@@ -0,0 +1,26 @@
//
// main.m
// Sample
//
// 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.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

View File

@@ -21,6 +21,85 @@
#define StrokeRoundedImages 0
#define IsDigit(v) (v >= '0' && v <= '9')
static time_t parseRfc3339ToTimeT(const char *string)
{
int dy, dm, dd;
int th, tm, ts;
int oh, om, osign;
char current;
if (!string)
return (time_t)0;
// date
if (sscanf(string, "%04d-%02d-%02d", &dy, &dm, &dd) == 3) {
string += 10;
if (*string++ != 'T')
return (time_t)0;
// time
if (sscanf(string, "%02d:%02d:%02d", &th, &tm, &ts) == 3) {
string += 8;
current = *string;
// optional: second fraction
if (current == '.') {
++string;
while(IsDigit(*string))
++string;
current = *string;
}
if (current == 'Z') {
oh = om = 0;
osign = 1;
} else if (current == '-') {
++string;
if (sscanf(string, "%02d:%02d", &oh, &om) != 2)
return (time_t)0;
osign = -1;
} else if (current == '+') {
++string;
if (sscanf(string, "%02d:%02d", &oh, &om) != 2)
return (time_t)0;
osign = 1;
} else {
return (time_t)0;
}
struct tm timeinfo;
timeinfo.tm_wday = timeinfo.tm_yday = 0;
timeinfo.tm_zone = NULL;
timeinfo.tm_isdst = -1;
timeinfo.tm_year = dy - 1900;
timeinfo.tm_mon = dm - 1;
timeinfo.tm_mday = dd;
timeinfo.tm_hour = th;
timeinfo.tm_min = tm;
timeinfo.tm_sec = ts;
// convert to utc
return timegm(&timeinfo) - (((oh * 60 * 60) + (om * 60)) * osign);
}
}
return (time_t)0;
}
static NSDate *parseRfc3339ToNSDate(NSString *rfc3339DateTimeString)
{
time_t t = parseRfc3339ToTimeT([rfc3339DateTimeString cStringUsingEncoding:NSUTF8StringEncoding]);
return [NSDate dateWithTimeIntervalSince1970:t];
}
@implementation UIColor (Additions)
+ (UIColor *)darkBlueColor
@@ -142,26 +221,15 @@
@implementation NSString (Additions)
// Returns a user-visible date time string that corresponds to the
// specified RFC 3339 date time string. Note that this does not handle
// all possible RFC 3339 date time strings, just one of the most common
// styles.
/*
* Returns a user-visible date time string that corresponds to the
* specified RFC 3339 date time string. Note that this does not handle
* all possible RFC 3339 date time strings, just one of the most common
* styles.
*/
+ (NSDate *)userVisibleDateTimeStringForRFC3339DateTimeString:(NSString *)rfc3339DateTimeString
{
NSDateFormatter * rfc3339DateFormatter;
NSLocale * enUSPOSIXLocale;
// Convert the RFC 3339 date time string to an NSDate.
rfc3339DateFormatter = [[NSDateFormatter alloc] init];
enUSPOSIXLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
[rfc3339DateFormatter setLocale:enUSPOSIXLocale];
[rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ssZ'"];
[rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
return [rfc3339DateFormatter dateFromString:rfc3339DateTimeString];
return parseRfc3339ToNSDate(rfc3339DateTimeString);
}
+ (NSString *)elapsedTimeStringSinceDate:(NSString *)uploadDateString

View File

@@ -40,6 +40,8 @@
_pagerNode.dataSource = self;
_pagerNode.delegate = self;
[ASRangeController setShouldShowRangeDebugOverlay:YES];
// Could implement ASCollectionDelegate if we wanted extra callbacks, like from UIScrollView.
//_pagerNode.delegate = self;