Skip to content

Commit

Permalink
Update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
garrettmoon committed Apr 14, 2017
1 parent ed892e1 commit 7ddfd6e
Show file tree
Hide file tree
Showing 14 changed files with 315 additions and 76 deletions.
130 changes: 124 additions & 6 deletions docs/_docs/automatic-subnode-mgmt.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ When enabled, ASM means that your nodes no longer require `addSubnode:` or `remo
<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 <code>ASCellNode</code> subclass produces a simple social media photo feed cell.

In the "Original Code" we see the familiar `addSubnode:` calls in bold. In the "Code with ASM" (switch at top right of code block) these have been removed and replaced with a single line that enables ASM.
In the "Original Code" we see the familiar `addSubnode:` calls in bold. In the "Code with ASM" these have been removed and replaced with a single line that enables ASM.

By setting `.automaticallyManagesSubnodes` 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.

<div class = "highlight-group">

<i>Original code</i>
<div class="highlight-group">
<span class="language-toggle">
<a data-lang="swift" class="swiftButton">Code with ASM</a>
<a data-lang="objective-c" class = "active objcButton">Original Code</a>
<a data-lang="objective-c" class="active objcButton">Objective-C</a>
<a data-lang="swift" class="swiftButton">Swift</a>
</span>
<div class = "code">
<pre lang="objc" class="objcCode">
Expand Down Expand Up @@ -57,8 +59,50 @@ By setting `.automaticallyManagesSubnodes` to `YES` on the `ASCellNode`, we _no
return self;
}
</pre>
<pre lang="swift" class="swiftCode hidden">
class PhotoCellNode {
private let photoModel: PhotoModel

private let userAvatarImageNode = ASNetworkImageNode()
private let photoImageNode = ASNetworkImageNode()
private let userNameTextNode = ASTextNode()
private let photoLocationTextNode = ASTextNode()

<pre lang="swift" class = "swiftCode hidden">
init(photo: PhotoModel) {
photoModel = photo

super.init()

userAvatarImageNode.URL = photo.ownerUserProfile.userPicURL
<b>addSubnode(userAvatarImageNode)</b>

photoImageNode.URL = photo.URL
<b>addSubnode(photoImageNode)</b>

userNameTextNode.attributedText = poto.ownerUserProfile.usernameAttributedString(fontSize: fontSize)
<b>addSubnode(userNameTextNode)</b>

photo.location.reverseGeocodeLocation { [weak self] location in
if locationModel == self?.photoModel.location {
self?.photoLocationTextNode.attributedText = photo.locationAttributedString(fontSize: fontSize)
self?.setNeedsLayout()
}
}
<b>addSubnode(photoLocationTextNode)</b>
}
}
</pre>
</div>
</div>

<i>Code with ASM</i>
<div class="highlight-group">
<span class="language-toggle">
<a data-lang="objective-c" class="active objcButton">Objective-C</a>
<a data-lang="swift" class="swiftButton">Swift</a>
</span>
<div class = "code">
<pre lang="objc" class="objcCode">
- (instancetype)initWithPhotoObject:(PhotoModel *)photo;
{
self = [super init];
Expand Down Expand Up @@ -89,6 +133,37 @@ By setting `.automaticallyManagesSubnodes` to `YES` on the `ASCellNode`, we _no
return self;
}
</pre>
<pre lang="swift" class="swiftCode hidden">
class PhotoCellNode {
private let photoModel: PhotoModel

private let userAvatarImageNode = ASNetworkImageNode()
private let photoImageNode = ASNetworkImageNode()
private let userNameTextNode = ASTextNode()
private let photoLocationTextNode = ASTextNode()

init(photo: PhotoModel) {
photoModel = photo

super.init()

<b>automaticallyManagesSubnodes = true</b>

userAvatarImageNode.URL = photo.ownerUserProfile.userPicURL

photoImageNode.URL = photo.URL

userNameTextNode.attributedText = poto.ownerUserProfile.usernameAttributedString(fontSize: fontSize)

photo.location.reverseGeocodeLocation { [weak self] location in
if locationModel == self?.photoModel.location {
self?.photoLocationTextNode.attributedText = photo.locationAttributedString(fontSize: fontSize)
self?.setNeedsLayout()
}
}
}
}
</pre>
</div>
</div>

Expand All @@ -105,7 +180,10 @@ An <code>ASLayoutSpec</code> completely describes the UI of a view in your app b
Consider the abreviated `layoutSpecThatFits:` method for the `ASCellNode` subclass above.

<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>
<span class="language-toggle">
<a data-lang="objective-c" class="active objcButton">Objective-C</a>
<a data-lang="swift" class="swiftButton">Swift</a>
</span>

<div class = "code">
<pre lang="objc" class="objcCode">
Expand Down Expand Up @@ -153,7 +231,47 @@ Consider the abreviated `layoutSpecThatFits:` method for the `ASCellNode` subcla
</pre>

<pre lang="swift" class = "swiftCode hidden">
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
let headerSubStack: ASStackLayoutSpec = .vertical()
headerSubStack.style.flexShrink = 1

<b>if photoLocationLabel.attributedText != nil {</b>
headerSubStack.children = [userNameLabel, photoLocationLabel]
<b>} else {</b>
headerSubStack.children = [userNameLabel]
<b>}</b>

userAvatarImageNode.style.preferredSize = CGSize(width: userImageHeight, height: userImageHeight) // constrain avatar image frame size

let spacer = ASLayoutSpec()
spacer.style.flexGrow = 1

let avatarInsets = UIEdgeInsets(top: horizontalBuffer, left: 0, bottom: horizontalBuffer, right: horizontalBuffer)
let avatarInset = ASInsetLayoutSpec(insets: avatarInsets, child: <b>userAvatarImageNode</b>)

let headerStack: ASStackLayoutSpec = .horizontal()
headerStack.alignItems = .center // center items vertically in horizontal stack
headerStack.justifyContent = .start // justify content to the left side of the header stack
headerStack.children = [avatarInset, headerSubStack, spacer]

// header inset stack
let insets = UIEdgeInsets(top: 0, left: horizontalBuffer, bottom: 0, right: horizontalBuffer)
let headerWithInset = ASInsetLayoutSpec(insets: insets, child: headerStack)

// footer inset stack
let footerInsets = UIEdgeInsets(top: verticalBuffer, left: horizontalBuffer, bottom: verticalBuffer, right: horizontalBuffer)
let footerWithInset = ASInsetLayoutSpec(insets: footerInsets, child: <b>photoCommentsNode</b>)

// vertical stack
let cellWidth = constrainedSize.max.width
photoImageNode.style.preferredSize = CGSize(width: cellWidth, height: cellWidth) // constrain photo frame size

let verticalStack: ASStackLayoutSpec = .vertical()
verticalStack.alignItems = .stretch // stretch headerStack to fill horizontal space
verticalStack.children = [headerWithInset, <b>photoImageNode</b>, footerWithInset]

return verticalStack
}
</pre>
</div>
</div>
Expand Down
10 changes: 5 additions & 5 deletions docs/_docs/button-node.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ If you've used `-setTitle:forControlState:` then you already know how to set up
[buttonNode setTitle:@"Button Title Normal" withFont:nil withColor:[UIColor blueColor] forState:ASControlStateNormal];
</pre>
<pre lang="swift" class = "swiftCode hidden">
button.setTitle("Button Title Normal", withFont: nil, withColor: UIColor.blueColor(), forState: .Normal)
buttonNode.setTitle("Button Title Normal", with: nil, with: .blue, for: .normal)
</pre>
</div>
</div>
Expand All @@ -37,7 +37,7 @@ If you need even more control, you can also opt to use the attributed string ver
[self.buttonNode setAttributedTitle:attributedTitle forState:ASControlStateNormal];
</pre>
<pre lang="swift" class = "swiftCode hidden">
buttonNode.setAttributedTitle(attributedTitle, for: [])
buttonNode.setAttributedTitle(attributedTitle, for: .normal)
</pre>
</div>
</div>
Expand All @@ -54,7 +54,7 @@ Again, analagous to UIKit, you can add sets of target-action pairs to respond to
[buttonNode addTarget:self action:@selector(buttonPressed:) forControlEvents:ASControlNodeEventTouchUpInside];
</pre>
<pre lang="swift" class = "swiftCode hidden">
button.addTarget(self, action: #selector(buttonPressed(_:)), forControlEvents: .TouchUpInside)
buttonNode.addTarget(self, action: #selector(buttonPressed), forControlEvents: .touchUpInside)
</pre>
</div>
</div>
Expand All @@ -72,8 +72,8 @@ self.buttonNode.contentVerticalAlignment = ASVerticalAlignmentTop;
self.buttonNode.contentHorizontalAlignment = ASHorizontalAlignmentMiddle;
</pre>
<pre lang="swift" class = "swiftCode hidden">
buttonNode.contentVerticalAlignment = .Top
buttonNode.contentHorizontalAlignment = .Middle
buttonNode.contentVerticalAlignment = .top
buttonNode.contentHorizontalAlignment = .middle
</pre>
</div>
</div>
Expand Down
14 changes: 7 additions & 7 deletions docs/_docs/cell-node.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,20 @@ For example, say you already have a view controller written that manages an `AST
return [[AnimalTableNodeController alloc] initWithAnimals:animals];
} didLoadBlock:nil];

node.preferredFrameSize = pagerNode.bounds.size;
node.style.preferredSize = pagerNode.bounds.size;

return node;
}
</pre>
<pre lang="swift" class = "swiftCode hidden">
func pagerNode(pagerNode: ASPagerNode!, nodeAtIndex index: Int) -> ASCellNode! {
func pagerNode(_ pagerNode: ASPagerNode, nodeAt index: Int) -> ASCellNode {
let animals = allAnimals[index]

let node = ASCellNode(viewControllerBlock: { () -> UIViewController in
return AnimalTableNodeController(animals: animals)
}, didLoadBlock: nil)
}, didLoad: nil)

node.preferredFrameSize = pagerNode.bounds.size
node.style.preferredSize = pagerNode.bounds.size

return node
}
Expand Down Expand Up @@ -86,20 +86,20 @@ Alternatively, if you already have a `UIView` or `CALayer` subclass that you'd l
return [[SomeAnimalView alloc] initWithAnimal:animal];
}];

node.preferredFrameSize = pagerNode.bounds.size;
node.style.preferredSize = pagerNode.bounds.size;

return node;
}
</pre>
<pre lang="swift" class = "swiftCode hidden">
func pagerNode(pagerNode: ASPagerNode!, nodeAtIndex index: Int) -> ASCellNode! {
func pagerNode(_ pagerNode: ASPagerNode, nodeAt index: Int) -> ASCellNode {
let animal = animals[index]

let node = ASCellNode { () -> UIView in
return SomeAnimalView(animal: animal)
}

node.preferredFrameSize = pagerNode.bounds.size
node.style.preferredSize = pagerNode.bounds.size

return node
}
Expand Down
48 changes: 47 additions & 1 deletion docs/_docs/containers-ascollectionnode.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,58 @@ It is recommended that you use the node block version of the method so that your
As noted in the previous section:

<ul>
<li>ASCollectionNodes do not utilize cell resuse.</li>
<li>ASCollectionNodes do not utilize cell reuse.</li>
<li>Using the "nodeBlock" method is preferred.</li>
<li>It is very important that the returned node blocks are thread-safe.</li>
<li>ASCellNodes can be used by ASTableNode, ASCollectionNode and ASPagerNode.</li>
</ul>

### Node Block Thread Safety Warning

It is very important that node blocks be thread-safe. One aspect of that is ensuring that the data model is accessed _outside_ of the node block. Therefore, it is unlikely that you should need to use the index inside of the block.

Consider the following `-collectionNode:nodeBlockForItemAtIndexPath:` method.

<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">
- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath
{
PhotoModel *photoModel = [_photoFeed objectAtIndex:indexPath.row];

// this may be executed on a background thread - it is important to make sure it is thread safe
ASCellNode *(^cellNodeBlock)() = ^ASCellNode *() {
PhotoCellNode *cellNode = [[PhotoCellNode alloc] initWithPhoto:photoModel];
cellNode.delegate = self;
return cellNode;
};

return cellNodeBlock;
}
</pre>

<pre lang="swift" class = "swiftCode hidden">
func tableNode(_ collectionNode: ASCollectionNode, nodeBlockForItemAt indexPath: IndexPath) -> ASCellNodeBlock {
guard photoFeed.count > indexPath.row else { return { ASCellNode() } }

let photoModel = photoFeed[indexPath.row]

// this may be executed on a background thread - it is important to make sure it is thread safe
let cellNodeBlock = { () -> ASCellNode in
let cellNode = PhotoCellNode(photo: photoModel)
cellNode.delegate = self
return cellNode
}

return cellNodeBlock
}
</pre>
</div>
</div>

In the example above, you can see how the index is used to access the photo model before creating the node block.

### Replacing a UICollectionViewController with an ASViewController

AsyncDisplayKit does not offer an equivalent to UICollectionViewController. Instead, you can use the flexibility of ASViewController to recreate any type of UI<em>...</em>ViewController.
Expand Down
Loading

0 comments on commit 7ddfd6e

Please sign in to comment.