mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-16 10:30:08 +00:00
541 lines
22 KiB
Objective-C
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
|