mirror of
https://github.com/HackPlan/AsyncDisplayKit.git
synced 2026-03-30 16:53:29 +08:00
[AsyncDisplayKit.org] add Advanced Technologies, cleanup IHM, Layout Transition API
This commit is contained in:
@@ -34,13 +34,15 @@
|
||||
- title: Layout
|
||||
items:
|
||||
- automatic-layout-containers
|
||||
- layout-api-sizing
|
||||
- title: Advanced Layout
|
||||
items:
|
||||
- layout-api-sizing
|
||||
- layout-transition-api
|
||||
- title: Conveniences
|
||||
items:
|
||||
- hit-test-slop
|
||||
- batch-fetching-api
|
||||
- implicit-hierarchy-mgmt
|
||||
- image-modification-block
|
||||
- placeholder-fade-duration
|
||||
- title: Optimizations
|
||||
@@ -52,13 +54,11 @@
|
||||
- title: Tools
|
||||
items:
|
||||
- debug-tool-hit-test-visualization
|
||||
- debug-tool-pixel-scaling
|
||||
- title: Beta
|
||||
items:
|
||||
- implicit-hierarchy-mgmt
|
||||
- layout-transition-api
|
||||
- debug-tool-pixel-scaling
|
||||
- debug-tool-ASRangeController
|
||||
- title: Advanced Technologies
|
||||
items:
|
||||
- asenvironment
|
||||
- asrunloopqueue
|
||||
|
||||
|
||||
15
_docs/asenvironment.md
Executable file
15
_docs/asenvironment.md
Executable file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: ASEnvironment
|
||||
layout: docs
|
||||
permalink: /docs/asenvironment.html
|
||||
prevPage: debug-tool-ASRangeController.html
|
||||
nextPage: asrunloopqueue.html
|
||||
---
|
||||
|
||||
`ASEnvironment` allows objects that conform to the `<ASEnvironment>` protocol to be able to propagate specific states defined in an ASEnvironmentState up and/or down the ASEnvironment tree. To define how merges of States should happen, specific merge functions can be provided.
|
||||
|
||||
One of AsyncDisplayKit's built in propogation states is the `ASEnvironmentTraitCollection`. This allows nodes to propagate information about the UIDevice down the ASEnvironment tree, to their subnodes. This performance optimization means that we only have to check our UIDevice settings once.
|
||||
|
||||
### Addding your own ASEnvironmentState
|
||||
|
||||
Coming Soon...
|
||||
@@ -2,7 +2,7 @@
|
||||
title: ASRunLoopQueue
|
||||
layout: docs
|
||||
permalink: /docs/asrunloopqueue.html
|
||||
prevPage: layout-transition-api.html
|
||||
prevPage: asenvironment.html
|
||||
---
|
||||
|
||||
Even with main thread work, AsyncDisplayKit is able to dramatically reduce its impact on the user experience by way of the rather amazing ASRunLoopQueue.
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
title: LayoutSpecs
|
||||
layout: docs
|
||||
permalink: /docs/automatic-layout-containers.html
|
||||
prevPage: automatic-layout-basics.html
|
||||
nextPage: automatic-layout-examples.html
|
||||
prevPage: scroll-node.html
|
||||
nextPage: layout-api-sizing.html
|
||||
---
|
||||
|
||||
AsyncDisplayKit includes a library of `layoutSpec` components that can be composed to declaratively specify a layout. The **child(ren) of a layoutSpec may be a node, a layoutSpec or a combination of the two types.** In the below image, an ASStackLayoutSpec (vertical) containing a text node and an image node, is wrapped in another ASStackLayoutSpec (horizontal) with another text node.
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Batch Fetching API
|
||||
layout: docs
|
||||
permalink: /docs/batch-fetching-api.html
|
||||
prevPage: hit-test-slop.html
|
||||
nextPage: image-modification-block.html
|
||||
nextPage: implicit-hierarchy-mgmt.html
|
||||
---
|
||||
|
||||
AsyncDisplayKit's Batch Fetching API makes it easy for developers to add fetching of new data in chunks. In case the user scrolled to a specific range of a table or collection view the automatic batch fetching mechanism of ASDK kicks in.
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
title: Corner Rounding
|
||||
layout: docs
|
||||
permalink: /docs/corner-rounding.html
|
||||
prevPage: drawing-priority.html
|
||||
nextPage: hit-test-slop.html
|
||||
prevPage: synchronous-concurrency.html
|
||||
nextPage: debug-tools-hit-test-visualization.html
|
||||
---
|
||||
|
||||
When it comes to corner rounding, many developers stick with CALayer's `.cornerRadius` property. Unfortunately, this convenient property greatly taxes performance and should only be used when there is _no_ alternative. This post will cover:
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
title: Range Visualization
|
||||
layout: docs
|
||||
permalink: /docs/debug-tool-ASRangeController.html
|
||||
prevPage: debug-tool-pixel-scaling.html
|
||||
nextPage: asenvironment.html
|
||||
---
|
||||
##Visualize ASRangeController tuning parameters (PR #1390)
|
||||
###Description
|
||||
|
||||
##Visualize ASRangeController tuning parameters <a href="https://github.com/facebook/AsyncDisplayKit/pull/1390">(PR #1390)</a>
|
||||
<br>
|
||||
This debug feature adds a semi-transparent subview in the bottom right hand corner of the sharedApplication keyWindow that visualizes the ASRangeTuningParameters per each ASLayoutRangeType for each visible (on-screen) instance of ASRangeController.
|
||||
|
||||
- The instances of ASRangeController are represented as bars
|
||||
@@ -15,24 +18,25 @@ This debug feature adds a semi-transparent subview in the bottom right hand corn
|
||||
|
||||
This debug feature is useful for highly optimized ASDK apps that require tuning of any ASRangeController. Or for anyone who is curious about how ASRangeControllers work.
|
||||
|
||||
The examples/VerticalWithinHorizontal app picture below contains an ASPagerNode with embedded ASTableViews. In the screenshot with this feature enabled, you can see the two range controllers - ASTableView and ASCollectionView (ASPagerNode) - in the overlay.
|
||||
The <a href="https://github.com/facebook/AsyncDisplayKit/tree/master/examples/VerticalWithinHorizontalScrolling">VerticalWithinHorizontal example app</a> contains an ASPagerNode with embedded ASTableViews. In the screenshot with this feature enabled, you can see the two range controllers - ASTableView and ASCollectionView (ASPagerNode) - in the overlay.
|
||||
|
||||
- The white arrows to the right of the rangeController bars indicate that the user is currently scrolling down through the table and right through the ASCollectionView/PagerNode.
|
||||
- The ASTableView rangeController bar indicates that the range parameters are tuned to both fetch and decode more data in the downward table direction rather than in the reverse direction (which makes sense as the user is scrolling down).
|
||||
- Since it’s less obvious whether or not the user will page to the right or left next, the ASCollectionView is tuned to fetch and decode equal amounts of data in each direction.
|
||||
- In the video, you can see as the user scrolls between pages, that new ASTableView rangeControllers are created and removed in the overlay view.
|
||||
- In the <a href="https://drive.google.com/file/d/0B1BArZ05bNhzVy1jSW9FeEVXUjg/view">video demo</a>, you can see as the user scrolls between pages, that new ASTableView rangeControllers are created and removed in the overlay view.
|
||||

|
||||
|
||||
### Video Demo
|
||||
https://drive.google.com/file/d/0B1BArZ05bNhzVy1jSW9FeEVXUjg/view
|
||||
## Limitations
|
||||
<ul>
|
||||
<li>only shows onscreen ASRangeControllers</li>
|
||||
<li>currently the ratio of red (fetch data), yellow (display) and green (visible) are relative to each other, but not between each bar view. So you cannot compare individual bars to eachother</li>
|
||||
</ul>
|
||||
|
||||
### Usage
|
||||
In your AppDelegate, (1) import `AsyncDisplayKit+Debug.h` and (2) at the top of `didFinishLaunchingWithOptions:` enable this feature by adding `[ASRangeController setShouldShowRangeDebugOverlay:YES];` Make sure to call this method before initializing any component that uses an ASRangeControllers (ASTableView, ASCollectionView).
|
||||
### Limitations
|
||||
- code performance is iffy
|
||||
- only shows onscreen ASRangeControllers
|
||||
- currently the ratio of red (fetch data), yellow (display) and green (visible) are relative to each other, but not between each bar view. So you cannot compare individual bars to eachother
|
||||
- configure the debug label to display more useful information - perhaps the actual values of the tuning parameters?
|
||||
- rather than displaying the ideal tuning parameters, show the achieved ones
|
||||
- rather than showing the tuning parameters in a bar representation, show the actual nodes in each state
|
||||
## Usage
|
||||
In your `AppDelegate.m` file,
|
||||
<ul>
|
||||
<li>import `AsyncDisplayKit+Debug.h`</li>
|
||||
<li>add `[ASRangeController setShouldShowRangeDebugOverlay:YES]` at the top of your AppDelegate's '`didFinishLaunchingWithOptions:` method</li>
|
||||
</ul>
|
||||
|
||||
## ASLayoutSpecPlayground...coming soon!
|
||||
**Make sure to call this method before initializing any component that uses an ASRangeControllers (ASTableView, ASCollectionView).**
|
||||
@@ -2,11 +2,12 @@
|
||||
title: Hit Test Visualization
|
||||
layout: docs
|
||||
permalink: /docs/debug-tool-hit-test-visualization.html
|
||||
prevPage: synchronous-concurrency.html
|
||||
prevPage: corner-rounding.html
|
||||
nextPage: debug-tool-pixel-scaling.html
|
||||
---
|
||||
|
||||
##Visualize ASControlNode Tappable Areas##
|
||||
<br>
|
||||
This debug feature adds a semi-transparent highlight overlay on any ASControlNodes containing a `target:action:` pair or gesture recognizer. The tappable range is defined as the ASControlNode’s frame + its `.hitTestSlop` `UIEdgeInsets`. Hit test slop is a unique feature of `ASControlNode` that allows it to extend its tappable range.
|
||||
|
||||
In the screenshot below, you can quickly see that
|
||||
@@ -19,13 +20,14 @@ In the screenshot below, you can quickly see that
|
||||

|
||||
|
||||
##Restrictions##
|
||||
|
||||
<br>
|
||||
A _green_ border on the edge(s) of the highlight overlay indicates that that edge of the tapable area is restricted by one of it's superview's tapable areas. An _orange_ border on the edge(s) of the highlight overlay indicates that that edge of the tapable area is clipped by .clipsToBounds of a parent in its hierarchy.
|
||||
|
||||
##Usage##
|
||||
<br>
|
||||
In your `AppDelegate.m` file,
|
||||
<ul>
|
||||
<li>import `AsyncDisplayKit+Debug.h`</li>
|
||||
<li>add `[ASControlNode setEnableHitTestDebug:YES]` at the top of your `didFinishLaunchingWithOptions:` method</li>
|
||||
<li>add `[ASControlNode setEnableHitTestDebug:YES]` at the top of your AppDelegate's `didFinishLaunchingWithOptions:` method</li>
|
||||
</ul>
|
||||
Make sure to call this method before initializing any ASControlNodes - including ASButtonNodes, ASImageNodes, and ASTextNodes.
|
||||
|
||||
@@ -3,10 +3,11 @@ title: Image Scaling
|
||||
layout: docs
|
||||
permalink: /docs/debug-tool-pixel-scaling.html
|
||||
prevPage: debug-tool-hit-test-visualization.html
|
||||
nextPage: implicit-hierarchy-mgmt.html
|
||||
nextPage: debug-tool-ASRangeController.html
|
||||
---
|
||||
|
||||
##Visualize ASImageNode.image’s pixel scaling##
|
||||
<br>
|
||||
This debug feature adds a red text overlay on the bottom right hand corner of an ASImageNode if (and only if) the image’s size in pixels does not match it’s bounds size in pixels, e.g.
|
||||
|
||||
```objective-c
|
||||
@@ -20,19 +21,23 @@ if (scaleFactor != 1.0) {
|
||||
}
|
||||
```
|
||||
|
||||
**This debug feature is useful for quickly determining if you are**
|
||||
<b>This debug feature is useful for quickly determining if you are</b>
|
||||
|
||||
<ul>
|
||||
<li><strong>downloading and rendering excessive amounts of image data</li>
|
||||
<li>upscaling a low quality image</strong></li>
|
||||
</ul>
|
||||
|
||||
In the screenshot below of an app with this debug feature enabled, you can see that the avatar image is unnecessarily large (9x too large) for it’s bounds size and that the center picture is more optimized, but not perfectly so. If you control your own endpoint, optimize your API / app to return an optimally sized image.
|
||||
In the screenshot below of an app with this debug feature enabled, you can see that the avatar image is unnecessarily large (9x too large) for it’s bounds size and that the center picture is more optimized, but not perfectly so. If you control your own endpoint, make sure to return an optimally sized image.
|
||||
|
||||

|
||||
##Usage##
|
||||
|
||||
## Usage ##
|
||||
<br>
|
||||
In your `AppDelegate.m` file,
|
||||
<ul>
|
||||
<li>import `AsyncDisplayKit+Debug.h`</li>
|
||||
<li>add `[ASImageNode setShouldShowImageScalingOverlay:YES]` at the top of your `didFinishLaunchingWithOptions:` method</li>
|
||||
<li>add `[ASImageNode setShouldShowImageScalingOverlay:YES]` at the top of your AppDelegate's `didFinishLaunchingWithOptions:` method</li>
|
||||
</ul>
|
||||
Make sure to call this method before initializing any ASImageNodes.
|
||||
|
||||
**Make sure to call this method before initializing any ASImageNodes.**
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: Hit Test Slop
|
||||
layout: docs
|
||||
permalink: /docs/hit-test-slop.html
|
||||
prevPage: layoutSpecs.html
|
||||
prevPage: layout-transition-api.html
|
||||
nextPage: batch-fetching-api.html
|
||||
---
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: Image Modification Blocks
|
||||
layout: docs
|
||||
permalink: /docs/image-modification-block.html
|
||||
prevPage: batch-fetching-api.html
|
||||
prevPage: implicit-hierarchy-mgmt.html
|
||||
nextPage: placeholder-fade-duration.html
|
||||
---
|
||||
|
||||
|
||||
@@ -1,98 +1,142 @@
|
||||
---
|
||||
title: Implicit Hierarchy Management
|
||||
title: Implicit Hierarchy Management <b><i>(Beta)</i></b>
|
||||
layout: docs
|
||||
permalink: /docs/implicit-hierarchy-mgmt.html
|
||||
prevPage: debug-tool-pixel-scaling.html
|
||||
nextPage: debug-hit-test.html
|
||||
prevPage: batch-fetching-api.html
|
||||
nextPage: image-modification-block.html
|
||||
---
|
||||
|
||||
This feature was initially a result of building the AsyncDisplayKit Layout Transition API. However, even apps using AsyncDisplayKit that don't require animations can still benefit from the reduction in code size that this feature enables.
|
||||
Enabling Implicit Hierarchy Management (IHM) is required to use the <a href="layout-transition-api.html">Layout Transition API</a>. However, apps that don't require animations can still benefit from the reduction in code size that this feature enables.
|
||||
|
||||
**This feature will soon be enabled by default for all ASDisplayNodes using ASLayouts.**
|
||||
When enabled, IHM means that your nodes no longer require `-addSubnode:` or `-removeFromSupernode` method calls. The presence or absence of the IHM node _and_ its subnodes is completely determined in its `-layoutSpecThatFits:` method.
|
||||
|
||||
### Enabling IHM ###
|
||||
<br>
|
||||
- import `"ASDisplayNode+Beta.h"`. <b><i>This feature will soon be enabled by default for all ASDisplayNodes using ASLayouts.</b></i>
|
||||
- set `.usesImplicitHierarchyManagement = YES` on the node that you would like managed.
|
||||
|
||||
<div class = "note">
|
||||
Implicit Hierarchy Management is implemented using ASLayoutSpecs. If you are unfamiliar with that concept, please read that documentation (INSERT LINK) first. <br><br>
|
||||
To recap, an ASLayoutSpec completely describes the UI of a view in your app by specifying the **hierarchy state of a node and its subnodes**. An ASLayoutSpec is returned by a node from its <br><br>
|
||||
`- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize` <br><br>
|
||||
method.
|
||||
Note that the subnodes of any node with this property set will inherit IHM, so it is only neccessary to enable IHM on the highest level node. Generally, this will be the highest level node in your app, most likely an ASTableNode, ASCollectionNode or ASPagerNode.
|
||||
</div>
|
||||
|
||||
When enabled, IHM, means that your nodes no longer require `addSubnode:` or `removeFromSupernode` method calls. The presence or absence of the IHM node _and_ its subnodes are determined in the layoutSpecThatFits: method.
|
||||
### Example ###
|
||||
<br>
|
||||
Consider the following intialization method from the PhotoCellNode class in <a href="https://github.com/facebook/AsyncDisplayKit/tree/master/examples/ASDKgram">ASDKgram sample app</a>. This ASCellNode subclass produces a simple social media photo feed cell.
|
||||
|
||||
**Note that the subnodes of any node with this property set will inherit IHM, so it is only neccessary to put it on the highest level node. Most likely that will be an ASTableNode, ASCollectionNode or ASPagerNode.**
|
||||
In the "Original Code" we see the familiar `-addSubnode:` calls in bold. In the "Code with IHM" (switch at top right of code block) these have been removed and replaced with a single line that enables IHM.
|
||||
|
||||
####Example####
|
||||
Consider the following `ASCellNode` subclass `PhotoCellNode` from the <a href="https://github.com/facebook/AsyncDisplayKit/tree/master/examples/ASDKgram">ASDKgram sample app</a> which produces a simple social media photo feed UI.
|
||||
By setting usesImplicitHierarchyManagement to YES on the ASCellNode, we _no longer_ need to call `-addSubnode:` for each of the ASCellNode's subnodes. These subNodes will be present in the node hierarchy as long as this class' `-layoutSpecThatFits:` method includes them.
|
||||
|
||||
```objective-c
|
||||
<div class = "highlight-group">
|
||||
<span class="language-toggle">
|
||||
<a data-lang="swift" class="swiftButton">Code with IHM</a>
|
||||
<a data-lang="objective-c" class = "active objcButton">Original Code</a>
|
||||
</span>
|
||||
<div class = "code">
|
||||
<pre lang="objc" class="objcCode">
|
||||
- (instancetype)initWithPhotoObject:(PhotoModel *)photo;
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
|
||||
self.usesImplicitHierarchyManagement = YES;
|
||||
_photoModel = photo;
|
||||
|
||||
_photoModel = photo;
|
||||
|
||||
_userAvatarImageView = [[ASNetworkImageNode alloc] init];
|
||||
_userAvatarImageView.URL = photo.ownerUserProfile.userPicURL;
|
||||
_userAvatarImageNode = [[ASNetworkImageNode alloc] init];
|
||||
_userAvatarImageNodeURL = photo.ownerUserProfile.userPicURL;
|
||||
<b>[self addSubnode:_userAvatarImageNode];</b>
|
||||
|
||||
_photoImageView = [[ASNetworkImageNode alloc] init];
|
||||
_photoImageView.URL = photo.URL;
|
||||
_photoImageNode = [[ASNetworkImageNode alloc] init];
|
||||
_photoImageNode.URL = photo.URL;
|
||||
<b>[self addSubnode:_photoImageNode];</b>
|
||||
|
||||
_userNameLabel = [[ASTextNode alloc] init];
|
||||
_userNameLabel.attributedString = [photo.ownerUserProfile usernameAttributedStringWithFontSize:FONT_SIZE];
|
||||
_userNameTextNode = [[ASTextNode alloc] init];
|
||||
_userNameTextNode.attributedString = [photo.ownerUserProfile usernameAttributedStringWithFontSize:FONT_SIZE];
|
||||
<b>[self addSubnode:_userNameTextNode];</b>
|
||||
|
||||
_photoLocationLabel = [[ASTextNode alloc] init];
|
||||
_photoLocationTextNode = [[ASTextNode alloc] init];
|
||||
[photo.location reverseGeocodedLocationWithCompletionBlock:^(LocationModel *locationModel) {
|
||||
if (locationModel == _photoModel.location) {
|
||||
_photoLocationLabel.attributedString = [photo locationAttributedStringWithFontSize:FONT_SIZE];
|
||||
_photoLocationTextNode.attributedString = [photo locationAttributedStringWithFontSize:FONT_SIZE];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}];
|
||||
|
||||
_photoCommentsView = [[CommentsNode alloc] init];
|
||||
<b>[self addSubnode:_photoLocationTextNode];</b>
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
</pre>
|
||||
|
||||
```
|
||||
|
||||
By setting usesImplicitHierarchyManagement to YES on the ASCellNode, we _no longer_ need to call `addSubnode:` for each of the ASCellNode's subnodes.
|
||||
|
||||
Several of the elements in this cell - `_userAvatarImageView`, `_photoImageView`, `_photoLocationLabel` and `_photoCommentsView` - depend on seperate data fetches from the network that could return at any time.
|
||||
|
||||
Implicit Hierarchy Management knows whether or not to include these elements in the UI based on information provided in the cell's ASLayoutSpec.
|
||||
|
||||
**It is your job to construct a `layoutSpecThatFits:` that handles how the UI should look with and without these elements.**
|
||||
|
||||
Consider the layoutSpecThatFits: method for the ASCellNode subclass
|
||||
|
||||
```objective-c
|
||||
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
|
||||
<pre lang="swift" class = "swiftCode hidden">
|
||||
- (instancetype)initWithPhotoObject:(PhotoModel *)photo;
|
||||
{
|
||||
// username / photo location header vertical stack
|
||||
_photoLocationLabel.flexShrink = YES;
|
||||
_userNameLabel.flexShrink = YES;
|
||||
self = [super init];
|
||||
|
||||
ASStackLayoutSpec *headerSubStack = [ASStackLayoutSpec verticalStackLayoutSpec];
|
||||
headerSubStack.flexShrink = YES;
|
||||
if (_photoLocationLabel.attributedString) {
|
||||
[headerSubStack setChildren:@[_userNameLabel, _photoLocationLabel]];
|
||||
} else {
|
||||
[headerSubStack setChildren:@[_userNameLabel]];
|
||||
if (self) {
|
||||
<b>self.usesImplicitHierarchyManagement = YES;</b>
|
||||
|
||||
_photoModel = photo;
|
||||
|
||||
_userAvatarImageNode = [[ASNetworkImageNode alloc] init];
|
||||
_userAvatarImageNodeURL = photo.ownerUserProfile.userPicURL;
|
||||
|
||||
_photoImageNode = [[ASNetworkImageNode alloc] init];
|
||||
_photoImageNode.URL = photo.URL;
|
||||
|
||||
_userNameTextNode = [[ASTextNode alloc] init];
|
||||
_userNameTextNode.attributedString = [photo.ownerUserProfile usernameAttributedStringWithFontSize:FONT_SIZE];
|
||||
|
||||
_photoLocationTextNode = [[ASTextNode alloc] init];
|
||||
[photo.location reverseGeocodedLocationWithCompletionBlock:^(LocationModel *locationModel) {
|
||||
if (locationModel == _photoModel.location) {
|
||||
_photoLocationTextNode.attributedString = [photo locationAttributedStringWithFontSize:FONT_SIZE];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
// header stack
|
||||
_userAvatarImageView.preferredFrameSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); // constrain avatar image frame size
|
||||
return self;
|
||||
}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Several of the elements in this cell - `_userAvatarImageNode`, `_photoImageNode`, and `_photoLocationLabel` depend on seperate data fetches from the network that could return at any time. When should they be added to the UI?
|
||||
|
||||
IHM knows whether or not to include these elements in the UI based on the information provided in the cell's ASLayoutSpec.
|
||||
|
||||
<div class = "note">
|
||||
An `ASLayoutSpec` completely describes the UI of a view in your app by specifying the hierarchy state of a node and its subnodes. An ASLayoutSpec is returned by a node from its <code>
|
||||
`-layoutSpecThatFits:`</code> method.
|
||||
</div>
|
||||
|
||||
**It is your job to construct a `-layoutSpecThatFits:` that handles how the UI should look with and without these elements.**
|
||||
|
||||
Consider the abreviated `-layoutSpecThatFits:` method for the ASCellNode subclass above.
|
||||
|
||||
<div class = "highlight-group">
|
||||
<span class="language-toggle">
|
||||
<a data-lang="objective-c" class = "active objcButton">Objective-C</a>
|
||||
</span>
|
||||
<div class = "code">
|
||||
<pre lang="objc" class="objcCode">
|
||||
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
ASStackLayoutSpec *headerSubStack = [ASStackLayoutSpec verticalStackLayoutSpec];
|
||||
headerSubStack.flexShrink = YES;
|
||||
<b>if (_photoLocationLabel.attributedString) {</b>
|
||||
[headerSubStack setChildren:@[_userNameLabel, _photoLocationLabel]];
|
||||
<b>} else {</b>
|
||||
[headerSubStack setChildren:@[_userNameLabel]];
|
||||
<b>}</b>
|
||||
|
||||
_userAvatarImageNode.preferredFrameSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); // constrain avatar image frame size
|
||||
|
||||
ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init];
|
||||
spacer.flexGrow = YES;
|
||||
|
||||
UIEdgeInsets avatarInsets = UIEdgeInsetsMake(HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER, HORIZONTAL_BUFFER);
|
||||
ASInsetLayoutSpec *avatarInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:avatarInsets child:_userAvatarImageView];
|
||||
ASInsetLayoutSpec *avatarInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:avatarInsets child:<b>_userAvatarImageNode</b>];
|
||||
|
||||
ASStackLayoutSpec *headerStack = [ASStackLayoutSpec horizontalStackLayoutSpec];
|
||||
headerStack.alignItems = ASStackLayoutAlignItemsCenter; // center items vertically in horizontal stack
|
||||
@@ -105,39 +149,35 @@ Consider the layoutSpecThatFits: method for the ASCellNode subclass
|
||||
|
||||
// footer inset stack
|
||||
UIEdgeInsets footerInsets = UIEdgeInsetsMake(VERTICAL_BUFFER, HORIZONTAL_BUFFER, VERTICAL_BUFFER, HORIZONTAL_BUFFER);
|
||||
ASInsetLayoutSpec *footerWithInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:footerInsets child:_photoCommentsView];
|
||||
ASInsetLayoutSpec *footerWithInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:footerInsets child:<b>_photoCommentsNode</b>];
|
||||
|
||||
// vertical stack
|
||||
CGFloat cellWidth = constrainedSize.max.width;
|
||||
_photoImageView.preferredFrameSize = CGSizeMake(cellWidth, cellWidth); // constrain photo frame size
|
||||
_photoImageNode.preferredFrameSize = CGSizeMake(cellWidth, cellWidth); // constrain photo frame size
|
||||
|
||||
ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec verticalStackLayoutSpec];
|
||||
verticalStack.alignItems = ASStackLayoutAlignItemsStretch; // stretch headerStack to fill horizontal space
|
||||
[verticalStack setChildren:@[headerWithInset, _photoImageView, footerWithInset]];
|
||||
[verticalStack setChildren:@[headerWithInset, <b>_photoImageNode</b>, footerWithInset]];
|
||||
|
||||
return verticalStack;
|
||||
}
|
||||
```
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Here you can see that I set the children of my `headerSubStack` to depending on wehther or not the `_photoLocationLabel` attributed string has returned from the reverseGeocode process yet.
|
||||
|
||||
The `_userAvatarImageView`, `_photoImageView`, and `_photoCommentsView` are added into the ASLayoutSpec, but will not show up until their data fetches return.
|
||||
Here you can see that the children of the `headerSubStack` depend on whether or not the `_photoLocationLabel` attributed string has returned from the reverseGeocode process yet.
|
||||
|
||||
####Updating an ASLayoutSpec####
|
||||
The `_userAvatarImageNode`, `_photoImageNode`, and `_photoCommentsNode` are added into the ASLayoutSpec, but will not show up until their data fetches return.
|
||||
|
||||
**If something happens that you know will change your `ASLayoutSpec`, it is your job to call `-setNeedsLayout`** (equivalent to `transitionLayout:duration:0` that will be mentioned in the Transition Layout API). As can be seen in the completion block of the photo.location reverseGeocodedLocationWithCompletionBlock: call above.
|
||||
### Updating an ASLayoutSpec ###
|
||||
<br>
|
||||
**If something happens that you know will change your `ASLayoutSpec`, it is your job to call `-setNeedsLayout`**. This is equivalent to `-transitionLayout:duration:0` in the Transition Layout API. You can see this call in the completion block of the `photo.location reverseGeocodedLocationWithCompletionBlock:` call in the first code block.
|
||||
|
||||
An appropriately constructed ASLayoutSpec will know which subnodes need to be added, removed or animated.
|
||||
|
||||
If you try out the ASDKgram sample app after looking at the code above, you can see how simple it is to code a cell node thats layout is responsive to numerous, individual data fetches and returns. While the ASLayoutSpec is coded in a way that leaves holes for the avatar and photo to populate, you can see how the cell's height will automatically adjust to accomodate the comments node at the bottom of the photo.
|
||||
Try out the <a href="https://github.com/facebook/AsyncDisplayKit/tree/master/examples/ASDKgram">ASDKgram sample app</a> after looking at the code above, and you will see how simple it is to code an `ASCellNode` whose layout is responsive to numerous, individual data fetches and returns. While the ASLayoutSpec is coded in a way that leaves holes for the avatar and photo to populate, you can see how the cell's height will automatically adjust to accomodate the comments node at the bottom of the photo.
|
||||
|
||||
This is just a simple example, but this feature has many more powerful uses.
|
||||
|
||||
####To Use####
|
||||
|
||||
- import `"ASDisplayNode+Beta.h"`
|
||||
- set the `.usesImplicitHierarchyManagement = YES` on the node that you would like managed.
|
||||
|
||||
**Note that the subnodes of any node with this property set will inherit the property, so it is only neccessary to put it on the highest level node. Most likely that will be an ASTableNode, ASCollectionNode or ASPagerNode.**
|
||||
|
||||
Please check it out and let us know what you think at <a href="https://github.com/facebook/AsyncDisplayKit/pull/1156">PR #1156</a>!
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
title: Layout API Sizing
|
||||
layout: docs
|
||||
permalink: /docs/layout-api-sizing.html
|
||||
prevPage: automatic-layout-containers.html
|
||||
nextPage: layout-transition-api.html
|
||||
---
|
||||
|
||||
The easiest way to understand the compound dimension types in the Layout API is to see all the units in relation to one another.
|
||||
|
||||
@@ -1,27 +1,39 @@
|
||||
---
|
||||
title: Layout Transition API
|
||||
title: Layout Transition API <b><i>(Beta)</i></b>
|
||||
layout: docs
|
||||
permalink: /docs/layout-transition-api.html
|
||||
prevPage: implicit-hierarchy-mgmt.html
|
||||
prevPage: batch-fetching-api.html
|
||||
nextPage: hit-test-slop.html
|
||||
---
|
||||
###Overview
|
||||
<a href="https://github.com/facebook/AsyncDisplayKit/pulls?utf8=%E2%9C%93&q=is%3Apr+author%3Alevi+">@levi</a> designed this API to make all animations with AsyncDisplayKit easy - even transforming an entire set of views into a completely different set of views!
|
||||
|
||||
With this system, you simply specify the desired layout and AsyncDisplayKit will do the work to figure out differences from the current layout. It will automatically and new elements, remove unneeded elements after the transiton and update the position of any existing elements.
|
||||
The Layout Transition API was designed to make all animations with AsyncDisplayKit easy - even transforming an entire set of views into a completely different set of views!
|
||||
|
||||
With this system, you simply specify the desired layout and AsyncDisplayKit will do the work to figure out differences from the current layout. It will automatically add new elements, remove unneeded elements after the transiton, and update the position of any existing elements.
|
||||
|
||||
There are also easy to use APIs that allow you to fully customize the starting position of newly introduced elements, as well as the ending position of removed elements.
|
||||
|
||||
## Animating between layouts
|
||||
<div class = "note">
|
||||
Use of <a href="implicit-hierarchy-management.html">Implicit Hierarchy Management</a> is required to use the Layout Transition API.
|
||||
</div>
|
||||
|
||||
The layout transition API makes it easy to animate between a node's generated layouts in response to some internal state change in a node.
|
||||
## Animating between Layouts
|
||||
<br>
|
||||
The layout Transition API makes it easy to animate between a node's generated layouts in response to some internal state change in a node.
|
||||
|
||||
Imagine you wanted to implement this sign up form and animate in the new field when tapping the next button:
|
||||
|
||||

|
||||
|
||||
A standard way to implement this would be to create a container node called `SignupNode` that includes two `FieldNode`s and a button node as subnodes. We'll include a property on the `SignupNode` called `fieldState` that will be used to select which `FieldNode` to show when the node calculates its layout. The internal layout spec of the `SignupNode` container would look something like this:
|
||||
A standard way to implement this would be to create a container node called `SignupNode` that includes two editable text field nodes and a button node as subnodes. We'll include a property on the SignupNode called `fieldState` that will be used to select which editable text field node to show when the node calculates its layout.
|
||||
|
||||
```objective-c
|
||||
The internal layout spec of the `SignupNode` container would look something like this:
|
||||
|
||||
<div class = "highlight-group">
|
||||
<span class="language-toggle">
|
||||
<a data-lang="objective-c" class = "active objcButton">Objective-C</a>
|
||||
</span>
|
||||
<div class = "code">
|
||||
<pre lang="objc" class="objcCode">
|
||||
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
FieldNode *field;
|
||||
@@ -37,20 +49,39 @@ A standard way to implement this would be to create a container node called `Sig
|
||||
UIEdgeInsets insets = UIEdgeInsetsMake(15.0, 15.0, 15.0, 15.0);
|
||||
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:stack];
|
||||
}
|
||||
```
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
To trigger a transition from the `nameField` to the `ageField` in this example, we'll update the SignupNode's fieldState property and begin the transition with `transitionLayoutWithAnimation:`. This method will invalidate the current calculated layout and recompute a new layout with the `ageField` now in the stack.
|
||||
To trigger a transition from the `nameField` to the `ageField` in this example, we'll update the SignupNode's .fieldState property and begin the transition with `transitionLayoutWithAnimation:`.
|
||||
|
||||
```objective-c
|
||||
This method will invalidate the current calculated layout and recompute a new layout with the `ageField` now in the stack.
|
||||
|
||||
<div class = "highlight-group">
|
||||
<span class="language-toggle">
|
||||
<a data-lang="objective-c" class = "active objcButton">Objective-C</a>
|
||||
</span>
|
||||
<div class = "code">
|
||||
<pre lang="objc" class="objcCode">
|
||||
self.signupNode.fieldState = SignupNodeAge;
|
||||
|
||||
[self.signupNode transitionLayoutWithAnimation:YES];
|
||||
```
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
In the default implementation of this API, the layout will recalculate the new layout and use its sublayouts to size and position the `SignupNode`'s subnodes without animation. Future versions of this API will likely include a default animation between layouts and we're open to feedback on what you'd like to see here. However, we'll need to implement a custom animation block to handle the signup form case.
|
||||
In the default implementation of this API, the layout will recalculate the new layout and use its sublayouts to size and position the SignupNode's subnodes without animation. Future versions of this API will likely include a default animation between layouts and we're open to feedback on what you'd like to see here. However, we'll need to implement a custom animation block to handle the signup form case.
|
||||
|
||||
The example below represents an override of `animateLayoutTransition:` in `SignupNode`. This method is called after the new layout has been calculated via `transitionLayoutWithAnimation:` and in the implementation we'll perform a specific animation based upon the `fieldState` property that was set before the animation was triggered.
|
||||
The example below represents an override of `animateLayoutTransition:` in the SignupNode.
|
||||
|
||||
```objective-c
|
||||
This method is called after the new layout has been calculated via `transitionLayoutWithAnimation:` and in the implementation we'll perform a specific animation based upon the fieldState property that was set before the animation was triggered.
|
||||
|
||||
<div class = "highlight-group">
|
||||
<span class="language-toggle">
|
||||
<a data-lang="objective-c" class = "active objcButton">Objective-C</a>
|
||||
</span>
|
||||
<div class = "code">
|
||||
<pre lang="objc" class="objcCode">
|
||||
- (void)animateLayoutTransition:(id<ASContextTransitioning>)context
|
||||
{
|
||||
if (self.fieldState == SignupNodeName) {
|
||||
@@ -85,16 +116,27 @@ The example below represents an override of `animateLayoutTransition:` in `Signu
|
||||
}];
|
||||
}
|
||||
}
|
||||
```
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
The passed [ASContextTransitioning](https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/ASContextTransitioning.h) context object in this method contains relevant information to help you determine the state of the nodes before and after the transition. It includes getters into old and new constrained sizes, inserted and removed nodes, and even the raw old and new `ASLayout` objects. In the `SignupNode` example, we're using it to determine the frame for each of the fields and animate them in an out of place.
|
||||
|
||||
The passed <a href="https://github.com/facebook/AsyncDisplayKit/blob/master/AsyncDisplayKit/ASContextTransitioning.h"><code>ASContextTransitioning</code></a> context object in this method contains relevant information to help you determine the state of the nodes before and after the transition. It includes getters into old and new constrained sizes, inserted and removed nodes, and even the raw old and new `ASLayout` objects. In the `SignupNode` example, we're using it to determine the frame for each of the fields and animate them in an out of place.
|
||||
|
||||
It is imperative to call `completeTransition:` on the context object once your animation has finished, as it will perform the necessary internal steps for the newly calculated layout to become the current `calculatedLayout`.
|
||||
|
||||
Note that there hasn't been a use of `addSubnode:` or `removeFromSupernode` during the transition. AsyncDisplayKit's layout transition API analyzes the differences in the node hierarchy between the old and new layout, implicitly performing node insertions and removals for automatically. Nodes are inserted before your implementation of `animateLayoutTransition:` is called and this is a good place to manually manage the hierarchy before you begin the animation. Removals are preformed in `didCompleteLayoutTransition:` after you call `completeTransition:` on the context object. If you need to manually perform deletions, override `didCompleteLayoutTransition:` and perform your custom operations. Note that this will override the default behavior and it is recommended to either call `super` or walk through the `removedSubnodes` getter in the context object to perform the cleanup.
|
||||
Note that there hasn't been a use of `addSubnode:` or `removeFromSupernode` during the transition. AsyncDisplayKit's layout transition API analyzes the differences in the node hierarchy between the old and new layout, implicitly performing node insertions and removals via <a href="implicit-hierarchy-management.html">Implicit Hierarchy Management</a>.
|
||||
|
||||
Passing `NO` to `transitionLayoutWithAnimation:` will still run through your `animateLayoutTransition:` and `didCompleteLayoutTransition:` implementations with the `[context isAnimated]` property set to `NO`. It is your choice on how to handle this case — if at all. An easy way to provide a default implementation this is to call `super`:
|
||||
```objective-c
|
||||
Nodes are inserted before your implementation of `animateLayoutTransition:` is called and this is a good place to manually manage the hierarchy before you begin the animation. Removals are preformed in `didCompleteLayoutTransition:` after you call `completeTransition:` on the context object. If you need to manually perform deletions, override `didCompleteLayoutTransition:` and perform your custom operations. Note that this will override the default behavior and it is recommended to either call `super` or walk through the `removedSubnodes` getter in the context object to perform the cleanup.
|
||||
|
||||
Passing NO to `transitionLayoutWithAnimation:` will still run through your `animateLayoutTransition:` and `didCompleteLayoutTransition:` implementations with the `[context isAnimated]` property set to NO. It is your choice on how to handle this case — if at all. An easy way to provide a default implementation this is to call super:
|
||||
|
||||
<div class = "highlight-group">
|
||||
<span class="language-toggle">
|
||||
<a data-lang="objective-c" class = "active objcButton">Objective-C</a>
|
||||
</span>
|
||||
<div class = "code">
|
||||
<pre lang="objc" class="objcCode">
|
||||
- (void)animateLayoutTransition:(id<ASContextTransitioning>)context
|
||||
{
|
||||
if ([context isAnimated]) {
|
||||
@@ -103,12 +145,22 @@ Passing `NO` to `transitionLayoutWithAnimation:` will still run through your `an
|
||||
[super animateLayoutTransition:context];
|
||||
}
|
||||
}
|
||||
```
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Animating constrained size changes
|
||||
## Animating constrainedSize Changes
|
||||
<br>
|
||||
There will be times you'll simply want to respond to bounds changes to your node and animate the recalculation of its layout. To handle this case, call `transitionLayoutWithSizeRange:animated:` on your node.
|
||||
|
||||
There will be times you'll simply want to respond to bounds changes to your node and animate the recalculation of its layout. To handle this case, call `transitionLayoutWithSizeRange:animated:` on your node. This method is similar to `transitionLayoutWithAnimation:`, but will not trigger an animation if the passed `ASSizeRange` is equal to the current `constrainedSizeForCalculatedLayout` value. This is great for responding to rotation events and view controller size changes:
|
||||
```objective-c
|
||||
This method is similar to `transitionLayoutWithAnimation:`, but will not trigger an animation if the passed `ASSizeRange` is equal to the current `constrainedSizeForCalculatedLayout` value. This is great for responding to rotation events and view controller size changes:
|
||||
|
||||
<div class = "highlight-group">
|
||||
<span class="language-toggle">
|
||||
<a data-lang="objective-c" class = "active objcButton">Objective-C</a>
|
||||
</span>
|
||||
<div class = "code">
|
||||
<pre lang="objc" class="objcCode">
|
||||
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
|
||||
{
|
||||
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
|
||||
@@ -116,6 +168,6 @@ There will be times you'll simply want to respond to bounds changes to your node
|
||||
[self.node transitionLayoutWithSizeRange:ASSizeRangeMake(size, size) animated:YES];
|
||||
} completion:nil];
|
||||
}
|
||||
```
|
||||
|
||||
Please check it out and let @levi know what you think at <a href="https://github.com/facebook/AsyncDisplayKit/pull/1156">PR #1156</a>!
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ title: ASScrollNode
|
||||
layout: docs
|
||||
permalink: /docs/scroll-node.html
|
||||
prevPage: video-node.html
|
||||
nextPage: automatic-layout-basics.html
|
||||
nextPage: automatic-layout-containers.html
|
||||
---
|
||||
|
||||
<div>😑 This page is coming soon...</div>
|
||||
@@ -3,7 +3,7 @@ title: Synchronous Concurrency
|
||||
layout: docs
|
||||
permalink: /docs/synchronous-concurrency.html
|
||||
prevPage: subtree-rasterization.html
|
||||
nextPage: debug-tool-hit-test-visualization.html
|
||||
nextPage: corner-rounding.html
|
||||
---
|
||||
|
||||
Both `ASViewController` and `ASCellNode` have a property called `neverShowPlaceholders`.
|
||||
|
||||
Reference in New Issue
Block a user