---
title: Automatic Subnode Management
layout: docs
permalink: /docs/automatic-subnode-mgmt.html
prevPage: batch-fetching-api.html
nextPage: inversion.html
---
Enabling Automatic Subnode Management (ASM) is required to use the Layout Transition API. However, apps that don't require animations can still benefit from the reduction in code size that this feature enables.
When enabled, ASM means that your nodes no longer require `addSubnode:` or `removeFromSupernode` method calls. The presence or absence of the ASM node _and_ its subnodes is completely determined in its `layoutSpecThatFits:` method.
### Example ###
Consider the following intialization method from the PhotoCellNode class in ASDKgram sample app. This ASCellNode
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" 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.
Original code
- (instancetype)initWithPhotoObject:(PhotoModel *)photo;
{
self = [super init];
if (self) {
_photoModel = photo;
_userAvatarImageNode = [[ASNetworkImageNode alloc] init];
_userAvatarImageNode.URL = photo.ownerUserProfile.userPicURL;
[self addSubnode:_userAvatarImageNode];
_photoImageNode = [[ASNetworkImageNode alloc] init];
_photoImageNode.URL = photo.URL;
[self addSubnode:_photoImageNode];
_userNameTextNode = [[ASTextNode alloc] init];
_userNameTextNode.attributedString = [photo.ownerUserProfile usernameAttributedStringWithFontSize:FONT_SIZE];
[self addSubnode:_userNameTextNode];
_photoLocationTextNode = [[ASTextNode alloc] init];
[photo.location reverseGeocodedLocationWithCompletionBlock:^(LocationModel *locationModel) {
if (locationModel == _photoModel.location) {
_photoLocationTextNode.attributedString = [photo locationAttributedStringWithFontSize:FONT_SIZE];
[self setNeedsLayout];
}
}];
[self addSubnode:_photoLocationTextNode];
}
return self;
}
- (instancetype)initWithPhotoObject:(PhotoModel *)photo;
{
self = [super init];
if (self) {
self.automaticallyManagesSubnodes = YES;
_photoModel = photo;
_userAvatarImageNode = [[ASNetworkImageNode alloc] init];
_userAvatarImageNode.URL = 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];
}
}];
}
return self;
}
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 layoutSpecThatFits:
method.
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
ASStackLayoutSpec *headerSubStack = [ASStackLayoutSpec verticalStackLayoutSpec];
headerSubStack.flexShrink = YES;
if (_photoLocationLabel.attributedString) {
[headerSubStack setChildren:@[_userNameLabel, _photoLocationLabel]];
} else {
[headerSubStack setChildren:@[_userNameLabel]];
}
_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:_userAvatarImageNode];
ASStackLayoutSpec *headerStack = [ASStackLayoutSpec horizontalStackLayoutSpec];
headerStack.alignItems = ASStackLayoutAlignItemsCenter; // center items vertically in horizontal stack
headerStack.justifyContent = ASStackLayoutJustifyContentStart; // justify content to the left side of the header stack
[headerStack setChildren:@[avatarInset, headerSubStack, spacer]];
// header inset stack
UIEdgeInsets insets = UIEdgeInsetsMake(0, HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER);
ASInsetLayoutSpec *headerWithInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:headerStack];
// footer inset stack
UIEdgeInsets footerInsets = UIEdgeInsetsMake(VERTICAL_BUFFER, HORIZONTAL_BUFFER, VERTICAL_BUFFER, HORIZONTAL_BUFFER);
ASInsetLayoutSpec *footerWithInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:footerInsets child:_photoCommentsNode];
// vertical stack
CGFloat cellWidth = constrainedSize.max.width;
_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, _photoImageNode, footerWithInset]];
return verticalStack;
}
addSubnode:
and removeFromSupernode
should never be called on a node that has ASM enabled. Doing so could cause the following exception - "A flattened layout must consist exclusively of node sublayouts".