mirror of
https://github.com/HackPlan/AsyncDisplayKit.git
synced 2026-03-29 22:36:09 +08:00
Merge branch 'master' into releases/p6.10
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -134,6 +134,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
- (NSArray<NSArray <ASCellNode *> *> *)completedNodes;
|
||||
|
||||
- (NSString *)nameForRangeControllerDataSource;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#import "NSIndexSet+ASHelpers.h"
|
||||
#import "ASAssert.h"
|
||||
#import "ASDisplayNode+Beta.h"
|
||||
|
||||
#import <unordered_map>
|
||||
|
||||
#define ASFailUpdateValidation(...)\
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
}];
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
[self waitForExpectationsWithTimeout:3 handler:nil];
|
||||
[self waitForExpectationsWithTimeout:30 handler:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -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"];
|
||||
|
||||
35
AsyncDisplayKitTests/ASTextNodeSnapshotTests.m
Normal file
35
AsyncDisplayKitTests/ASTextNodeSnapshotTests.m
Normal 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 |
BIN
examples/ASDKLayoutTransition/Default-568h@2x.png
Normal file
BIN
examples/ASDKLayoutTransition/Default-568h@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
examples/ASDKLayoutTransition/Default-667h@2x.png
Normal file
BIN
examples/ASDKLayoutTransition/Default-667h@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
examples/ASDKLayoutTransition/Default-736h@3x.png
Normal file
BIN
examples/ASDKLayoutTransition/Default-736h@3x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
5
examples/ASDKLayoutTransition/Podfile
Normal file
5
examples/ASDKLayoutTransition/Podfile
Normal file
@@ -0,0 +1,5 @@
|
||||
source 'https://github.com/CocoaPods/Specs.git'
|
||||
platform :ios, '7.0'
|
||||
target 'Sample' do
|
||||
pod 'AsyncDisplayKit', :path => '../..'
|
||||
end
|
||||
368
examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj
Normal file
368
examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj
Normal 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 */;
|
||||
}
|
||||
7
examples/ASDKLayoutTransition/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
examples/ASDKLayoutTransition/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:Sample.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -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>
|
||||
24
examples/ASDKLayoutTransition/Sample/AppDelegate.h
Normal file
24
examples/ASDKLayoutTransition/Sample/AppDelegate.h
Normal 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
|
||||
33
examples/ASDKLayoutTransition/Sample/AppDelegate.m
Normal file
33
examples/ASDKLayoutTransition/Sample/AppDelegate.m
Normal 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
|
||||
36
examples/ASDKLayoutTransition/Sample/Info.plist
Normal file
36
examples/ASDKLayoutTransition/Sample/Info.plist
Normal 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>
|
||||
22
examples/ASDKLayoutTransition/Sample/ViewController.h
Normal file
22
examples/ASDKLayoutTransition/Sample/ViewController.h
Normal 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
|
||||
199
examples/ASDKLayoutTransition/Sample/ViewController.m
Normal file
199
examples/ASDKLayoutTransition/Sample/ViewController.m
Normal 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
|
||||
26
examples/ASDKLayoutTransition/Sample/main.m
Normal file
26
examples/ASDKLayoutTransition/Sample/main.m
Normal 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]));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user