Swiftgram/AsyncDisplayKit/ASDisplayNode.h

541 lines
22 KiB
Objective-C

/* 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 <UIKit/UIKit.h>
#import "_ASAsyncTransactionContainer.h"
#import "ASBaseDefines.h"
/**
* An `ASDisplayNode` is an abstraction over `UIView` and `CALayer` that allows you to perform calculations about a view
* hierarchy off the main thread, and could do rendering off the main thread as well.
*
* The node API is designed to be as similar as possible to `UIView`.
*
* TODO add more details + example
*
* ## Subclassing
*
* `ASDisplayNode` can be subclassed to create a new UI element. The subclass header `ASDisplayNode+Subclasses` provides
* necessary declarations and conveniences.
*
* Commons reasons to subclass includes making a `UIView` property available and receiving a callback after async
* display.
*
*/
@interface ASDisplayNode : NSObject
/** @name Initializing a node object */
/**
* @abstract Designated initializer.
*
* @return An ASDisplayNode instance whose view will be a subclass that enables asynchronous rendering, and passes
* through -layout and touch handling methods.
*/
- (id)init;
/**
* @abstract Alternative initializer with a view class.
*
* @param viewClass Any UIView subclass, such as UIScrollView.
*
* @return An ASDisplayNode instance whose view will be of class viewClass.
*
* @discussion If viewClass is not a subclass of _ASDisplayView, it will still render synchronously and -layout and
* touch handling methods on the node will not be called.
* The view instance will be created with alloc/init.
*/
- (id)initWithViewClass:(Class)viewClass;
/**
* @abstract Alternative initializer with a layer class.
*
* @param layerClass Any CALayer subclass, such as CATransformLayer.
*
* @return An ASDisplayNode instance whose layer will be of class layerClass.
*
* @discussion If layerClass is not a subclass of _ASDisplayLayer, it will still render synchronously and -layout on the
* node will not be called.
* The layer instance will be created with alloc/init.
*/
- (id)initWithLayerClass:(Class)layerClass;
/** @name Properties */
/**
* @abstract Returns whether the view is synchronous.
*
* @return NO if the node wraps a _ASDisplayView, YES otherwise.
*/
@property (nonatomic, readonly, assign, getter=isSynchronous) BOOL synchronous;
/** @name Getting view and layer */
/**
* @abstract Returns a view.
*
* @discussion The view property is lazily initialized, similar to UIViewController.
* To go the other direction, use ASViewToDisplayNode() in ASDisplayNodeExtras.h.
*
* @warning The first access to it must be on the main thread, and should only be used on the main thread thereafter as
* well.
*/
@property (nonatomic, readonly, retain) UIView *view;
/**
* @abstract Returns whether a view is loaded.
*
* @return YES if a view is loaded, or if isLayerBacked is YES and layer is not nil; NO otherwise.
*/
@property (atomic, readonly, assign, getter=isViewLoaded) BOOL viewLoaded; //TODO Rename to isBackingLoaded?
/**
* @abstract Returns whether the node rely on a layer instead of a view.
*
* @return YES if the node rely on a layer, NO otherwise.
*/
@property (nonatomic, assign, getter=isLayerBacked) BOOL layerBacked;
/**
* @abstract Returns a layer.
*
* @discussion The layer property is lazily initialized, similar to the view property.
* To go the other direction, use ASLayerToDisplayNode() in ASDisplayNodeExtras.h.
*
* @warning The first access to it must be on the main thread, and should only be used on the main thread thereafter as
* well.
*/
@property (nonatomic, readonly, retain) CALayer *layer;
/** @name Managing dimensions */
/**
* @abstract Asks the node to calculate and return the size that best fits its subnodes.
*
* @param size The current size of the receiver.
*
* @return A new size that fits the receiver's subviews.
*
* @discussion Though this method does not set the bounds of the view, it does have side effects--caching both the
* constraint and the result.
*
* @warning Subclasses must not override this; it caches results from -calculateSizeThatFits:. Calling this method may
* be expensive if result is not cached.
*
* @see calculateSizeThatFits:
*/
- (CGSize)sizeToFit:(CGSize)constrainedSize; //TODO UIView names it sizeThatFits ("that" instead of "to")
/**
* @abstract Return the calculated size.
*
* @discussion Ideal for use by subclasses in -layout, having already prompted their subnodes to calculate their size by
* calling -sizeToFit: on them in -calculateSizeThatFits:.
*
* @return Size already calculated by calculateSizeThatFits:.
*
* @warning Subclasses must not override this; it returns the last cached size calculated and is never expensive.
*/
@property (nonatomic, readonly, assign) CGSize calculatedSize;
/**
* @abstract Return the constrained size used for calculating size.
*
* @return The constrained size used by calculateSizeThatFits:.
*/
@property (nonatomic, readonly, assign) CGSize constrainedSizeForCalulatedSize;
/** @name Managing the nodes hierarchy */
/**
* @abstract Add a node as a subnode to this node.
*
* @param subnode The node to be added.
*
* @discussion The subnode's view will automatically be added to this node's view, lazily if the views are not created
* yet.
*/
- (void)addSubnode:(ASDisplayNode *)subnode;
/**
* @abstract Insert a subnode before a given subnode in the list.
*
* @param subnode The node to insert below another node.
* @param below The sibling node that will be above the inserted node.
*
* @discussion If the views are loaded, the subnode's view will be inserted below the given node's view in the hierarchy
* even if there are other non-displaynode views.
*/
- (void)insertSubnode:(ASDisplayNode *)subnode belowSubnode:(ASDisplayNode *)below;
/**
* @abstract Insert a subnode after a given subnode in the list.
*
* @param subnode The node to insert below another node.
* @param above The sibling node that will be behind the inserted node.
*
* @discussion If the views are loaded, the subnode's view will be inserted above the given node's view in the hierarchy
* even if there are other non-displaynode views.
*/
- (void)insertSubnode:(ASDisplayNode *)subnode aboveSubnode:(ASDisplayNode *)above;
/**
* @abstract Insert a subnode at a given index in subnodes.
*
* @param subnode The node to insert.
* @param idx The index in the array of the subnodes property at which to insert the node. Subnodes indices start at 0
* and cannot be greater than the number of subnodes.
*
* @discussion If this node's view is loaded, ASDisplayNode insert the subnode's view after the subnode at index - 1's
* view even if there are other non-displaynode views.
*/
- (void)insertSubnode:(ASDisplayNode *)subnode atIndex:(NSInteger)idx;
/**
* @abstract Replace subnode with replacementSubnode.
*
* @param subnode A subnode of self.
* @param replacementSubnode A node with which to replace subnode.
*
* @discussion Should both subnode and replacementSubnode already be subnodes of self, subnode is removed and
* replacementSubnode inserted in its place.
* If subnode is not a subnode of self, this method will throw an exception.
* If replacementSubnode is nil, this method will throw an exception
*/
- (void)replaceSubnode:(ASDisplayNode *)subnode withSubnode:(ASDisplayNode *)replacementSubnode;
/**
* @abstract Add a subnode, but have it size asynchronously on a background queue.
*
* @param subnode The unsized subnode to insert into the view hierarchy
* @param completion The completion callback will be called on the main queue after the subnode has been inserted in
* place of the placeholder.
*
* @return A placeholder node is inserted into the hierarchy where the node will be. The placeholder can be moved around
* in the hiercharchy while the view is sizing. Once sizing is complete on the background queue, this placeholder will
* be removed and the replacement will be put at its place.
*/
- (ASDisplayNode *)addSubnodeAsynchronously:(ASDisplayNode *)subnode
completion:(void(^)(ASDisplayNode *replacement))completion;
/**
* @abstract Replace a subnode, but have it size asynchronously on a background queue.
*
* @param subnode A subnode of self.
* @param replacementSubnode A node with which to replace subnode.
* @param completion The completion callback will be called on the main queue after the replacementSubnode has replaced
* subnode.
*/
- (void)replaceSubnodeAsynchronously:(ASDisplayNode *)subnode
withNode:(ASDisplayNode *)replacementSubnode
completion:(void(^)(BOOL cancelled, ASDisplayNode *replacement, ASDisplayNode *oldSubnode))completion;
/**
* @abstract Remove this node from its supernode.
*
* @discussion The node's view will be automatically removed from the supernode's view.
*/
- (void)removeFromSupernode;
/**
* @abstract The receiver's immediate subnodes.
*/
@property (nonatomic, readonly, retain) NSArray *subnodes;
/**
* @abstract The receiver's supernode.
*/
@property (nonatomic, readonly, assign) ASDisplayNode *supernode;
/** @name Observing node-related changes */
// TODO rename these to the UIView selectors, willMoveToSuperview etc
// Called just before the view is added to a superview.
- (void)willAppear;
// Called after the view is removed from the window
- (void)willDisappear;
// Called after the view is removed from the window
- (void)didDisappear;
/** @name Drawing and Updating the View */
/**
* @abstract Whether this node's view performs asynchronous rendering.
*
* @return Defaults to YES, except for synchronous views (ie, those created with -initWithViewClass: /
* -initWithLayerClass:), which are always NO.
*
* @discussion If this flag is set, then the node will participate in the current asyncdisplaykit_async_transaction and
* do its rendering on the displayQueue instead of the main thread.
*
* Asynchronous rendering proceeds as follows:
*
* When the view is initially added to the hierarchy, it has -needsDisplay true.
* After layout, Core Animation will call -display on the _ASDisplayLayer
* -display enqueues a rendering operation on the displayQueue
* When the render block executes, it calls the delegate display method (-drawRect:... or -display)
* The delegate provides contents via this method and an operation is added to the asyncdisplaykit_async_transaction
* Once all rendering is complete for the current asyncdisplaykit_async_transaction,
* the completion for the block sets the contents on all of the layers in the same frame
*
* If asynchronous rendering is disabled:
*
* When the view is initially added to the hierarchy, it has -needsDisplay true.
* After layout, Core Animation will call -display on the _ASDisplayLayer
* -display calls delegate display method (-drawRect:... or -display) immediately
* -display sets the layer contents immediately with the result
*
* Note: this has nothing to do with -[CALayer drawsAsynchronously].
*/
@property (nonatomic, assign) BOOL displaysAsynchronously;
/**
* @abstract Whether to draw all descendant nodes' layers/views into this node's layer/view's backing store.
*
* @discussion
* When set to YES, causes all descendant nodes' layers/views to be drawn directly into this node's layer/view's backing
* store. Defaults to NO.
*
* If a node's descendants are static (never animated or never change attributes after creation) then that node is a
* good candidate for rasterization. Rasterizing descendants has two main benefits:
* 1) Backing stores for descendant layers are not created. Instead the layers are drawn directly into the rasterized
* container. This can save a great deal of memory.
* 2) Since the entire subtree is drawn into one backing store, compositing and blending are eliminated in that subtree
* which can help improve animation/scrolling/etc performance.
*
* Rasterization does not currently support descendants with transform, sublayerTransform, or alpha. Those properties
* will be ignored when rasterizing descendants.
*
* Note: this has nothing to do with -[CALayer shouldRasterize], which doesn't work with ASDisplayNode's asynchronous
* rendering model.
*/
@property (nonatomic, assign) BOOL shouldRasterizeDescendants;
/**
* @abstract Display the node's view/layer immediately on the current thread, bypassing the background thread rendering.
*/
- (void)displayImmediately;
/**
* @abstract Prevent the node's layer from displaying.
*
* @discussion A subclass may check this flag during -display or -drawInContext: to cancel a display that is already in
* progress.
*
* Defaults to NO. Does not control display for any child or descendant nodes; for that, use
* -recursiveSetPreventOrCancelDisplay:.
*
* If a setNeedsDisplay occurs while preventOrCancelDisplay is YES, and preventOrCancelDisplay is set to NO, then the
* layer will be automatically displayed.
*
* @see displayWasCancelled
*/
@property (nonatomic, assign) BOOL preventOrCancelDisplay;
/**
* @abstract Prevent the node and its descendants' layer from displaying.
*
* @see preventOrCancelDisplay
*/
- (void)recursiveSetPreventOrCancelDisplay:(BOOL)flag;
/** @name Hit Testing */
/**
* @abstract Bounds insets for hit testing.
*
* @discussion When set to a non-zero inset, increases the bounds for hit testing to make it easier to tap or perform
* gestures on this node. Default is UIEdgeInsetsZero.
*
* This affects the default implementation of -hitTest and -pointInside, so subclasses should call super if you override
* it and want hitTestSlop applied.
*/
@property (nonatomic, assign) UIEdgeInsets hitTestSlop;
/**
* @abstract Returns a Boolean value indicating whether the receiver contains the specified point.
*
* @discussion Includes the "slop" factor specified with hitTestSlop.
*
* @param point A point that is in the receiver's local coordinate system (bounds).
* @param event The event that warranted a call to this method.
*
* @return YES if point is inside the receiver's bounds; otherwise, NO.
*/
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
/** @name Converting Between View Coordinate Systems */
/**
* @abstract Converts a point from the receiver's coordinate system to that of the specified node.
*
* @param point A point specified in the local coordinate system (bounds) of the receiver.
* @param node The node into whose coordinate system point is to be converted.
*
* @return The point converted to the coordinate system of node.
*/
- (CGPoint)convertPoint:(CGPoint)point toNode:(ASDisplayNode *)node;
/**
* @abstract Converts a point from the coordinate system of a given node to that of the receiver.
*
* @param point A point specified in the local coordinate system (bounds) of node.
* @param node The node with point in its coordinate system.
*
* @return The point converted to the local coordinate system (bounds) of the receiver.
*/
- (CGPoint)convertPoint:(CGPoint)point fromNode:(ASDisplayNode *)node;
/**
* @abstract Converts a rectangle from the receiver's coordinate system to that of another view.
*
* @param rect A rectangle specified in the local coordinate system (bounds) of the receiver.
* @param node The node that is the target of the conversion operation.
*
* @return The converted rectangle.
*/
- (CGRect)convertRect:(CGRect)rect toNode:(ASDisplayNode *)node;
/**
* @abstract Converts a rectangle from the coordinate system of another node to that of the receiver.
*
* @param rect A rectangle specified in the local coordinate system (bounds) of node.
* @param node The node with rect in its coordinate system.
*
* @return The converted rectangle.
*/
- (CGRect)convertRect:(CGRect)rect fromNode:(ASDisplayNode *)node;
@end
@interface ASDisplayNode (Debugging)
/**
* @abstract Return a description of the node hierarchy
*
* @discussion For debugging: (lldb) po [node displayNodeRecursiveDescription]
*/
- (NSString *)displayNodeRecursiveDescription;
@end
/**
* ## UIView bridge
*
* ASDisplayNode provides thread-safe access to most of UIView and CALayer properties and methods, traditionally unsafe.
*
* Using them will not cause the actual view/layer to be created, and will be applied when it is created (when the view
* or layer property is accessed).
*
* After the view is created, the properties pass through to the view directly as if called on the main thread.
*
* @see UIView and CALayer for documentation on these common properties.
*/
@interface ASDisplayNode (UIViewBridge)
- (void)setNeedsDisplay; // Marks the view as needing display. Convenience for use whether view is created or not, or from a background thread.
- (void)setNeedsLayout; // Marks the view as needing layout. Convenience for use whether view is created or not, or from a background thread.
@property (atomic, retain) id contents; // default=nil
@property (atomic, assign) BOOL clipsToBounds; // default==NO
@property (atomic, getter=isOpaque) BOOL opaque; // default==YES
@property (atomic, assign) BOOL allowsEdgeAntialiasing;
@property (atomic, assign) unsigned int edgeAntialiasingMask; // default==all values from CAEdgeAntialiasingMask
@property (atomic, getter=isHidden) BOOL hidden; // default==NO
@property (atomic, assign) BOOL needsDisplayOnBoundsChange; // default==NO
@property (atomic, assign) BOOL autoresizesSubviews; // default==YES (undefined for layer-backed nodes)
@property (atomic, assign) UIViewAutoresizing autoresizingMask; // default==UIViewAutoresizingNone (undefined for layer-backed nodes)
@property (atomic, assign) CGFloat alpha; // default=1.0f
@property (atomic, assign) CGRect bounds; // default=CGRectZero
@property (atomic, assign) CGRect frame; // default=CGRectZero
@property (atomic, assign) CGPoint anchorPoint; // default={0.5, 0.5}
@property (atomic, assign) CGFloat zPosition; // default=0.0
@property (atomic, assign) CGPoint position; // default=CGPointZero
@property (atomic, assign) CGFloat contentsScale; // default=1.0f. See @contentsScaleForDisplay for more info
@property (atomic, assign) CATransform3D transform; // default=CATransform3DIdentity
@property (atomic, assign) CATransform3D subnodeTransform; // default=CATransform3DIdentity
@property (atomic, copy) NSString *name; // default=nil. Use this to tag your layers in the server-recurse-description / pca or for your own purposes
/**
* @abstract The node view's background color.
*
* @discussion In contrast to UIView, setting a transparent color will not set opaque = NO.
* This only affects nodes that implement +drawRect like ASTextNode.
*/
@property (atomic, retain) UIColor *backgroundColor; // default=nil
/**
* @abstract A flag used to determine how a node lays out its content when its bounds change.
*
* @discussion This is like UIView's contentMode property, but better. We do our own mapping to layer.contentsGravity in
* _ASDisplayView. You can set needsDisplayOnBoundsChange independently.
* Thus, UIViewContentModeRedraw is not allowed; use needsDisplayOnBoundsChange = YES instead, and pick an appropriate
* contentMode for your content while it's being re-rendered.
*/
@property (atomic, assign) UIViewContentMode contentMode; // default=UIViewContentModeScaleToFill
@property (atomic, assign, getter=isUserInteractionEnabled) BOOL userInteractionEnabled; // default=YES (NO for layer-backed nodes)
@property (atomic, assign, getter=isExclusiveTouch) BOOL exclusiveTouch; // default=NO
@property (atomic, assign) CGColorRef shadowColor; // default=opaque rgb black
@property (atomic, assign) CGFloat shadowOpacity; // default=0.0
@property (atomic, assign) CGSize shadowOffset; // default=(0, -3)
@property (atomic, assign) CGFloat shadowRadius; // default=3
@property (atomic, assign) CGFloat borderWidth; // default=0
@property (atomic, assign) CGColorRef borderColor; // default=opaque rgb black
// Accessibility support
@property (atomic, assign) BOOL isAccessibilityElement;
@property (atomic, copy) NSString *accessibilityLabel;
@property (atomic, copy) NSString *accessibilityHint;
@property (atomic, copy) NSString *accessibilityValue;
@property (atomic, assign) UIAccessibilityTraits accessibilityTraits;
@property (atomic, assign) CGRect accessibilityFrame;
@property (atomic, retain) NSString *accessibilityLanguage;
@property (atomic, assign) BOOL accessibilityElementsHidden;
@property (atomic, assign) BOOL accessibilityViewIsModal;
@property (atomic, assign) BOOL shouldGroupAccessibilityChildren;
@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