Merge pull request #1891 from lappp9/scroll-node-docs

[AsyncDisplayKit.org] Scroll node, batch fetch, other tweaks
This commit is contained in:
Hannah Troisi
2016-07-16 16:08:58 -07:00
committed by GitHub
10 changed files with 291 additions and 61 deletions

View File

@@ -11,8 +11,11 @@ Three examples in increasing order of complexity.
<img src="/static/images/layout-example-1.png">
```objective-c
<div class = "highlight-group">
<span class="language-toggle"><a data-lang="swift" class="swiftButton">Swift</a><a data-lang="objective-c" class = "active objcButton">Objective-C</a></span>
<div class = "code">
<pre lang="objc" class="objcCode">
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constraint
{
ASStackLayoutSpec *vStack = [[ASStackLayoutSpec alloc] init];
@@ -29,7 +32,12 @@ Three examples in increasing order of complexity.
return insetSpec;
}
```
</pre>
<pre lang="swift" class = "swiftCode hidden">
</pre>
</div>
</div>
###Discussion
@@ -37,7 +45,11 @@ Three examples in increasing order of complexity.
<img src="/static/images/layout-example-2.png">
```objective-c
<div class = "highlight-group">
<span class="language-toggle"><a data-lang="swift" class="swiftButton">Swift</a><a data-lang="objective-c" class = "active objcButton">Objective-C</a></span>
<div class = "code">
<pre lang="objc" class="objcCode">
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
// header stack
@@ -71,8 +83,12 @@ Three examples in increasing order of complexity.
return verticalStack;
}
</pre>
<pre lang="swift" class = "swiftCode hidden">
```
</pre>
</div>
</div>
###Discussion
@@ -82,7 +98,11 @@ Get the full ASDK project at examples/ASDKgram.
<img src="/static/images/layout-example-3.png">
```objective-c
<div class = "highlight-group">
<span class="language-toggle"><a data-lang="swift" class="swiftButton">Swift</a><a data-lang="objective-c" class = "active objcButton">Objective-C</a></span>
<div class = "code">
<pre lang="objc" class="objcCode">
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
ASLayoutSpec *textSpec = [self textSpec];
@@ -172,7 +192,13 @@ Get the full ASDK project at examples/ASDKgram.
ASBackgroundLayoutSpec *soldOutLabelOverBackground = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:centerSoldOutLabel background:centerSoldOut];
return soldOutLabelOverBackground;
}
```
</pre>
<pre lang="swift" class = "swiftCode hidden">
</pre>
</div>
</div>
###Discussion
Get the full ASDK project at examples/CatDealsCollectionView.

View File

@@ -6,51 +6,111 @@ prevPage: hit-test-slop.html
nextPage: image-modification-block.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.
AsyncDisplayKit's Batch Fetching API makes it easy to add fetching chunks of new data. Usually this would be done in a `-scrollViewDidScroll:` method, but ASDK provides a more structured mechanism.
You as a developer can define the point when the batch fetching mechanism should start via the `leadingScreensForBatching` property on an `ASTableView` or `ASCollectionView`. The default value for this property is 2.0.
By default, as a user is scrolling, when they approach the point in the table or collection where they are 2 "screens" away from the end of the current content, the table will try to fetch more data.
To support batch fetching you have to implement two methods in your ASTableView or ASCollectionView delegate object:
The first method you have to implement is for ASTableView delegate:
If you'd like to configure how far away from the end you should be, just change the `leadingScreensForBatching` property on an `ASTableView` or `ASCollectionView` to something else.
`- (BOOL)shouldBatchFetchForTableView:(ASTableView *)tableView`
<div class = "highlight-group">
<span class="language-toggle"><a data-lang="swift" class="swiftButton">Swift</a><a data-lang="objective-c" class = "active objcButton">Objective-C</a></span>
or for ASCollectionView delegate:
<div class = "code">
<pre lang="objc" class="objcCode">
tableNode.view.leadingScreensForBatching = 3.0; // overriding default of 2.0
</pre>
<pre lang="swift" class = "swiftCode hidden">
tableNode.view.leadingScreensForBatching = 3.0 // overriding default of 2.0
</pre>
</div>
</div>
`- (BOOL)shouldBatchFetchForCollectionView:(ASCollectionView *)collectionView`
### Batch Fetching Delegate Methods
In this method you have decide if the batch fetching mechanism should kick in if the user scrolled in batch fetching range or not. Usually this decision is based on if there is still data to fetch or not, based on e.g. previous API calls or some local dataset operations.
The first thing you have to do in order to support batch fetching, is implement a method that decides if it's an appropriate time to load new content or not.
If you return NO from `- (BOOL)shouldBatchFetchForCollectionView:(ASCollectionView *)collectionView`, no new batch fetching process will happen, in case you return YES the batch fetching mechanism will start and the following method is called for your ASTableView delegate:
For tables it would look something like:
- (void)tableView:(ASTableView *)tableView willBeginBatchFetchWithContext:(ASBatchContext *)context;
<div class = "highlight-group">
<span class="language-toggle"><a data-lang="swift" class="swiftButton">Swift</a><a data-lang="objective-c" class = "active objcButton">Objective-C</a></span>
or for ASCollectionView delegate:
- (void)collectionView:(ASCollectionView *)collectionView willBeginBatchFetchWithContext:(ASBatchContext *)context;
First of all, you have to be careful within this method as it's called on a background thread. If you have to do anything on the main thread, you are responsible for dispatching it to the main thread and proceed with work you have to do in process to finish the batch fetch.
Within `- (void)collectionView:(ASCollectionView *)collectionView willBeginBatchFetchWithContext:(ASBatchContext *)context;` you should do any necessary steps to fetch the next chunk of data e.g. from a local database, an API etc.
After you finished fetching the next chunk of data, it is very important to let ASDK know that you finished the process. To do that you have to call `completeBatchFetching:` on the `context` object that was passed in with a parameter value of YES. This assures that the whole batch fetching mechanism stays in sync and a next batch fetching cycle can happen. Only by passing YES will the context know to attempt another batch update when necessary. If you pass in NO nothing will happen.
Here you can see an example how a batch fetching cycle could look like:
```objective-c
- (BOOL)shouldBatchFetchForTableView:(ASTableView *)tableView
<div class = "code">
<pre lang="objc" class="objcCode">
- (BOOL)shouldBatchFetchForTableView:(ASTableView *)tableView
{
// Decide if the batch fetching mechanism should kick in
if (_stillDataToFetch) {
if (_weNeedMoreContent) {
return YES;
}
return NO;
}
</pre>
<pre lang="swift" class = "swiftCode hidden">
func shouldBatchFetchForTableView(tableView: ASTableView) -> Bool {
if (weNeedMoreContent) {
return true
}
return false
}
</pre>
</div>
</div>
and for collections:
<div class = "highlight-group">
<span class="language-toggle"><a data-lang="swift" class="swiftButton">Swift</a><a data-lang="objective-c" class = "active objcButton">Objective-C</a></span>
<div class = "code">
<pre lang="objc" class="objcCode">
- (BOOL)shouldBatchFetchForCollectionView:(ASCollectionView *)collectionView
{
if (_weNeedMoreContent) {
return YES;
}
return NO;
}
</pre>
<pre lang="swift" class = "swiftCode hidden">
func shouldBatchFetchForCollectionView(collectionView: ASCollectionView) -> Bool {
if (weNeedMoreContent) {
return true
}
return false
}
</pre>
</div>
</div>
These methods will be called when the user has scrolled into the batch fetching range, and their answer will determine if another request actually needs to be made or not. Usually this decision is based on if there is still data to fetch.
If you return NO, then no new batch fetching process will happen. If you return YES, the batch fetching mechanism will start and the following method will be called next.
`-tableView:willBeginBatchFetchWithContext:`
or
`-collectionView:willBeginBatchFetchWithContext:`
This is where you should actually fetch data, be it from a web API or some local database.
<div class = "note">
<strong>Note:</strong> This method will always be called on a background thread. This means, if you need to do any work on the main thread, you should dispatch it to the main thread and then proceed with the work needed in order to finish the batch fetch operation.
</div>
<div class = "highlight-group">
<span class="language-toggle"><a data-lang="swift" class="swiftButton">Swift</a><a data-lang="objective-c" class = "active objcButton">Objective-C</a></span>
<div class = "code">
<pre lang="objc" class="objcCode">
- (void)tableView:(ASTableView *)tableView willBeginBatchFetchWithContext:(ASBatchContext *)context
{
// Fetch data most of the time asynchronoulsy from an API or local database
NSArray *data = ...;
NSArray *newPhotos = [SomeSource getNewPhotos];
// Insert data into table or collection view
[self insertNewRowsInTableView:newPhotos];
@@ -61,9 +121,28 @@ Here you can see an example how a batch fetching cycle could look like:
// Properly finish the batch fetch
[context completeBatchFetching:YES]
}
```
</pre>
<pre lang="swift" class = "swiftCode hidden">
func tableView(tableView: ASTableView, willBeginBatchFetchWithContext context: ASBatchContext) {
// Fetch data most of the time asynchronoulsy from an API or local database
let newPhotos = SomeSource.getNewPhotos()
Check out the following sample apps to see the batch fetching API implemented within an app:
// Insert data into table or collection view
insertNewRowsInTableView(newPhotos)
// Decide if it's still necessary to trigger more batch fetches in the future
stillDataToFetch = ...
// Properly finish the batch fetch
context.completeBatchFetching(true)
}
</pre>
</div>
</div>
Once you've finished fetching your data, it is very important to let ASDK know that you have finished the process. To do that, you need to call `-completeBatchFetching:` on the `context` object that was passed in with a parameter value of `YES`. This assures that the whole batch fetching mechanism stays in sync and the next batch fetching cycle can happen. Only by passing `YES` will the context know to attempt another batch update when necessary.
Check out the following sample apps to see the batch fetching API in action:
<ul>
<li><a href="https://github.com/facebook/AsyncDisplayKit/tree/master/examples/ASDKgram">ASDKgram</a></li>
<li><a href="https://github.com/facebook/AsyncDisplayKit/tree/master/examples/Kittens">Kittens</a></li>

View File

@@ -6,19 +6,37 @@ prevPage: debug-tool-hit-test-visualization.html
nextPage: implicit-hierarchy-mgmt.html
---
##Visualize ASImageNode.images pixel scaling##
### Visualize ASImageNode.images pixel scaling
This debug feature adds a red text overlay on the bottom right hand corner of an ASImageNode if (and only if) the images size in pixels does not match its bounds size in pixels, e.g.
```objective-c
imageSizeInPixels = image.size * image.scale
boundsSizeInPixels = bounds.size * contentsScale
scaleFactor = imageSizeInPixels / boundsSizeInPixels
<div class = "highlight-group">
<span class="language-toggle"><a data-lang="swift" class="swiftButton">Swift</a><a data-lang="objective-c" class = "active objcButton">Objective-C</a></span>
<div class = "code">
<pre lang="objc" class="objcCode">
CGFloat imageSizeInPixels = image.size.width * image.size.height;
CGFloat boundsSizeInPixels = imageView.bounds.size.width * imageView.bounds.size.height;
CGFloat scaleFactor = imageSizeInPixels / boundsSizeInPixels;
if (scaleFactor != 1.0) {
NSString *scaleString = [NSString stringWithFormat:@"%.2fx", scaleFactor];
_debugLabelNode.hidden = NO;
}
```
</pre>
<pre lang="swift" class = "swiftCode hidden">
let imageSizeInPixels = image.size.width * image.size.height
let boundsSizeInPixels = imageView.bounds.size.width * imageView.bounds.size.height
let scaleFactor = imageSizeInPixels / boundsSizeInPixels
if scaleFactor != 1.0 {
let scaleString = "\(scaleFactor)"
_debugLabelNode.hidden = false
}
</pre>
</div>
</div>
**This debug feature is useful for quickly determining if you are**
<ul>

View File

@@ -10,10 +10,31 @@ nextPage: batch-fetching-api.html
ASDisplayNode is the base class for all nodes, so this property is available on any of AsyncDisplayKit's nodes.
Note:
<ul>
<li>the default value is UIEdgeInsetsZero</li>
<li>This affects the default implementation of `-hitTest` and `-pointInside`, so subclasses should call super if you override it and want hitTestSlop applied.</li>
</ul>
<div class = "note">
<strong>Note:</strong> This affects the default implementation of -hitTest and -pointInside, so subclasses should call super if you override it and want hitTestSlop applied.
</div>
A node's ability to capture touch events is restricted by its parent's bounds + parent hitTestSlop UIEdgeInsets. Should you want to extend the hitTestSlop of a child outside its parent's bounds, simply extend the parent node's hitTestSlop to include the child's hitTestSlop needs.
### Usage
A common need for hit test slop, is when you have a text node (aka label) you'd like to use as a button. Often, the text node's height won't meet the 44 point minimum recommended for tappable areas. In that case, you can calculate the difference, and apply a negative inset to your label to increase the tappable area.
<div class = "highlight-group">
<span class="language-toggle"><a data-lang="swift" class="swiftButton">Swift</a><a data-lang="objective-c" class = "active objcButton">Objective-C</a></span>
<div class = "code">
<pre lang="objc" class="objcCode">
ASTextNode *textNode = [[ASTextNode alloc] init];
CGFloat padding = (44.0 - button.bounds.size.height)/2.0;
textNode.hitTestSlop = UIEdgeInsetsMake(-padding, 0, -padding, 0);
</pre>
<pre lang="swift" class = "swiftCode hidden">
let textNode = ASTextNode()
let padding = (44.0 - button.bounds.size.height)/2.0
textNode.hitTestSlop = UIEdgeInsetsMake(-padding, 0, -padding, 0)
</pre>
</div>
</div>

View File

@@ -6,4 +6,40 @@ prevPage: batch-fetching-api.html
nextPage: placeholder-fade-duration.html
---
<div class = "warning">😑 This page is coming soon...</div>
Many times, operations that would affect the appearance of an image you're displaying are big sources of main thread work. Naturally, you want to move these to a background thread. By assigning an `imageModificationBlock` to your imageNode, you can define a set of transformations that need to happen asynchronously to any image that gets set on the imageNode.
<div class = "highlight-group">
<span class="language-toggle"><a data-lang="swift" class="swiftButton">Swift</a><a data-lang="objective-c" class = "active objcButton">Objective-C</a></span>
<div class = "code">
<pre lang="objc" class="objcCode">
_backgroundImageNode.imageModificationBlock = ^(UIImage *image) {
UIImage *newImage = [image applyBlurWithRadius:30
tintColor:[UIColor colorWithWhite:0.5 alpha:0.3]
saturationDeltaFactor:1.8
maskImage:nil];
return newImage ? newImage : image;
};
//some time later...
_backgroundImageNode.image = someImage;
</pre>
<pre lang="swift" class = "swiftCode hidden">
backgroundImageNode.imageModificationBlock = { image in
let newImage = image.applyBlurWithRadius(30, tintColor: UIColor(white: 0.5, alpha: 0.3),
saturationDeltaFactor: 1.8,
maskImage: nil)
return (newImage != nil) ? newImage : image
}
//some time later...
backgroundImageNode.image = someImage
</pre>
</div>
</div>
The image named "someImage" will now be blurred asynchronously before being assigned to the imageNode to be displayed.

View File

@@ -3,7 +3,7 @@ title: Implicit Hierarchy Management
layout: docs
permalink: /docs/implicit-hierarchy-mgmt.html
prevPage: debug-tool-pixel-scaling.html
nextPage: debug-hit-test.html
nextPage: hit-test-slop.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.
@@ -24,7 +24,11 @@ When enabled, IHM, means that your nodes no longer require `addSubnode:` or `rem
####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.
```objective-c
<div class = "highlight-group">
<span class="language-toggle"><a data-lang="swift" class="swiftButton">Swift</a><a data-lang="objective-c" class = "active objcButton">Objective-C</a></span>
<div class = "code">
<pre lang="objc" class="objcCode">
- (instancetype)initWithPhotoObject:(PhotoModel *)photo;
{
self = [super init];
@@ -57,10 +61,15 @@ Consider the following `ASCellNode` subclass `PhotoCellNode` from the <a href="h
return self;
}
</pre>
<pre lang="swift" class = "swiftCode hidden">
```
</pre>
</div>
</div>
By setting usesImplicitHierarchyManagement to YES on the ASCellNode, we _no longer_ need to call `addSubnode:` for each of the ASCellNode's subnodes.
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.
@@ -70,7 +79,11 @@ Implicit Hierarchy Management knows whether or not to include these elements in
Consider the layoutSpecThatFits: method for the ASCellNode subclass
```objective-c
<div class = "highlight-group">
<span class="language-toggle"><a data-lang="swift" class="swiftButton">Swift</a><a data-lang="objective-c" class = "active objcButton">Objective-C</a></span>
<div class = "code">
<pre lang="objc" class="objcCode">
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
// username / photo location header vertical stack
@@ -86,7 +99,7 @@ Consider the layoutSpecThatFits: method for the ASCellNode subclass
}
// header stack
_userAvatarImageView.preferredFrameSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); // constrain avatar image frame size
_userAvatarImageView.preferredFrameSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); // constrain avatar image frame size
ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init];
spacer.flexGrow = YES;
@@ -117,7 +130,12 @@ Consider the layoutSpecThatFits: method for the ASCellNode subclass
return verticalStack;
}
```
</pre>
<pre lang="swift" class = "swiftCode hidden">
</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.

View File

@@ -8,7 +8,6 @@ nextPage: subtree-rasterization.html
In some cases, you can substantially improve your app's performance by using layers instead of views. We recommend enabling layer-backing as a matter of course in **any custom node that doesn't need touch handling**.
With UIKit, manually converting view-based code to layers is laborious due to the difference in APIs. Worse, if at some point you need to enable touch handling or other view-specific functionality, you have to manually convert everything back (and risk regressions!).
With all AsyncDisplayKit nodes, converting an entire subtree from views to layers is as simple as...
@@ -16,7 +15,7 @@ With all AsyncDisplayKit nodes, converting an entire subtree from views to layer
<div class = "highlight-group">
<span class="language-toggle"><a data-lang="swift" class="swiftButton">Swift</a><a data-lang="objective-c" class = "active objcButton">Objective-C</a></span>
<div class = "code">
<pre lang="objc" class="objcCode">
<pre lang="objc" class="objcCode">
rootNode.layerBacked = YES;
</pre>
<pre lang="swift" class = "swiftCode hidden">
@@ -24,7 +23,6 @@ rootNode.layerBacked = true
</pre>
</div>
</div>
<br>
...and if you need to go back, it's as simple as deleting one line.

View File

@@ -6,4 +6,40 @@ prevPage: video-node.html
nextPage: automatic-layout-basics.html
---
<div>😑 This page is coming soon...</div>
`ASScrollNode` is literally a wrapped `UIScrollView`.
### Basic Usage
In case you're not familiar with scroll views, they are basically windows into content that would take up more space than can fit in that area.
Say you have a giant image, but you only want to take up 200x200 pts on the screen.
<div class = "highlight-group">
<span class="language-toggle"><a data-lang="swift" class="swiftButton">Swift</a><a data-lang="objective-c" class = "active objcButton">Objective-C</a></span>
<div class = "code">
<pre lang="objc" class="objcCode">
UIImage *scrollNodeImage = [UIImage imageNamed:@"image"];
ASScrollNode *scrollNode = [[ASScrollNode alloc] init];
scrollNode.preferredFrameSize = CGSizeMake(200.0, 200.0);
UIScrollView *scrollNodeView = scrollNode.view;
[scrollNodeView addSubview:[[UIImageView alloc] initWithImage:scrollNodeImage]];
scrollNodeView.contentSize = scrollNodeImage.size;
</pre>
<pre lang="swift" class = "swiftCode hidden">
let scrollNodeImage = UIImage(named: "image")
let scrollNode = ASScrollNode()
scrollNode.preferredFrameSize = CGSize(width: 200.0, height: 200.0)
let scrollNodeView = scrollNode.view
scrollNodeView.addSubview(UIImageView(image: scrollNodeImage))
scrollNodeView.contentSize = scrollNodeImage.size
</pre>
</div>
</div>
As you can see, the scrollNode's underlying view is a `UIScrollView`.

View File

@@ -8,8 +8,6 @@ nextPage: synchronous-concurrency.html
Flattening an entire view hierarchy into a single layer improves performance, but with UIKit, comes with a hit to maintainability and hierarchy-based reasoning.
With all AsyncDisplayKit nodes, enabling precompositing is as simple as:
<div class = "highlight-group">
<span class="language-toggle"><a data-lang="swift" class="swiftButton">Swift</a><a data-lang="objective-c" class = "active objcButton">Objective-C</a></span>
<div class = "code">

View File

@@ -92,7 +92,7 @@ code {
p code, span code, li code {
border: 1px solid rgb(220, 220, 220);
background-color: rgba(90, 140, 140, 0.1);
background-color: rgba(135, 215, 255, 0.2);
}
.highlight pre, .redhighlight pre {