diff --git a/submodules/AsyncDisplayKit/.buckconfig b/submodules/AsyncDisplayKit/.buckconfig
new file mode 100644
index 0000000000..821cb2c82f
--- /dev/null
+++ b/submodules/AsyncDisplayKit/.buckconfig
@@ -0,0 +1,22 @@
+[cxx]
+ default_platform = iphonesimulator-x86_64
+ combined_preprocess_and_compile = true
+
+[apple]
+ iphonesimulator_target_sdk_version = 8.0
+ iphoneos_target_sdk_version = 8.0
+ xctool_default_destination_specifier = platform=iOS Simulator, name=iPhone 6, OS=10.2
+
+[alias]
+ lib = //:AsyncDisplayKit
+ tests = //:Tests
+
+[httpserver]
+ port = 8080
+
+[project]
+ ide = xcode
+ ignore = .buckd, \
+ .hg, \
+ .git, \
+ buck-out, \
diff --git a/submodules/AsyncDisplayKit/.buckversion b/submodules/AsyncDisplayKit/.buckversion
new file mode 100644
index 0000000000..437aedac09
--- /dev/null
+++ b/submodules/AsyncDisplayKit/.buckversion
@@ -0,0 +1 @@
+f399f484bf13b47bcc2bf0f2e092ab5d8de9f6e6
diff --git a/submodules/AsyncDisplayKit/.editorconfig b/submodules/AsyncDisplayKit/.editorconfig
new file mode 100644
index 0000000000..0605feef2a
--- /dev/null
+++ b/submodules/AsyncDisplayKit/.editorconfig
@@ -0,0 +1,19 @@
+# http://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[**.{h,cc,mm,m}]
+indent_style = space
+indent_size = 2
+
+[*.{md,markdown}]
+trim_trailing_whitespace = false
+
+# Makefiles always use tabs for indentation
+[Makefile]
+indent_style = tab
\ No newline at end of file
diff --git a/submodules/AsyncDisplayKit/.github/ISSUE_TEMPLATE.md b/submodules/AsyncDisplayKit/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000000..c5ab71942b
--- /dev/null
+++ b/submodules/AsyncDisplayKit/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,7 @@
+// If you're looking for help, please consider joining our slack channel:
+// http://asyncdisplaykit.org/slack (we'll be updating the name to Texture soon)
+
+// The more information you include, the faster we can help you out!
+// Please include: a sample project or screenshots, code snippets
+// Texture version, and/or backtraces for any crashes (> bt all).
+// Please delete these lines before posting. Thanks!
diff --git a/submodules/AsyncDisplayKit/.github_changelog_generator b/submodules/AsyncDisplayKit/.github_changelog_generator
new file mode 100644
index 0000000000..5b1ccea517
--- /dev/null
+++ b/submodules/AsyncDisplayKit/.github_changelog_generator
@@ -0,0 +1,3 @@
+issues=false
+since-tag=2.8
+future-release=2.9
diff --git a/submodules/AsyncDisplayKit/.gitignore b/submodules/AsyncDisplayKit/.gitignore
new file mode 100644
index 0000000000..eed07e6482
--- /dev/null
+++ b/submodules/AsyncDisplayKit/.gitignore
@@ -0,0 +1,25 @@
+fastlane/README.md
+fastlane/report.xml
+fastlane/test_output/*
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata
+*.xccheckout
+*.xcscmblueprint
+*.moved-aside
+DerivedData
+*.hmap
+*.ipa
+*.xcuserstate
+.DS_Store
+*.dSYM
+*.dSYM.zip
+*.ipa
+*/xcuserdata/*
+AsyncDisplayKit.xcodeproj/*
diff --git a/submodules/AsyncDisplayKit/.slather.yml b/submodules/AsyncDisplayKit/.slather.yml
new file mode 100644
index 0000000000..ef84e32dea
--- /dev/null
+++ b/submodules/AsyncDisplayKit/.slather.yml
@@ -0,0 +1,5 @@
+ci_service: travis_ci
+coverage_service: coveralls
+xcodeproj: AsyncDisplayKit.xcodeproj
+source_directory: AsyncDisplayKit
+
diff --git a/submodules/AsyncDisplayKit/.travis.yml b/submodules/AsyncDisplayKit/.travis.yml
new file mode 100644
index 0000000000..b493cff43c
--- /dev/null
+++ b/submodules/AsyncDisplayKit/.travis.yml
@@ -0,0 +1,34 @@
+language: objective-c
+cache:
+ - bundler
+ - cocoapods
+osx_image: xcode8.1
+git:
+ depth: 10
+before_install:
+ - brew update
+ - brew outdated xctool || brew upgrade xctool
+ - brew outdated carthage || brew upgrade carthage
+ - gem install cocoapods -v 1.0.1
+ - gem install xcpretty -v 0.2.2
+ - gem install xcpretty-travis-formatter
+# - gem install slather
+ - xcrun simctl list
+install: echo "<3"
+env:
+ - MODE=tests
+ - MODE=tests_listkit
+ - MODE=examples-pt1
+ - MODE=examples-pt2
+ - MODE=examples-pt3
+ - MODE=life-without-cocoapods
+ - MODE=framework
+script: ./build.sh $MODE
+
+#after_success:
+# - slather
+
+# whitelist
+branches:
+ only:
+ - master
diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit.xcworkspace/contents.xcworkspacedata b/submodules/AsyncDisplayKit/AsyncDisplayKit.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000000..574f0ec195
--- /dev/null
+++ b/submodules/AsyncDisplayKit/AsyncDisplayKit.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/submodules/AsyncDisplayKit/AsyncDisplayKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000000..18d981003d
--- /dev/null
+++ b/submodules/AsyncDisplayKit/AsyncDisplayKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/submodules/AsyncDisplayKit/AsyncDisplayKit.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000000..08de0be8d3
--- /dev/null
+++ b/submodules/AsyncDisplayKit/AsyncDisplayKit.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
+
+
+
diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/ASPagerFlowLayout.m b/submodules/AsyncDisplayKit/AsyncDisplayKit/ASPagerFlowLayout.m
new file mode 100644
index 0000000000..df8ce69868
--- /dev/null
+++ b/submodules/AsyncDisplayKit/AsyncDisplayKit/ASPagerFlowLayout.m
@@ -0,0 +1,96 @@
+//
+// ASPagerFlowLayout.m
+// AsyncDisplayKit
+//
+// Created by Levi McCallum on 2/12/16.
+//
+// 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.
+//
+
+#ifndef MINIMAL_ASDK
+#import
+
+@interface ASPagerFlowLayout () {
+ BOOL _didRotate;
+ CGRect _cachedCollectionViewBounds;
+ NSIndexPath *_currentIndexPath;
+}
+
+@end
+
+@implementation ASPagerFlowLayout
+
+- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
+{
+ NSInteger currentPage = ceil(proposedContentOffset.x / self.collectionView.bounds.size.width);
+ _currentIndexPath = [NSIndexPath indexPathForItem:currentPage inSection:0];
+
+ return [super targetContentOffsetForProposedContentOffset:proposedContentOffset withScrollingVelocity:velocity];
+}
+
+
+- (void)prepareForAnimatedBoundsChange:(CGRect)oldBounds
+{
+ // Cache the current page if a rotation did happen. This happens before the rotation animation
+ // is occuring and the bounds changed so we use this as an opportunity to cache the current index path
+ if (_cachedCollectionViewBounds.size.width != self.collectionView.bounds.size.width) {
+ _cachedCollectionViewBounds = self.collectionView.bounds;
+
+ // Figurring out current page based on the old bounds visible space
+ CGRect visibleRect = oldBounds;
+
+ CGFloat visibleXCenter = CGRectGetMidX(visibleRect);
+ NSArray *layoutAttributes = [self layoutAttributesForElementsInRect:visibleRect];
+ for (UICollectionViewLayoutAttributes *attributes in layoutAttributes) {
+ if ([attributes representedElementCategory] == UICollectionElementCategoryCell && attributes.center.x == visibleXCenter) {
+ _currentIndexPath = attributes.indexPath;
+ break;
+ }
+ }
+
+ _didRotate = YES;
+ }
+
+ [super prepareForAnimatedBoundsChange:oldBounds];
+}
+- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
+{
+ // Don't mess around if the user is interacting with the page node. Although if just a rotation happened we should
+ // try to use the current index path to not end up setting the target content offset to something in between pages
+ if (_didRotate || (!self.collectionView.isDecelerating && !self.collectionView.isTracking)) {
+ _didRotate = NO;
+ if (_currentIndexPath) {
+ return [self _targetContentOffsetForItemAtIndexPath:_currentIndexPath proposedContentOffset:proposedContentOffset];
+ }
+ }
+
+ return [super targetContentOffsetForProposedContentOffset:proposedContentOffset];
+}
+
+- (CGPoint)_targetContentOffsetForItemAtIndexPath:(NSIndexPath *)indexPath proposedContentOffset:(CGPoint)proposedContentOffset
+{
+ if ([self _dataSourceIsEmpty]) {
+ return proposedContentOffset;
+ }
+
+ UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:_currentIndexPath];
+ if (attributes == nil) {
+ return proposedContentOffset;
+ }
+
+ CGFloat xOffset = (CGRectGetWidth(self.collectionView.bounds) - CGRectGetWidth(attributes.frame)) / 2.0;
+ return CGPointMake(attributes.frame.origin.x - xOffset, proposedContentOffset.y);
+}
+
+- (BOOL)_dataSourceIsEmpty
+{
+ return ([self.collectionView numberOfSections] == 0 ||
+ [self.collectionView numberOfItemsInSection:0] == 0);
+}
+
+@end
+
+#endif
diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.h b/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.h
new file mode 100644
index 0000000000..8e0602911e
--- /dev/null
+++ b/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.h
@@ -0,0 +1,31 @@
+//
+// ASLayoutElementInspectorCell.h
+// AsyncDisplayKit
+//
+// Created by Hannah Troisi on 3/27/16.
+// Copyright © 2016 Facebook. All rights reserved.
+//
+
+#ifndef MINIMAL_ASDK
+
+#import
+
+typedef NS_ENUM(NSInteger, ASLayoutElementPropertyType) {
+ ASLayoutElementPropertyFlexGrow = 0,
+ ASLayoutElementPropertyFlexShrink,
+ ASLayoutElementPropertyAlignSelf,
+ ASLayoutElementPropertyFlexBasis,
+ ASLayoutElementPropertySpacingBefore,
+ ASLayoutElementPropertySpacingAfter,
+ ASLayoutElementPropertyAscender,
+ ASLayoutElementPropertyDescender,
+ ASLayoutElementPropertyCount
+};
+
+@interface ASLayoutElementInspectorCell : ASCellNode
+
+- (instancetype)initWithProperty:(ASLayoutElementPropertyType)property layoutElementToEdit:(id)layoutable NS_DESIGNATED_INITIALIZER;
+
+@end
+
+#endif
diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.m b/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.m
new file mode 100644
index 0000000000..7e5ca1ad9c
--- /dev/null
+++ b/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.m
@@ -0,0 +1,570 @@
+//
+// ASLayoutElementInspectorCell.m
+// AsyncDisplayKit
+//
+// Created by Hannah Troisi on 3/27/16.
+// Copyright © 2016 Facebook. All rights reserved.
+//
+#ifndef MINIMAL_ASDK
+#import
+#import
+
+typedef NS_ENUM(NSInteger, CellDataType) {
+ CellDataTypeBool,
+ CellDataTypeFloat,
+};
+
+__weak static ASLayoutElementInspectorCell *__currentlyOpenedCell = nil;
+
+@protocol InspectorCellEditingBubbleProtocol
+- (void)valueChangedToIndex:(NSUInteger)index;
+@end
+
+@interface ASLayoutElementInspectorCellEditingBubble : ASDisplayNode
+@property (nonatomic, strong, readwrite) id delegate;
+- (instancetype)initWithEnumOptions:(BOOL)yes enumStrings:(NSArray *)options currentOptionIndex:(NSUInteger)currentOption;
+- (instancetype)initWithSliderMinValue:(CGFloat)min maxValue:(CGFloat)max currentValue:(CGFloat)current
+;@end
+
+@interface ASLayoutElementInspectorCell ()
+@end
+
+@implementation ASLayoutElementInspectorCell
+{
+ ASLayoutElementPropertyType _propertyType;
+ CellDataType _dataType;
+ id _layoutElementToEdit;
+
+ ASButtonNode *_buttonNode;
+ ASTextNode *_textNode;
+ ASTextNode *_textNode2;
+
+ ASLayoutElementInspectorCellEditingBubble *_textBubble;
+}
+
+#pragma mark - Lifecycle
+
+- (instancetype)initWithProperty:(ASLayoutElementPropertyType)property layoutElementToEdit:(id)layoutElement
+{
+ self = [super init];
+ if (self) {
+
+ _propertyType = property;
+ _dataType = [ASLayoutElementInspectorCell dataTypeForProperty:property];
+ _layoutElementToEdit = layoutElement;
+
+ self.automaticallyManagesSubnodes = YES;
+
+ _buttonNode = [self makeBtnNodeWithTitle:[ASLayoutElementInspectorCell propertyStringForPropertyType:property]];
+ [_buttonNode addTarget:self action:@selector(buttonTapped:) forControlEvents:ASControlNodeEventTouchUpInside];
+
+ _textNode = [[ASTextNode alloc] init];
+ _textNode.attributedText = [ASLayoutElementInspectorCell propertyValueAttributedStringForProperty:property withLayoutElement:layoutElement];
+
+ [self updateButtonStateForProperty:property withLayoutElement:layoutElement];
+
+ _textNode2 = [[ASTextNode alloc] init];
+ _textNode2.attributedText = [ASLayoutElementInspectorCell propertyValueDetailAttributedStringForProperty:property withLayoutElement:layoutElement];
+
+ }
+ return self;
+}
+
+- (void)updateButtonStateForProperty:(ASLayoutElementPropertyType)property withLayoutElement:(id)layoutElement
+{
+ if (property == ASLayoutElementPropertyFlexGrow) {
+ _buttonNode.selected = layoutElement.style.flexGrow;
+ }
+ else if (property == ASLayoutElementPropertyFlexShrink) {
+ _buttonNode.selected = layoutElement.style.flexShrink;
+ }
+}
+
+- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
+{
+ ASStackLayoutSpec *horizontalSpec = [ASStackLayoutSpec horizontalStackLayoutSpec];
+ horizontalSpec.children = @[_buttonNode, _textNode];
+ horizontalSpec.style.flexGrow = 1.0;
+ horizontalSpec.alignItems = ASStackLayoutAlignItemsCenter;
+ horizontalSpec.justifyContent = ASStackLayoutJustifyContentSpaceBetween;
+
+ ASLayoutSpec *childSpec;
+ if (_textBubble) {
+ ASStackLayoutSpec *verticalSpec = [ASStackLayoutSpec verticalStackLayoutSpec];
+ verticalSpec.children = @[horizontalSpec, _textBubble];
+ verticalSpec.spacing = 8;
+ verticalSpec.style.flexGrow = 1.0;
+ _textBubble.style.flexGrow = 1.0;
+ childSpec = verticalSpec;
+ } else {
+ childSpec = horizontalSpec;
+ }
+ ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(2, 4, 2, 4) child:childSpec];
+ insetSpec.style.flexGrow =1.0;
+
+ return insetSpec;
+}
+
++ (NSAttributedString *)propertyValueAttributedStringForProperty:(ASLayoutElementPropertyType)property withLayoutElement:(id)layoutElement
+{
+ NSString *valueString;
+
+ switch (property) {
+ case ASLayoutElementPropertyFlexGrow:
+ valueString = layoutElement.style.flexGrow ? @"YES" : @"NO";
+ break;
+ case ASLayoutElementPropertyFlexShrink:
+ valueString = layoutElement.style.flexShrink ? @"YES" : @"NO";
+ break;
+ case ASLayoutElementPropertyAlignSelf:
+ valueString = [ASLayoutElementInspectorCell alignSelfEnumValueString:layoutElement.style.alignSelf];
+ break;
+ case ASLayoutElementPropertyFlexBasis:
+ if (layoutElement.style.flexBasis.unit && layoutElement.style.flexBasis.value) { // ENUM TYPE
+ valueString = [NSString stringWithFormat:@"%0.0f %@", layoutElement.style.flexBasis.value,
+ [ASLayoutElementInspectorCell ASRelativeDimensionEnumString:layoutElement.style.alignSelf]];
+ } else {
+ valueString = @"0 pts";
+ }
+ break;
+ case ASLayoutElementPropertySpacingBefore:
+ valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.spacingBefore];
+ break;
+ case ASLayoutElementPropertySpacingAfter:
+ valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.spacingAfter];
+ break;
+ case ASLayoutElementPropertyAscender:
+ valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.ascender];
+ break;
+ case ASLayoutElementPropertyDescender:
+ valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.descender];
+ break;
+ default:
+ valueString = @"?";
+ break;
+ }
+ return [ASLayoutElementInspectorCell attributedStringFromString:valueString];
+}
+
++ (NSAttributedString *)propertyValueDetailAttributedStringForProperty:(ASLayoutElementPropertyType)property withLayoutElement:(id)layoutElement
+{
+ NSString *valueString;
+
+ switch (property) {
+ case ASLayoutElementPropertyFlexGrow:
+ case ASLayoutElementPropertyFlexShrink:
+ case ASLayoutElementPropertyAlignSelf:
+ case ASLayoutElementPropertyFlexBasis:
+ case ASLayoutElementPropertySpacingBefore:
+ case ASLayoutElementPropertySpacingAfter:
+ case ASLayoutElementPropertyAscender:
+ case ASLayoutElementPropertyDescender:
+ default:
+ return nil;
+ }
+ return [ASLayoutElementInspectorCell attributedStringFromString:valueString];
+}
+
+- (void)endEditingValue
+{
+ _textBubble = nil;
+ __currentlyOpenedCell = nil;
+ _buttonNode.selected = NO;
+ [self setNeedsLayout];
+}
+
+- (void)beginEditingValue
+{
+ _textBubble.delegate = self;
+ __currentlyOpenedCell = self;
+ [self setNeedsLayout];
+}
+
+- (void)valueChangedToIndex:(NSUInteger)index
+{
+ switch (_propertyType) {
+
+ case ASLayoutElementPropertyAlignSelf:
+ _layoutElementToEdit.style.alignSelf = (ASStackLayoutAlignSelf)index;
+ _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[ASLayoutElementInspectorCell alignSelfEnumValueString:index]];
+ break;
+
+ case ASLayoutElementPropertySpacingBefore:
+ _layoutElementToEdit.style.spacingBefore = (CGFloat)index;
+ _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingBefore]];
+ break;
+
+ case ASLayoutElementPropertySpacingAfter:
+ _layoutElementToEdit.style.spacingAfter = (CGFloat)index;
+ _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingAfter]];
+ break;
+
+ case ASLayoutElementPropertyAscender:
+ _layoutElementToEdit.style.ascender = (CGFloat)index;
+ _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.ascender]];
+ break;
+
+ case ASLayoutElementPropertyDescender:
+ _layoutElementToEdit.style.descender = (CGFloat)index;
+ _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.descender]];
+ break;
+
+ default:
+ break;
+ }
+
+ [self setNeedsLayout];
+}
+
+#pragma mark - gesture handling
+
+- (void)buttonTapped:(ASButtonNode *)sender
+{
+ BOOL selfIsEditing = (self == __currentlyOpenedCell);
+ [__currentlyOpenedCell endEditingValue];
+ if (selfIsEditing) {
+ sender.selected = NO;
+ return;
+ }
+
+// NSUInteger currentAlignSelfValue;
+// NSUInteger nextAlignSelfValue;
+// CGFloat newValue;
+
+ sender.selected = !sender.selected;
+ switch (_propertyType) {
+
+ case ASLayoutElementPropertyFlexGrow:
+ _layoutElementToEdit.style.flexGrow = sender.isSelected ? 1.0 : 0.0;
+ _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:sender.selected ? @"YES" : @"NO"];
+ break;
+
+ case ASLayoutElementPropertyFlexShrink:
+ _layoutElementToEdit.style.flexShrink = sender.isSelected ? 1.0 : 0.0;
+ _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:sender.selected ? @"YES" : @"NO"];
+ break;
+
+ case ASLayoutElementPropertyAlignSelf:
+ _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithEnumOptions:YES
+ enumStrings:[ASLayoutElementInspectorCell alignSelfEnumStringArray]
+ currentOptionIndex:_layoutElementToEdit.style.alignSelf];
+
+ [self beginEditingValue];
+// if ([self layoutSpec]) {
+// currentAlignSelfValue = [[self layoutSpec] alignSelf];
+// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0;
+// [[self layoutSpec] setAlignSelf:nextAlignSelfValue];
+//
+// } else if ([self node]) {
+// currentAlignSelfValue = [[self node] alignSelf];
+// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0;
+// [[self node] setAlignSelf:nextAlignSelfValue];
+// }
+ break;
+
+ case ASLayoutElementPropertySpacingBefore:
+ _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.spacingBefore];
+ [self beginEditingValue];
+ _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingBefore]];
+ break;
+
+ case ASLayoutElementPropertySpacingAfter:
+ _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.spacingAfter];
+ [self beginEditingValue];
+ _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingAfter]];
+ break;
+
+
+ case ASLayoutElementPropertyAscender:
+ _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.ascender];
+ [self beginEditingValue];
+ _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.ascender]];
+ break;
+
+ case ASLayoutElementPropertyDescender:
+ _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.descender];
+ [self beginEditingValue];
+ _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.descender]];
+ break;
+
+ default:
+ break;
+ }
+ [self setNeedsLayout];
+}
+
+#pragma mark - cast layoutElementToEdit
+
+- (ASDisplayNode *)node
+{
+ if (_layoutElementToEdit.layoutElementType == ASLayoutElementTypeDisplayNode) {
+ return (ASDisplayNode *)_layoutElementToEdit;
+ }
+ return nil;
+}
+
+- (ASLayoutSpec *)layoutSpec
+{
+ if (_layoutElementToEdit.layoutElementType == ASLayoutElementTypeLayoutSpec) {
+ return (ASLayoutSpec *)_layoutElementToEdit;
+ }
+ return nil;
+}
+
+#pragma mark - data / property type helper methods
+
++ (CellDataType)dataTypeForProperty:(ASLayoutElementPropertyType)property
+{
+ switch (property) {
+
+ case ASLayoutElementPropertyFlexGrow:
+ case ASLayoutElementPropertyFlexShrink:
+ return CellDataTypeBool;
+
+ case ASLayoutElementPropertySpacingBefore:
+ case ASLayoutElementPropertySpacingAfter:
+ case ASLayoutElementPropertyAscender:
+ case ASLayoutElementPropertyDescender:
+ return CellDataTypeFloat;
+
+ default:
+ break;
+ }
+ return CellDataTypeBool;
+}
+
++ (NSString *)propertyStringForPropertyType:(ASLayoutElementPropertyType)property
+{
+ NSString *string;
+ switch (property) {
+ case ASLayoutElementPropertyFlexGrow:
+ string = @"FlexGrow";
+ break;
+ case ASLayoutElementPropertyFlexShrink:
+ string = @"FlexShrink";
+ break;
+ case ASLayoutElementPropertyAlignSelf:
+ string = @"AlignSelf";
+ break;
+ case ASLayoutElementPropertyFlexBasis:
+ string = @"FlexBasis";
+ break;
+ case ASLayoutElementPropertySpacingBefore:
+ string = @"SpacingBefore";
+ break;
+ case ASLayoutElementPropertySpacingAfter:
+ string = @"SpacingAfter";
+ break;
+ case ASLayoutElementPropertyAscender:
+ string = @"Ascender";
+ break;
+ case ASLayoutElementPropertyDescender:
+ string = @"Descender";
+ break;
+ default:
+ string = @"Unknown";
+ break;
+ }
+ return string;
+}
+
++ (NSDictionary *)alignSelfTypeNames
+{
+ return @{@(ASStackLayoutAlignSelfAuto) : @"Auto",
+ @(ASStackLayoutAlignSelfStart) : @"Start",
+ @(ASStackLayoutAlignSelfEnd) : @"End",
+ @(ASStackLayoutAlignSelfCenter) : @"Center",
+ @(ASStackLayoutAlignSelfStretch) : @"Stretch"};
+}
+
++ (NSString *)alignSelfEnumValueString:(NSUInteger)type
+{
+ return [[self class] alignSelfTypeNames][@(type)];
+}
+
++ (NSArray *)alignSelfEnumStringArray
+{
+ return @[@"ASStackLayoutAlignSelfAuto",
+ @"ASStackLayoutAlignSelfStart",
+ @"ASStackLayoutAlignSelfEnd",
+ @"ASStackLayoutAlignSelfCenter",
+ @"ASStackLayoutAlignSelfStretch"];
+}
+
++ (NSDictionary *)ASRelativeDimensionTypeNames
+{
+ return @{@(ASDimensionUnitPoints) : @"pts",
+ @(ASDimensionUnitFraction) : @"%"};
+}
+
++ (NSString *)ASRelativeDimensionEnumString:(NSUInteger)type
+{
+ return [[self class] ASRelativeDimensionTypeNames][@(type)];
+}
+
+#pragma mark - formatting helper methods
+
++ (NSAttributedString *)attributedStringFromString:(NSString *)string
+{
+ return [ASLayoutElementInspectorCell attributedStringFromString:string withTextColor:[UIColor whiteColor]];
+}
+
++ (NSAttributedString *)attributedStringFromString:(NSString *)string withTextColor:(nullable UIColor *)color
+{
+ NSDictionary *attributes = @{NSForegroundColorAttributeName : color,
+ NSFontAttributeName : [UIFont fontWithName:@"Menlo-Regular" size:12]};
+
+ return [[NSAttributedString alloc] initWithString:string attributes:attributes];
+}
+
+- (ASButtonNode *)makeBtnNodeWithTitle:(NSString *)title
+{
+ UIColor *orangeColor = [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1];
+ UIImage *orangeStretchBtnImg = [ASLayoutElementInspectorCell imageForButtonWithBackgroundColor:orangeColor
+ borderColor:[UIColor whiteColor]
+ borderWidth:3];
+ UIImage *greyStretchBtnImg = [ASLayoutElementInspectorCell imageForButtonWithBackgroundColor:[UIColor darkGrayColor]
+ borderColor:[UIColor lightGrayColor]
+ borderWidth:3];
+ UIImage *clearStretchBtnImg = [ASLayoutElementInspectorCell imageForButtonWithBackgroundColor:[UIColor clearColor]
+ borderColor:[UIColor whiteColor]
+ borderWidth:3];
+ ASButtonNode *btn = [[ASButtonNode alloc] init];
+ btn.contentEdgeInsets = UIEdgeInsetsMake(5, 5, 5, 5);
+ [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:title] forState:ASControlStateNormal];
+ [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:title withTextColor:[UIColor lightGrayColor]] forState:ASControlStateDisabled];
+ [btn setBackgroundImage:clearStretchBtnImg forState:ASControlStateNormal];
+ [btn setBackgroundImage:orangeStretchBtnImg forState:ASControlStateSelected];
+ [btn setBackgroundImage:greyStretchBtnImg forState:ASControlStateDisabled];
+
+ return btn;
+}
+
+#define CORNER_RADIUS 3
++ (UIImage *)imageForButtonWithBackgroundColor:(UIColor *)backgroundColor borderColor:(UIColor *)borderColor borderWidth:(CGFloat)width
+{
+ CGSize unstretchedSize = CGSizeMake(2 * CORNER_RADIUS + 1, 2 * CORNER_RADIUS + 1);
+ CGRect rect = (CGRect) {CGPointZero, unstretchedSize};
+ UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:CORNER_RADIUS];
+
+ // create a graphics context for the following status button
+ UIGraphicsBeginImageContextWithOptions(unstretchedSize, NO, 0);
+
+ [path addClip];
+ [backgroundColor setFill];
+ [path fill];
+
+ path.lineWidth = width;
+ [borderColor setStroke];
+ [path stroke];
+
+ UIImage *btnImage = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+
+ return [btnImage stretchableImageWithLeftCapWidth:CORNER_RADIUS topCapHeight:CORNER_RADIUS];
+}
+
+@end
+
+
+
+@implementation ASLayoutElementInspectorCellEditingBubble
+{
+ NSMutableArray *_textNodes;
+ ASDisplayNode *_slider;
+}
+
+- (instancetype)initWithEnumOptions:(BOOL)yes enumStrings:(NSArray *)options currentOptionIndex:(NSUInteger)currentOption
+{
+ self = [super init];
+ if (self) {
+ self.automaticallyManagesSubnodes = YES;
+ self.backgroundColor = [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1];
+
+ _textNodes = [[NSMutableArray alloc] init];
+ int index = 0;
+ for (NSString *optionStr in options) {
+ ASButtonNode *btn = [[ASButtonNode alloc] init];
+ [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:optionStr] forState:ASControlStateNormal];
+ [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:optionStr withTextColor:[UIColor redColor]]
+ forState:ASControlStateSelected];
+ [btn addTarget:self action:@selector(enumOptionSelected:) forControlEvents:ASControlNodeEventTouchUpInside];
+ btn.selected = (index == currentOption) ? YES : NO;
+ [_textNodes addObject:btn];
+ index++;
+ }
+ }
+ return self;
+}
+
+- (instancetype)initWithSliderMinValue:(CGFloat)min maxValue:(CGFloat)max currentValue:(CGFloat)current
+{
+ if (self = [super init]) {
+ self.userInteractionEnabled = YES;
+ self.automaticallyManagesSubnodes = YES;
+ self.backgroundColor = [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1];
+
+ __weak id weakSelf = self;
+ _slider = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull{
+ UISlider *slider = [[UISlider alloc] init];
+ slider.minimumValue = min;
+ slider.maximumValue = max;
+ slider.value = current;
+ [slider addTarget:weakSelf action:@selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged];
+
+ return slider;
+ }];
+ _slider.userInteractionEnabled = YES;
+ }
+ return self;
+}
+
+- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
+{
+ _slider.style.preferredSize = CGSizeMake(constrainedSize.max.width, 25);
+
+ NSMutableArray *children = [[NSMutableArray alloc] init];
+ if (_textNodes) {
+ ASStackLayoutSpec *textStack = [ASStackLayoutSpec verticalStackLayoutSpec];
+ textStack.children = _textNodes;
+ textStack.spacing = 2;
+ [children addObject:textStack];
+ }
+ if (_slider) {
+ _slider.style.flexGrow = 1.0;
+ [children addObject:_slider];
+ }
+
+ ASStackLayoutSpec *verticalStackSpec = [ASStackLayoutSpec verticalStackLayoutSpec];
+ verticalStackSpec.children = children;
+ verticalStackSpec.spacing = 2;
+ verticalStackSpec.style.flexGrow = 1.0;
+ verticalStackSpec.style.alignSelf = ASStackLayoutAlignSelfStretch;
+
+ ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(8, 8, 8, 8) child:verticalStackSpec];
+
+ return insetSpec;
+}
+
+#pragma mark - gesture handling
+- (void)enumOptionSelected:(ASButtonNode *)sender
+{
+ sender.selected = !sender.selected;
+ for (ASButtonNode *node in _textNodes) {
+ if (node != sender) {
+ node.selected = NO;
+ }
+ }
+ [self.delegate valueChangedToIndex:[_textNodes indexOfObject:sender]];
+ [self setNeedsLayout];
+}
+
+- (void)sliderValueChanged:(UISlider *)sender
+{
+ [self.delegate valueChangedToIndex:roundf(sender.value)];
+}
+
+@end
+
+
+#endif
diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.m b/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.m
new file mode 100644
index 0000000000..b71c2f7413
--- /dev/null
+++ b/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.m
@@ -0,0 +1,426 @@
+//
+// ASLayoutElementInspectorNode.m
+// Sample
+//
+// Created by Hannah Troisi on 3/19/16.
+// Copyright © 2016 Facebook. All rights reserved.
+//
+
+#import
+#ifndef MINIMAL_ASDK
+#import
+#endif
+#import
+#import
+#import
+#import
+#import
+
+@interface ASLayoutElementInspectorNode ()
+#ifndef MINIMAL_ASDK
+
+#endif
+@end
+
+@implementation ASLayoutElementInspectorNode
+{
+#ifndef MINIMAL_ASDK
+ ASTableNode *_tableNode;
+#endif
+}
+
+#pragma mark - class methods
++ (instancetype)sharedInstance
+{
+ static ASLayoutElementInspectorNode *__inspector = nil;
+
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ __inspector = [[ASLayoutElementInspectorNode alloc] init];
+ });
+
+ return __inspector;
+}
+
+#pragma mark - lifecycle
+- (instancetype)init
+{
+ self = [super init];
+ if (self) {
+
+#ifndef MINIMAL_ASDK
+ _tableNode = [[ASTableNode alloc] init];
+ _tableNode.delegate = self;
+ _tableNode.dataSource = self;
+
+ [self addSubnode:_tableNode]; // required because of manual layout
+#endif
+ }
+ return self;
+}
+
+- (void)didLoad
+{
+ [super didLoad];
+#ifndef MINIMAL_ASDK
+ _tableNode.view.backgroundColor = [UIColor colorWithRed:40/255.0 green:43/255.0 blue:53/255.0 alpha:1];
+ _tableNode.view.separatorStyle = UITableViewCellSeparatorStyleNone;
+ _tableNode.view.allowsSelection = NO;
+ _tableNode.view.sectionHeaderHeight = 40;
+#endif
+}
+
+- (void)layout
+{
+ [super layout];
+#ifndef MINIMAL_ASDK
+ _tableNode.frame = self.bounds;
+#endif
+}
+
+#pragma mark - intstance methods
+- (void)setLayoutElementToEdit:(id)layoutElementToEdit
+{
+ if (_layoutElementToEdit != layoutElementToEdit) {
+ _layoutElementToEdit = layoutElementToEdit;
+ }
+#ifndef MINIMAL_ASDK
+ [_tableNode reloadData];
+#endif
+}
+
+#pragma mark - ASTableDataSource
+
+#ifndef MINIMAL_ASDK
+- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ if (indexPath.section == 0) {
+ NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1],
+ NSFontAttributeName : [UIFont fontWithName:@"Menlo-Regular" size:12]};
+ ASTextCellNode *textCell = [[ASTextCellNode alloc] initWithAttributes:attributes insets:UIEdgeInsetsMake(0, 4, 0, 0)];
+ textCell.text = [_layoutElementToEdit description];
+ return textCell;
+ } else {
+ return [[ASLayoutElementInspectorCell alloc] initWithProperty:(ASLayoutElementPropertyType)indexPath.row layoutElementToEdit:_layoutElementToEdit];
+ }
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+ if (section == 0) {
+ return 1;
+ } else {
+ return ASLayoutElementPropertyCount;
+ }
+}
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+ return 2;
+}
+
+- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
+{
+ UILabel *headerTitle = [[UILabel alloc] initWithFrame:CGRectZero];
+
+ NSString *title;
+ if (section == 0) {
+ title = @" Item";
+ } else {
+ title = @" Properties";
+ }
+
+ NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor whiteColor],
+ NSFontAttributeName : [UIFont fontWithName:@"Menlo-Bold" size:12]};
+ headerTitle.attributedText = [[NSAttributedString alloc] initWithString:title attributes:attributes];
+
+ return headerTitle;
+}
+
+#endif
+
+//- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
+//{
+// // navigate layout hierarchy
+//
+// _parentNodeNavBtn.alignSelf = ASStackLayoutAlignSelfCenter;
+// _childNodeNavBtn.alignSelf = ASStackLayoutAlignSelfCenter;
+//
+// ASStackLayoutSpec *horizontalStackNav = [ASStackLayoutSpec horizontalStackLayoutSpec];
+// horizontalStackNav.style.flexGrow = 1.0;
+// horizontalStackNav.alignSelf = ASStackLayoutAlignSelfCenter;
+// horizontalStackNav.children = @[_siblingNodeLefttNavBtn, _siblingNodeRightNavBtn];
+//
+// ASStackLayoutSpec *horizontalStack = [ASStackLayoutSpec horizontalStackLayoutSpec];
+// horizontalStack.style.flexGrow = 1.0;
+// ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init];
+//
+// spacer.style.flexGrow = 1.0;
+// horizontalStack.children = @[_flexGrowBtn, spacer];
+// _flexGrowValue.alignSelf = ASStackLayoutAlignSelfEnd; // FIXME: make framework give a warning if you use ASAlignmentBottom!!!!!
+//
+// ASStackLayoutSpec *horizontalStack2 = [ASStackLayoutSpec horizontalStackLayoutSpec];
+// horizontalStack2.style.flexGrow = 1.0;
+// horizontalStack2.children = @[_flexShrinkBtn, spacer];
+// _flexShrinkValue.alignSelf = ASStackLayoutAlignSelfEnd;
+//
+// ASStackLayoutSpec *horizontalStack3 = [ASStackLayoutSpec horizontalStackLayoutSpec];
+// horizontalStack3.style.flexGrow = 1.0;
+// horizontalStack3.children = @[_flexBasisBtn, spacer, _flexBasisValue];
+// _flexBasisValue.alignSelf = ASStackLayoutAlignSelfEnd;
+//
+// ASStackLayoutSpec *itemDescriptionStack = [ASStackLayoutSpec verticalStackLayoutSpec];
+// itemDescriptionStack.children = @[_itemDescription];
+// itemDescriptionStack.spacing = 5;
+// itemDescriptionStack.style.flexGrow = 1.0;
+//
+// ASStackLayoutSpec *layoutableStack = [ASStackLayoutSpec verticalStackLayoutSpec];
+// layoutableStack.children = @[_layoutablePropertiesSectionTitle, horizontalStack, horizontalStack2, horizontalStack3, _alignSelfBtn];
+// layoutableStack.spacing = 5;
+// layoutableStack.style.flexGrow = 1.0;
+//
+// ASStackLayoutSpec *layoutSpecStack = [ASStackLayoutSpec verticalStackLayoutSpec];
+// layoutSpecStack.children = @[_layoutSpecPropertiesSectionTitle, _alignItemsBtn];
+// layoutSpecStack.spacing = 5;
+// layoutSpecStack.style.flexGrow = 1.0;
+//
+// ASStackLayoutSpec *debugHelpStack = [ASStackLayoutSpec verticalStackLayoutSpec];
+// debugHelpStack.children = @[_debugSectionTitle, _vizNodeInsetSizeBtn, _vizNodeBordersBtn];
+// debugHelpStack.spacing = 5;
+// debugHelpStack.style.flexGrow = 1.0;
+//
+// ASStackLayoutSpec *verticalLayoutableStack = [ASStackLayoutSpec verticalStackLayoutSpec];
+// verticalLayoutableStack.style.flexGrow = 1.0;
+// verticalLayoutableStack.spacing = 20;
+// verticalLayoutableStack.children = @[_parentNodeNavBtn, horizontalStackNav, _childNodeNavBtn, itemDescriptionStack, layoutableStack, layoutSpecStack, debugHelpStack];
+// verticalLayoutableStack.alignItems = ASStackLayoutAlignItemsStretch; // stretch headerStack to fill horizontal space
+//
+// ASLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(100, 10, 10, 10) child:verticalLayoutableStack];
+// insetSpec.style.flexGrow = 1.0;
+// return insetSpec;
+//}
+//
+//#pragma mark - configure Inspector node for layoutable
+//- (void)updateInspectorWithLayoutable
+//{
+// _itemDescription.attributedText = [self attributedStringFromLayoutable:_layoutElementToEdit];
+//
+// if ([self node]) {
+// UIColor *nodeBackgroundColor = [[self node] backgroundColor];
+// UIImage *colorBtnImg = [ASLayoutElementInspectorNode imageForButtonWithBackgroundColor:nodeBackgroundColor
+// borderColor:[UIColor whiteColor]
+// borderWidth:3];
+// [_itemBackgroundColorBtn setBackgroundImage:colorBtnImg forState:ASControlStateNormal];
+// } else {
+// _itemBackgroundColorBtn.enabled = NO;
+// }
+//
+// _flexGrowBtn.selected = [self.layoutElementToEdit flexGrow];
+// _flexGrowValue.attributedText = [self attributedStringFromString: (_flexGrowBtn.selected) ? @"YES" : @"NO"];
+//
+// _flexShrinkBtn.selected = self.layoutElementToEdit.style.flexShrink;
+// _flexShrinkValue.attributedText = [self attributedStringFromString: (_flexShrinkBtn.selected) ? @"YES" : @"NO"];
+//
+// // _flexBasisBtn.selected = self.layoutElementToEdit.style.flexShrink;
+// // _flexBasisValue.attributedText = [self attributedStringFromString: (_flexBasisBtn.selected) ? @"YES" : @"NO"];
+//
+//
+// NSUInteger alignSelfValue = [self.layoutElementToEdit alignSelf];
+// NSString *newTitle = [@"alignSelf:" stringByAppendingString:[self alignSelfName:alignSelfValue]];
+// [_alignSelfBtn setAttributedTitle:[self attributedStringFromString:newTitle] forState:ASControlStateNormal];
+//
+// if ([self layoutSpec]) {
+// _alignItemsBtn.enabled = YES;
+//// NSUInteger alignItemsValue = [[self layoutSpec] alignItems];
+//// newTitle = [@"alignItems:" stringByAppendingString:[self alignSelfName:alignItemsValue]];
+//// [_alignItemsBtn setAttributedTitle:[self attributedStringFromString:newTitle] forState:ASControlStateNormal];
+// }
+//
+// [self setNeedsLayout];
+//}
+
+
+//- (void)enableInspectorNodesForLayoutable
+//{
+// if ([self layoutSpec]) {
+//
+// _itemBackgroundColorBtn.enabled = YES;
+// _flexGrowBtn.enabled = YES;
+// _flexShrinkBtn.enabled = YES;
+// _flexBasisBtn.enabled = YES;
+// _alignSelfBtn.enabled = YES;
+// _spacingBeforeBtn.enabled = YES;
+// _spacingAfterBtn.enabled = YES;
+// _alignItemsBtn.enabled = YES;
+//
+// } else if ([self node]) {
+//
+// _itemBackgroundColorBtn.enabled = YES;
+// _flexGrowBtn.enabled = YES;
+// _flexShrinkBtn.enabled = YES;
+// _flexBasisBtn.enabled = YES;
+// _alignSelfBtn.enabled = YES;
+// _spacingBeforeBtn.enabled = YES;
+// _spacingAfterBtn.enabled = YES;
+// _alignItemsBtn.enabled = NO;
+//
+// } else {
+//
+// _itemBackgroundColorBtn.enabled = NO;
+// _flexGrowBtn.enabled = NO;
+// _flexShrinkBtn.enabled = NO;
+// _flexBasisBtn.enabled = NO;
+// _alignSelfBtn.enabled = NO;
+// _spacingBeforeBtn.enabled = NO;
+// _spacingAfterBtn.enabled = NO;
+// _alignItemsBtn.enabled = YES;
+// }
+//}
+
+//+ (NSDictionary *)alignSelfTypeNames
+//{
+// return @{@(ASStackLayoutAlignSelfAuto) : @"Auto",
+// @(ASStackLayoutAlignSelfStart) : @"Start",
+// @(ASStackLayoutAlignSelfEnd) : @"End",
+// @(ASStackLayoutAlignSelfCenter) : @"Center",
+// @(ASStackLayoutAlignSelfStretch) : @"Stretch"};
+//}
+//
+//- (NSString *)alignSelfName:(NSUInteger)type
+//{
+// return [[self class] alignSelfTypeNames][@(type)];
+//}
+//
+//+ (NSDictionary *)alignItemTypeNames
+//{
+// return @{@(ASStackLayoutAlignItemsBaselineFirst) : @"BaselineFirst",
+// @(ASStackLayoutAlignItemsBaselineLast) : @"BaselineLast",
+// @(ASStackLayoutAlignItemsCenter) : @"Center",
+// @(ASStackLayoutAlignItemsEnd) : @"End",
+// @(ASStackLayoutAlignItemsStart) : @"Start",
+// @(ASStackLayoutAlignItemsStretch) : @"Stretch"};
+//}
+//
+//- (NSString *)alignItemName:(NSUInteger)type
+//{
+// return [[self class] alignItemTypeNames][@(type)];
+//}
+
+//#pragma mark - gesture handling
+//- (void)changeColor:(ASButtonNode *)sender
+//{
+// if ([self node]) {
+// NSArray *colorArray = @[[UIColor orangeColor],
+// [UIColor redColor],
+// [UIColor greenColor],
+// [UIColor purpleColor]];
+//
+// UIColor *nodeBackgroundColor = [(ASDisplayNode *)self.layoutElementToEdit backgroundColor];
+//
+// NSUInteger colorIndex = [colorArray indexOfObject:nodeBackgroundColor];
+// colorIndex = (colorIndex + 1 < [colorArray count]) ? colorIndex + 1 : 0;
+//
+// [[self node] setBackgroundColor: [colorArray objectAtIndex:colorIndex]];
+// }
+//
+// [self updateInspectorWithLayoutable];
+//}
+//
+//- (void)setFlexGrowValue:(ASButtonNode *)sender
+//{
+// [sender setSelected:!sender.isSelected]; // FIXME: fix ASControlNode documentation that this is automatic - unlike highlighted, it is up to the application to decide when a button should be selected or not. Selected is a more persistant thing and highlighted is for the moment, like as a user has a finger on it,
+//
+// if ([self layoutSpec]) {
+// [[self layoutSpec] setFlexGrow:sender.isSelected];
+// } else if ([self node]) {
+// [[self node] setFlexGrow:sender.isSelected];
+// }
+//
+// [self updateInspectorWithLayoutable];
+//}
+//
+//- (void)setFlexShrinkValue:(ASButtonNode *)sender
+//{
+// [sender setSelected:!sender.isSelected]; // FIXME: fix ASControlNode documentation that this is automatic - unlike highlighted, it is up to the application to decide when a button should be selected or not. Selected is a more persistant thing and highlighted is for the moment, like as a user has a finger on it,
+//
+// if ([self layoutSpec]) {
+// [[self layoutSpec] setFlexShrink:sender.isSelected];
+// } else if ([self node]) {
+// [[self node] setFlexShrink:sender.isSelected];
+// }
+//
+// [self updateInspectorWithLayoutable];
+//}
+//
+//- (void)setAlignSelfValue:(ASButtonNode *)sender
+//{
+// NSUInteger currentAlignSelfValue;
+// NSUInteger nextAlignSelfValue;
+//
+// if ([self layoutSpec]) {
+// currentAlignSelfValue = [[self layoutSpec] alignSelf];
+// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0;
+// [[self layoutSpec] setAlignSelf:nextAlignSelfValue];
+//
+// } else if ([self node]) {
+// currentAlignSelfValue = [[self node] alignSelf];
+// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0;
+// [[self node] setAlignSelf:nextAlignSelfValue];
+// }
+//
+// [self updateInspectorWithLayoutable];
+//}
+//
+//- (void)setAlignItemsValue:(ASButtonNode *)sender
+//{
+// NSUInteger currentAlignItemsValue;
+// NSUInteger nextAlignItemsValue;
+//
+// if ([self layoutSpec]) {
+// currentAlignItemsValue = [[self layoutSpec] alignSelf];
+// nextAlignItemsValue = (currentAlignItemsValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignItemsValue + 1 : 0;
+//// [[self layoutSpec] setAlignItems:nextAlignItemsValue];
+//
+// } else if ([self node]) {
+// currentAlignItemsValue = [[self node] alignSelf];
+// nextAlignItemsValue = (currentAlignItemsValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignItemsValue + 1 : 0;
+//// [[self node] setAlignItems:nextAlignItemsValue];
+// }
+//
+// [self updateInspectorWithLayoutable];
+//}
+//- (void)setFlexBasisValue:(ASButtonNode *)sender
+//{
+// [sender setSelected:!sender.isSelected]; // FIXME: fix ASControlNode documentation that this is automatic - unlike highlighted, it is up to the application to decide when a button should be selected or not. Selected is a more persistant thing and highlighted is for the moment, like as a user has a finger on it,
+// FIXME: finish
+//}
+//
+//- (void)setVizNodeInsets:(ASButtonNode *)sender
+//{
+// BOOL newState = !sender.selected;
+//
+// if (newState == YES) {
+// self.vizNodeInsetSize = 0;
+// [self.delegate toggleVisualization:NO]; // FIXME
+// [self.delegate toggleVisualization:YES]; // FIXME
+// _vizNodeBordersBtn.selected = YES;
+//
+// } else {
+// self.vizNodeInsetSize = 10;
+// [self.delegate toggleVisualization:NO]; // FIXME
+// [self.delegate toggleVisualization:YES]; // FIXME
+// }
+//
+// sender.selected = newState;
+//}
+//
+//- (void)setVizNodeBorders:(ASButtonNode *)sender
+//{
+// BOOL newState = !sender.selected;
+//
+// [self.delegate toggleVisualization:newState]; // FIXME
+//
+// sender.selected = newState;
+//}
+
+@end
diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASChangeSetDataController.h b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASChangeSetDataController.h
new file mode 100644
index 0000000000..49df1dc24d
--- /dev/null
+++ b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASChangeSetDataController.h
@@ -0,0 +1,31 @@
+//
+// ASChangeSetDataController.h
+// AsyncDisplayKit
+//
+// Created by Huy Nguyen on 19/10/15.
+//
+// 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.
+//
+
+#ifndef MINIMAL_ASDK
+
+#import
+
+/**
+ * @abstract Subclass of ASDataController that simulates ordering of operations in batch updates defined in UITableView and UICollectionView.
+ *
+ * @discussion The ordering is achieved by using _ASHierarchyChangeSet to enqueue and sort operations.
+ * More information about the ordering and the index paths used for operations can be found here:
+ * https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/TableView_iPhone/ManageInsertDeleteRow/ManageInsertDeleteRow.html#//apple_ref/doc/uid/TP40007451-CH10-SW17
+ *
+ * @see ASDataController
+ * @see _ASHierarchyChangeSet
+ */
+@interface ASChangeSetDataController : ASDataController
+
+@end
+
+#endif
diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASChangeSetDataController.mm b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASChangeSetDataController.mm
new file mode 100644
index 0000000000..c41820dbb0
--- /dev/null
+++ b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASChangeSetDataController.mm
@@ -0,0 +1,212 @@
+//
+// ASChangeSetDataController.m
+// AsyncDisplayKit
+//
+// Created by Huy Nguyen on 19/10/15.
+//
+// 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.
+//
+
+#ifndef MINIMAL_ASDK
+
+#import "ASChangeSetDataController.h"
+#import "_ASHierarchyChangeSet.h"
+#import "ASAssert.h"
+#import "ASDataController+Subclasses.h"
+
+@implementation ASChangeSetDataController {
+ NSInteger _changeSetBatchUpdateCounter;
+ _ASHierarchyChangeSet *_changeSet;
+}
+
+- (void)dealloc
+{
+ ASDisplayNodeCAssert(_changeSetBatchUpdateCounter == 0, @"ASChangeSetDataController deallocated in the middle of a batch update.");
+}
+
+#pragma mark - Batching (External API)
+
+- (void)beginUpdates
+{
+ ASDisplayNodeAssertMainThread();
+ if (_changeSetBatchUpdateCounter <= 0) {
+ _changeSetBatchUpdateCounter = 0;
+ _changeSet = [[_ASHierarchyChangeSet alloc] initWithOldData:[self itemCountsFromDataSource]];
+ }
+ _changeSetBatchUpdateCounter++;
+}
+
+- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion
+{
+ ASDisplayNodeAssertMainThread();
+ _changeSetBatchUpdateCounter--;
+
+ // Prevent calling endUpdatesAnimated:completion: in an unbalanced way
+ NSAssert(_changeSetBatchUpdateCounter >= 0, @"endUpdatesAnimated:completion: called without having a balanced beginUpdates call");
+
+ [_changeSet addCompletionHandler:completion];
+ if (_changeSetBatchUpdateCounter == 0) {
+ void (^batchCompletion)(BOOL) = _changeSet.completionHandler;
+
+ /**
+ * If the initial reloadData has not been called, just bail because we don't have
+ * our old data source counts.
+ * See ASUICollectionViewTests.testThatIssuingAnUpdateBeforeInitialReloadIsUnacceptable
+ * For the issue that UICollectionView has that we're choosing to workaround.
+ */
+ if (!self.initialReloadDataHasBeenCalled) {
+ if (batchCompletion != nil) {
+ batchCompletion(YES);
+ }
+ _changeSet = nil;
+ return;
+ }
+
+ [self invalidateDataSourceItemCounts];
+
+ // Attempt to mark the update completed. This is when update validation will occur inside the changeset.
+ // If an invalid update exception is thrown, we catch it and inject our "validationErrorSource" object,
+ // which is the table/collection node's data source, into the exception reason to help debugging.
+ @try {
+ [_changeSet markCompletedWithNewItemCounts:[self itemCountsFromDataSource]];
+ } @catch (NSException *e) {
+ id responsibleDataSource = self.validationErrorSource;
+ if (e.name == ASCollectionInvalidUpdateException && responsibleDataSource != nil) {
+ [NSException raise:ASCollectionInvalidUpdateException format:@"%@: %@", [responsibleDataSource class], e.reason];
+ } else {
+ @throw e;
+ }
+ }
+
+ ASDataControllerLogEvent(self, @"triggeredUpdate: %@", _changeSet);
+
+ [super beginUpdates];
+
+ for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) {
+ [super deleteRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions];
+ }
+
+ for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) {
+ [super deleteSections:change.indexSet withAnimationOptions:change.animationOptions];
+ }
+
+ for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) {
+ [super insertSections:change.indexSet withAnimationOptions:change.animationOptions];
+ }
+
+ for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) {
+ [super insertRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions];
+ }
+
+#if ASEVENTLOG_ENABLE
+ NSString *changeSetDescription = ASObjectDescriptionMakeTiny(_changeSet);
+ batchCompletion = ^(BOOL finished) {
+ if (batchCompletion != nil) {
+ batchCompletion(finished);
+ }
+ ASDataControllerLogEvent(self, @"finishedUpdate: %@", changeSetDescription);
+ };
+#endif
+
+ [super endUpdatesAnimated:animated completion:batchCompletion];
+
+ _changeSet = nil;
+ }
+}
+
+- (BOOL)batchUpdating
+{
+ BOOL batchUpdating = (_changeSetBatchUpdateCounter != 0);
+ // _changeSet must be available during batch update
+ ASDisplayNodeAssertTrue(batchUpdating == (_changeSet != nil));
+ return batchUpdating;
+}
+
+- (void)waitUntilAllUpdatesAreCommitted
+{
+ ASDisplayNodeAssertMainThread();
+ if (self.batchUpdating) {
+ // This assertion will be enabled soon.
+// ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd));
+ return;
+ }
+
+ [super waitUntilAllUpdatesAreCommitted];
+}
+
+#pragma mark - Section Editing (External API)
+
+- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
+{
+ ASDisplayNodeAssertMainThread();
+ [self beginUpdates];
+ [_changeSet insertSections:sections animationOptions:animationOptions];
+ [self endUpdates];
+}
+
+- (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
+{
+ ASDisplayNodeAssertMainThread();
+ [self beginUpdates];
+ [_changeSet deleteSections:sections animationOptions:animationOptions];
+ [self endUpdates];
+}
+
+- (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
+{
+ ASDisplayNodeAssertMainThread();
+ [self beginUpdates];
+ [_changeSet reloadSections:sections animationOptions:animationOptions];
+ [self endUpdates];
+}
+
+- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
+{
+ ASDisplayNodeAssertMainThread();
+ [self beginUpdates];
+ [_changeSet deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:animationOptions];
+ [_changeSet insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:animationOptions];
+ [self endUpdates];
+}
+
+#pragma mark - Row Editing (External API)
+
+- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
+{
+ ASDisplayNodeAssertMainThread();
+ [self beginUpdates];
+ [_changeSet insertItems:indexPaths animationOptions:animationOptions];
+ [self endUpdates];
+}
+
+- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
+{
+ ASDisplayNodeAssertMainThread();
+ [self beginUpdates];
+ [_changeSet deleteItems:indexPaths animationOptions:animationOptions];
+ [self endUpdates];
+}
+
+- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
+{
+ ASDisplayNodeAssertMainThread();
+ [self beginUpdates];
+ [_changeSet reloadItems:indexPaths animationOptions:animationOptions];
+ [self endUpdates];
+}
+
+- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
+{
+ ASDisplayNodeAssertMainThread();
+ [self beginUpdates];
+ [_changeSet deleteItems:@[indexPath] animationOptions:animationOptions];
+ [_changeSet insertItems:@[newIndexPath] animationOptions:animationOptions];
+ [self endUpdates];
+}
+
+@end
+
+#endif
diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASCollectionDataController.h b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASCollectionDataController.h
new file mode 100644
index 0000000000..adc1d8d957
--- /dev/null
+++ b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASCollectionDataController.h
@@ -0,0 +1,57 @@
+//
+// ASCollectionDataController.h
+// AsyncDisplayKit
+//
+// 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.
+//
+
+#ifndef MINIMAL_ASDK
+
+#import
+
+#import
+#import
+
+@class ASDisplayNode;
+@class ASCollectionDataController;
+@protocol ASSectionContext;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol ASCollectionDataControllerSource
+
+/**
+ The constrained size range for layout.
+ */
+- (ASSizeRange)dataController:(ASCollectionDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
+
+- (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController sections:(NSIndexSet *)sections;
+
+- (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section;
+
+- (nullable id)dataController:(ASCollectionDataController *)dataController contextForSection:(NSInteger)section;
+
+@optional
+
+- (ASCellNode *)dataController:(ASCollectionDataController *)dataController supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
+
+- (ASCellNodeBlock)dataController:(ASCollectionDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
+
+@end
+
+@interface ASCollectionDataController : ASDataController
+
+- (instancetype)initWithDataSource:(id)dataSource eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER;
+
+- (nullable ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
+
+- (nullable id)contextForSection:(NSInteger)section;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif
diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASCollectionDataController.mm b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASCollectionDataController.mm
new file mode 100644
index 0000000000..2dc4bb203c
--- /dev/null
+++ b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASCollectionDataController.mm
@@ -0,0 +1,320 @@
+//
+// ASCollectionDataController.mm
+// AsyncDisplayKit
+//
+// 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.
+//
+#ifndef MINIMAL_ASDK
+#import
+
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+
+//#define LOG(...) NSLog(__VA_ARGS__)
+#define LOG(...)
+
+@interface ASCollectionDataController () {
+ BOOL _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath;
+ NSInteger _nextSectionID;
+ NSMutableArray *_sections;
+ NSArray *_pendingSections;
+
+ /**
+ * supplementaryKinds can only be accessed on the main thread
+ * and so we set this in the -prepare stage, and then read it during the -will
+ * stage of each update operation.
+ */
+ NSArray *_supplementaryKindsForPendingOperation;
+}
+
+- (id)collectionDataSource;
+
+@end
+
+@implementation ASCollectionDataController {
+ NSMutableDictionary *> *_pendingNodeContexts;
+}
+
+- (instancetype)initWithDataSource:(id)dataSource eventLog:(ASEventLog *)eventLog
+{
+ self = [super initWithDataSource:dataSource eventLog:eventLog];
+ if (self != nil) {
+ _pendingNodeContexts = [NSMutableDictionary dictionary];
+ _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath = [dataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)];
+
+ ASDisplayNodeAssertTrue(_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath || [dataSource respondsToSelector:@selector(dataController:supplementaryNodeOfKind:atIndexPath:)]);
+
+ _nextSectionID = 0;
+ _sections = [NSMutableArray array];
+ }
+ return self;
+}
+
+- (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount
+{
+ ASDisplayNodeAssertMainThread();
+ NSIndexSet *sections = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newSectionCount)];
+
+ [_sections removeAllObjects];
+ [self _populatePendingSectionsFromDataSource:sections];
+
+ for (NSString *kind in [self supplementaryKindsInSections:sections]) {
+ LOG(@"Populating elements of kind: %@", kind);
+ NSMutableArray *contexts = [NSMutableArray array];
+ [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts];
+ _pendingNodeContexts[kind] = contexts;
+ }
+}
+
+- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount
+{
+ NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newSectionCount)];
+
+ [self applyPendingSections:sectionIndexes];
+
+ // Assert that ASDataController has already deleted all the old sections for us.
+ ASDisplayNodeAssert([self editingNodesOfKind:ASDataControllerRowNodeKind].count == 0, @"Expected that all old sections were deleted before %@. Sections: %@", NSStringFromSelector(_cmd), [self editingNodesOfKind:ASDataControllerRowNodeKind]);
+
+ [_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, __unused BOOL * _Nonnull stop) {
+ // Insert each section
+ NSMutableArray *sections = [NSMutableArray arrayWithCapacity:newSectionCount];
+ for (int i = 0; i < newSectionCount; i++) {
+ [sections addObject:[NSMutableArray array]];
+ }
+ [self insertSections:sections ofKind:kind atIndexSet:sectionIndexes completion:nil];
+
+ [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) {
+ [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
+ }];
+ }];
+ [_pendingNodeContexts removeAllObjects];
+}
+
+- (void)prepareForInsertSections:(NSIndexSet *)sections
+{
+ ASDisplayNodeAssertMainThread();
+ [self _populatePendingSectionsFromDataSource:sections];
+
+ for (NSString *kind in [self supplementaryKindsInSections:sections]) {
+ LOG(@"Populating elements of kind: %@, for sections: %@", kind, sections);
+ NSMutableArray *contexts = [NSMutableArray array];
+ [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts];
+ _pendingNodeContexts[kind] = contexts;
+ }
+}
+
+- (void)willInsertSections:(NSIndexSet *)sections
+{
+ [self applyPendingSections:sections];
+
+ [_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, BOOL * _Nonnull stop) {
+ NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count];
+ for (NSUInteger i = 0; i < sections.count; i++) {
+ [sectionArray addObject:[NSMutableArray array]];
+ }
+
+ [self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil];
+ [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) {
+ [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
+ }];
+ }];
+ [_pendingNodeContexts removeAllObjects];
+}
+
+- (void)willDeleteSections:(NSIndexSet *)sections
+{
+ [_sections removeObjectsAtIndexes:sections];
+}
+
+- (void)prepareForInsertRowsAtIndexPaths:(NSArray *)indexPaths
+{
+ ASDisplayNodeAssertMainThread();
+ NSIndexSet *sections = [NSIndexSet as_sectionsFromIndexPaths:indexPaths];
+ for (NSString *kind in [self supplementaryKindsInSections:sections]) {
+ LOG(@"Populating elements of kind: %@, for index paths: %@", kind, indexPaths);
+ NSMutableArray *contexts = [NSMutableArray array];
+ [self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts];
+ _pendingNodeContexts[kind] = contexts;
+ }
+}
+
+- (void)willInsertRowsAtIndexPaths:(NSArray *)indexPaths
+{
+ [_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, BOOL * _Nonnull stop) {
+ [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) {
+ [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
+ }];
+ }];
+
+ [_pendingNodeContexts removeAllObjects];
+}
+
+- (void)prepareForDeleteRowsAtIndexPaths:(NSArray *)indexPaths
+{
+ ASDisplayNodeAssertMainThread();
+ NSIndexSet *sections = [NSIndexSet as_sectionsFromIndexPaths:indexPaths];
+ _supplementaryKindsForPendingOperation = [self supplementaryKindsInSections:sections];
+ for (NSString *kind in _supplementaryKindsForPendingOperation) {
+ NSMutableArray *contexts = [NSMutableArray array];
+ [self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts];
+ _pendingNodeContexts[kind] = contexts;
+ }
+}
+
+- (void)willDeleteRowsAtIndexPaths:(NSArray *)indexPaths
+{
+ for (NSString *kind in _supplementaryKindsForPendingOperation) {
+ NSArray *deletedIndexPaths = ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths([self editingNodesOfKind:kind], indexPaths);
+
+ [self deleteNodesOfKind:kind atIndexPaths:deletedIndexPaths completion:nil];
+
+ // If any of the contexts remain after the deletion, re-insert them, e.g.
+ // UICollectionElementKindSectionHeader remains even if item 0 is deleted.
+ NSMutableArray *reinsertedContexts = [NSMutableArray array];
+ for (ASIndexedNodeContext *context in _pendingNodeContexts[kind]) {
+ if ([deletedIndexPaths containsObject:context.indexPath]) {
+ [reinsertedContexts addObject:context];
+ }
+ }
+
+ [self batchLayoutNodesFromContexts:reinsertedContexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) {
+ [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
+ }];
+ }
+ [_pendingNodeContexts removeAllObjects];
+ _supplementaryKindsForPendingOperation = nil;
+}
+
+- (void)_populatePendingSectionsFromDataSource:(NSIndexSet *)sectionIndexes
+{
+ ASDisplayNodeAssertMainThread();
+
+ NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionIndexes.count];
+ [sectionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
+ id context = [self.collectionDataSource dataController:self contextForSection:idx];
+ [sections addObject:[[ASSection alloc] initWithSectionID:_nextSectionID context:context]];
+ _nextSectionID++;
+ }];
+ _pendingSections = sections;
+}
+
+- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withSections:(NSIndexSet *)sections mutableContexts:(NSMutableArray *)contexts
+{
+ __weak id environment = [self.environmentDelegate dataControllerEnvironment];
+
+ [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) {
+ for (NSUInteger sec = range.location; sec < NSMaxRange(range); sec++) {
+ NSUInteger itemCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:sec];
+ for (NSUInteger i = 0; i < itemCount; i++) {
+ NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sec];
+ [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environment:environment];
+ }
+ }
+ }];
+}
+
+- (void)_populateSupplementaryNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths mutableContexts:(NSMutableArray *)contexts
+{
+ __weak id environment = [self.environmentDelegate dataControllerEnvironment];
+
+ NSMutableIndexSet *sections = [NSMutableIndexSet indexSet];
+ for (NSIndexPath *indexPath in indexPaths) {
+ [sections addIndex:indexPath.section];
+ }
+
+ [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) {
+ for (NSUInteger sec = range.location; sec < NSMaxRange(range); sec++) {
+ NSUInteger itemCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:sec];
+ for (NSUInteger i = 0; i < itemCount; i++) {
+ NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sec];
+ [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environment:environment];
+ }
+ }
+ }];
+}
+
+- (void)_populateSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath mutableContexts:(NSMutableArray *)contexts environment:(id)environment
+{
+ ASCellNodeBlock supplementaryCellBlock;
+ if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) {
+ supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath];
+ } else {
+ ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath];
+ supplementaryCellBlock = ^{ return supplementaryNode; };
+ }
+
+ ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
+ ASIndexedNodeContext *context = [[ASIndexedNodeContext alloc] initWithNodeBlock:supplementaryCellBlock
+ indexPath:indexPath
+ supplementaryElementKind:kind
+ constrainedSize:constrainedSize
+ environment:environment];
+ [contexts addObject:context];
+}
+
+#pragma mark - Sizing query
+
+- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
+{
+ if ([kind isEqualToString:ASDataControllerRowNodeKind]) {
+ return [super constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
+ } else {
+ ASDisplayNodeAssertMainThread();
+ return [self.collectionDataSource dataController:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath];
+ }
+}
+
+#pragma mark - External supplementary store and section context querying
+
+- (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
+{
+ ASDisplayNodeAssertMainThread();
+ NSArray *nodesOfKind = [self completedNodesOfKind:kind];
+ NSInteger section = indexPath.section;
+ if (section < nodesOfKind.count) {
+ NSArray *nodesOfKindInSection = nodesOfKind[section];
+ NSInteger itemIndex = indexPath.item;
+ if (itemIndex < nodesOfKindInSection.count) {
+ return nodesOfKindInSection[itemIndex];
+ }
+ }
+ return nil;
+}
+
+- (id)contextForSection:(NSInteger)section
+{
+ ASDisplayNodeAssertMainThread();
+ ASDisplayNodeAssertTrue(section >= 0 && section < _sections.count);
+ return _sections[section].context;
+}
+
+#pragma mark - Private Helpers
+
+- (NSArray *)supplementaryKindsInSections:(NSIndexSet *)sections
+{
+ return [self.collectionDataSource supplementaryNodeKindsInDataController:self sections:sections];
+}
+
+- (id)collectionDataSource
+{
+ return (id)self.dataSource;
+}
+
+- (void)applyPendingSections:(NSIndexSet *)sectionIndexes
+{
+ [_sections insertObjects:_pendingSections atIndexes:sectionIndexes];
+ _pendingSections = nil;
+}
+
+@end
+
+#endif
diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASDataController.mm b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASDataController.mm
new file mode 100644
index 0000000000..047b59099a
--- /dev/null
+++ b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASDataController.mm
@@ -0,0 +1,1093 @@
+//
+// ASDataController.mm
+// AsyncDisplayKit
+//
+// 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.
+//
+#ifndef MINIMAL_ASDK
+#import
+
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+
+//#define LOG(...) NSLog(__VA_ARGS__)
+#define LOG(...)
+
+#define AS_MEASURE_AVOIDED_DATACONTROLLER_WORK 0
+
+#define RETURN_IF_NO_DATASOURCE(val) if (_dataSource == nil) { return val; }
+#define ASSERT_ON_EDITING_QUEUE ASDisplayNodeAssertNotNil(dispatch_get_specific(&kASDataControllerEditingQueueKey), @"%@ must be called on the editing transaction queue.", NSStringFromSelector(_cmd))
+
+const static NSUInteger kASDataControllerSizingCountPerProcessor = 5;
+const static char * kASDataControllerEditingQueueKey = "kASDataControllerEditingQueueKey";
+const static char * kASDataControllerEditingQueueContext = "kASDataControllerEditingQueueContext";
+
+NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
+NSString * const ASCollectionInvalidUpdateException = @"ASCollectionInvalidUpdateException";
+
+#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK
+@interface ASDataController (AvoidedWorkMeasuring)
++ (void)_didLayoutNode;
++ (void)_expectToInsertNodes:(NSUInteger)count;
+@end
+#endif
+
+@interface ASDataController () {
+ NSMutableDictionary *_nodeContexts; // Main thread only. This is modified immediately during edits i.e. these are in the dataSource's index space.
+ NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available.
+ NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable.
+ NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propagated to _completedNodes.
+ BOOL _itemCountsFromDataSourceAreValid; // Main thread only.
+ std::vector _itemCountsFromDataSource; // Main thread only.
+
+ ASMainSerialQueue *_mainSerialQueue;
+
+ dispatch_queue_t _editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes.
+ dispatch_group_t _editingTransactionGroup; // Group of all edit transaction blocks. Useful for waiting.
+
+ BOOL _initialReloadDataHasBeenCalled;
+
+ BOOL _delegateDidInsertNodes;
+ BOOL _delegateDidDeleteNodes;
+ BOOL _delegateDidInsertSections;
+ BOOL _delegateDidDeleteSections;
+}
+
+@end
+
+@implementation ASDataController
+
+#pragma mark - Lifecycle
+
+- (instancetype)initWithDataSource:(id)dataSource eventLog:(ASEventLog *)eventLog
+{
+ if (!(self = [super init])) {
+ return nil;
+ }
+
+ _dataSource = dataSource;
+
+#if ASEVENTLOG_ENABLE
+ _eventLog = eventLog;
+#endif
+
+ _nodeContexts = [NSMutableDictionary dictionary];
+ _completedNodes = [NSMutableDictionary dictionary];
+ _editingNodes = [NSMutableDictionary dictionary];
+
+ _nodeContexts[ASDataControllerRowNodeKind] = [NSMutableArray array];
+ _completedNodes[ASDataControllerRowNodeKind] = [NSMutableArray array];
+ _editingNodes[ASDataControllerRowNodeKind] = [NSMutableArray array];
+
+ _mainSerialQueue = [[ASMainSerialQueue alloc] init];
+
+ const char *queueName = [[NSString stringWithFormat:@"org.AsyncDisplayKit.ASDataController.editingTransactionQueue:%p", self] cStringUsingEncoding:NSASCIIStringEncoding];
+ _editingTransactionQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL);
+ dispatch_queue_set_specific(_editingTransactionQueue, &kASDataControllerEditingQueueKey, &kASDataControllerEditingQueueContext, NULL);
+ _editingTransactionGroup = dispatch_group_create();
+
+ return self;
+}
+
+- (instancetype)init
+{
+ ASDisplayNodeFailAssert(@"Failed to call designated initializer.");
+ id fakeDataSource = nil;
+ ASEventLog *eventLog = nil;
+ return [self initWithDataSource:fakeDataSource eventLog:eventLog];
+}
+
+- (void)setDelegate:(id)delegate
+{
+ if (_delegate == delegate) {
+ return;
+ }
+
+ _delegate = delegate;
+
+ // Interrogate our delegate to understand its capabilities, optimizing away expensive respondsToSelector: calls later.
+ _delegateDidInsertNodes = [_delegate respondsToSelector:@selector(dataController:didInsertNodes:atIndexPaths:withAnimationOptions:)];
+ _delegateDidDeleteNodes = [_delegate respondsToSelector:@selector(dataController:didDeleteNodes:atIndexPaths:withAnimationOptions:)];
+ _delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSections:atIndexSet:withAnimationOptions:)];
+ _delegateDidDeleteSections = [_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOptions:)];
+}
+
++ (NSUInteger)parallelProcessorCount
+{
+ static NSUInteger parallelProcessorCount;
+
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ parallelProcessorCount = [[NSProcessInfo processInfo] activeProcessorCount];
+ });
+
+ return parallelProcessorCount;
+}
+
+#pragma mark - Cell Layout
+
+- (void)batchLayoutNodesFromContexts:(NSArray *)contexts batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler
+{
+ ASSERT_ON_EDITING_QUEUE;
+#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK
+ [ASDataController _expectToInsertNodes:contexts.count];
+#endif
+
+ if (contexts.count == 0) {
+ batchCompletionHandler(@[], @[]);
+ return;
+ }
+
+ ASProfilingSignpostStart(2, _dataSource);
+
+ NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor;
+ NSUInteger count = contexts.count;
+
+ // Processing in batches
+ for (NSUInteger i = 0; i < count; i += blockSize) {
+ NSRange batchedRange = NSMakeRange(i, MIN(count - i, blockSize));
+ NSArray *batchedContexts = [contexts subarrayWithRange:batchedRange];
+ NSArray *nodes = [self _layoutNodesFromContexts:batchedContexts];
+ NSArray *indexPaths = [ASIndexedNodeContext indexPathsFromContexts:batchedContexts];
+ batchCompletionHandler(nodes, indexPaths);
+ }
+
+ ASProfilingSignpostEnd(2, _dataSource);
+}
+
+/**
+ * Measure and layout the given node with the constrained size range.
+ */
+- (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrainedSize
+{
+ ASDisplayNodeAssert(ASSizeRangeHasSignificantArea(constrainedSize), @"Attempt to layout cell node with invalid size range %@", NSStringFromASSizeRange(constrainedSize));
+
+ CGRect frame = CGRectZero;
+ frame.size = [node layoutThatFits:constrainedSize].size;
+ node.frame = frame;
+}
+
+/**
+ * Measures and defines the layout for each node in optimized batches on an editing queue, inserting the results into the backing store.
+ */
+- (void)_batchLayoutAndInsertNodesFromContexts:(NSArray *)contexts withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
+{
+ ASSERT_ON_EDITING_QUEUE;
+
+ [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) {
+ // Insert finished nodes into data storage
+ [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
+ }];
+}
+
+- (NSArray *)_layoutNodesFromContexts:(NSArray *)contexts
+{
+ ASSERT_ON_EDITING_QUEUE;
+
+ NSUInteger nodeCount = contexts.count;
+ if (!nodeCount || _dataSource == nil) {
+ return nil;
+ }
+
+ __strong ASCellNode **allocatedNodeBuffer = (__strong ASCellNode **)calloc(nodeCount, sizeof(ASCellNode *));
+
+ dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+ ASDispatchApply(nodeCount, queue, 0, ^(size_t i) {
+ RETURN_IF_NO_DATASOURCE();
+
+ // Allocate the node.
+ ASIndexedNodeContext *context = contexts[i];
+ ASCellNode *node = context.node;
+ if (node == nil) {
+ ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, self.dataSource);
+ node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps.
+ }
+
+ // Layout the node if the size range is valid.
+ ASSizeRange sizeRange = context.constrainedSize;
+ if (ASSizeRangeHasSignificantArea(sizeRange)) {
+ [self _layoutNode:node withConstrainedSize:sizeRange];
+ }
+
+#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK
+ [ASDataController _didLayoutNode];
+#endif
+ allocatedNodeBuffer[i] = node;
+ });
+
+ BOOL canceled = _dataSource == nil;
+
+ // Create nodes array
+ NSArray *nodes = canceled ? nil : [NSArray arrayWithObjects:allocatedNodeBuffer count:nodeCount];
+
+ // Nil out buffer indexes to allow arc to free the stored cells.
+ for (int i = 0; i < nodeCount; i++) {
+ allocatedNodeBuffer[i] = nil;
+ }
+ free(allocatedNodeBuffer);
+
+ return nodes;
+}
+
+- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
+{
+ return [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath];
+}
+
+#pragma mark - External Data Querying + Editing
+
+- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock
+{
+ ASSERT_ON_EDITING_QUEUE;
+ if (!indexPaths.count || _dataSource == nil) {
+ return;
+ }
+
+ NSMutableArray *editingNodes = _editingNodes[kind];
+ ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes);
+
+ // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads.
+ NSMutableArray *completedNodes = ASTwoDimensionalArrayDeepMutableCopy(editingNodes);
+
+ [_mainSerialQueue performBlockOnMainThread:^{
+ _completedNodes[kind] = completedNodes;
+ if (completionBlock) {
+ completionBlock(nodes, indexPaths);
+ }
+ }];
+}
+
+- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock
+{
+ ASSERT_ON_EDITING_QUEUE;
+ if (!indexPaths.count || _dataSource == nil) {
+ return;
+ }
+
+ LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForTwoDimensionalArray(_editingNodes[kind]));
+ ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[kind], indexPaths);
+
+ [_mainSerialQueue performBlockOnMainThread:^{
+ NSMutableArray *allNodes = _completedNodes[kind];
+ NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(allNodes, indexPaths);
+ ASDeleteElementsInMultidimensionalArrayAtIndexPaths(allNodes, indexPaths);
+ if (completionBlock) {
+ completionBlock(nodes, indexPaths);
+ }
+ }];
+}
+
+- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock
+{
+ ASSERT_ON_EDITING_QUEUE;
+ if (!indexSet.count|| _dataSource == nil) {
+ return;
+ }
+
+ if (_editingNodes[kind] == nil) {
+ _editingNodes[kind] = [NSMutableArray array];
+ }
+
+ [_editingNodes[kind] insertObjects:sections atIndexes:indexSet];
+
+ // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads.
+ NSArray *sectionsForCompleted = ASTwoDimensionalArrayDeepMutableCopy(sections);
+
+ [_mainSerialQueue performBlockOnMainThread:^{
+ [_completedNodes[kind] insertObjects:sectionsForCompleted atIndexes:indexSet];
+ if (completionBlock) {
+ completionBlock(sections, indexSet);
+ }
+ }];
+}
+
+- (void)deleteSections:(NSIndexSet *)indexSet completion:(void (^)())completionBlock
+{
+ ASSERT_ON_EDITING_QUEUE;
+ if (!indexSet.count || _dataSource == nil) {
+ return;
+ }
+
+ [_editingNodes enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray *sections, BOOL * _Nonnull stop) {
+ [sections removeObjectsAtIndexes:indexSet];
+ }];
+ [_mainSerialQueue performBlockOnMainThread:^{
+ [_completedNodes enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray *sections, BOOL * _Nonnull stop) {
+ [sections removeObjectsAtIndexes:indexSet];
+ }];
+ if (completionBlock) {
+ completionBlock();
+ }
+ }];
+}
+
+#pragma mark - Internal Data Querying + Editing
+
+/**
+ * Inserts the specified nodes into the given index paths and notifies the delegate of newly inserted nodes.
+ *
+ * @discussion Nodes are first inserted into the editing store, then the completed store is replaced by a deep copy
+ * of the editing nodes. The delegate is invoked on the main thread.
+ */
+- (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
+{
+ ASSERT_ON_EDITING_QUEUE;
+
+ [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
+ ASDisplayNodeAssertMainThread();
+
+ if (_delegateDidInsertNodes)
+ [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
+ }];
+}
+
+/**
+ * Removes the specified nodes at the given index paths and notifies the delegate of the nodes removed.
+ *
+ * @discussion Nodes are first removed from the editing store then removed from the completed store on the main thread.
+ * Once the backing stores are consistent, the delegate is invoked on the main thread.
+ */
+- (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
+{
+ ASSERT_ON_EDITING_QUEUE;
+
+ [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
+ ASDisplayNodeAssertMainThread();
+
+ if (_delegateDidDeleteNodes)
+ [_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
+ }];
+}
+
+/**
+ * Inserts sections, represented as arrays, into the backing store at the given indices and notifies the delegate.
+ *
+ * @discussion The section arrays are inserted into the editing store, then a deep copy of the sections are inserted
+ * in the completed store on the main thread. The delegate is invoked on the main thread.
+ */
+- (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
+{
+ ASSERT_ON_EDITING_QUEUE;
+
+ [self insertSections:sections ofKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSArray *sections, NSIndexSet *indexSet) {
+ ASDisplayNodeAssertMainThread();
+
+ if (_delegateDidInsertSections)
+ [_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions];
+ }];
+}
+
+/**
+ * Removes sections at the given indices from the backing store and notifies the delegate.
+ *
+ * @discussion Section array are first removed from the editing store, then the associated section in the completed
+ * store is removed on the main thread. The delegate is invoked on the main thread.
+ */
+- (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
+{
+ ASSERT_ON_EDITING_QUEUE;
+
+ [self deleteSections:indexSet completion:^() {
+ ASDisplayNodeAssertMainThread();
+
+ if (_delegateDidDeleteSections)
+ [_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
+ }];
+}
+
+#pragma mark - Initial Load & Full Reload (External API)
+
+- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^)())completion
+{
+ [self _reloadDataWithAnimationOptions:animationOptions synchronously:NO completion:completion];
+}
+
+- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
+{
+ [self _reloadDataWithAnimationOptions:animationOptions synchronously:YES completion:nil];
+}
+
+- (void)_reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions synchronously:(BOOL)synchronously completion:(void (^)())completion
+{
+ ASDisplayNodeAssertMainThread();
+
+ _initialReloadDataHasBeenCalled = YES;
+ dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
+
+ [self invalidateDataSourceItemCounts];
+ NSUInteger sectionCount = [self itemCountsFromDataSource].size();
+ NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)];
+ NSArray *newContexts = [self _populateNodeContextsFromDataSourceForSections:sectionIndexes];
+
+ // Update _nodeContexts
+ NSMutableArray *allContexts = _nodeContexts[ASDataControllerRowNodeKind];
+ [allContexts removeAllObjects];
+ NSArray *nodeIndexPaths = [ASIndexedNodeContext indexPathsFromContexts:newContexts];
+ for (int i = 0; i < sectionCount; i++) {
+ [allContexts addObject:[[NSMutableArray alloc] init]];
+ }
+ ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(allContexts, nodeIndexPaths, newContexts);
+
+ // Allow subclasses to perform setup before going into the edit transaction
+ [self prepareForReloadDataWithSectionCount:sectionCount];
+
+ dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
+ LOG(@"Edit Transaction - reloadData");
+
+ /**
+ * Leave the current data in the collection view until the first batch of nodes are laid out.
+ * Once the first batch is laid out, in one operation, replace all the sections and insert
+ * the first batch of items.
+ *
+ * We previously would replace all the sections immediately, and then start adding items as they
+ * were laid out. This resulted in more traffic to the UICollectionView and it also caused all the
+ * section headers to bunch up until the items come and fill out the sections.
+ */
+ __block BOOL isFirstBatch = YES;
+ [self batchLayoutNodesFromContexts:newContexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) {
+ if (isFirstBatch) {
+ // -beginUpdates
+ [_mainSerialQueue performBlockOnMainThread:^{
+ [_delegate dataControllerBeginUpdates:self];
+ [_delegate dataControllerWillDeleteAllData:self];
+ }];
+
+ // deleteSections:
+ // Remove everything that existed before the reload, now that we're ready to insert replacements
+ NSUInteger oldSectionCount = [_editingNodes[ASDataControllerRowNodeKind] count];
+ if (oldSectionCount) {
+ NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, oldSectionCount)];
+ [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
+ }
+
+ [self willReloadDataWithSectionCount:sectionCount];
+
+ // insertSections:
+ NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
+ for (int i = 0; i < sectionCount; i++) {
+ [sections addObject:[[NSMutableArray alloc] init]];
+ }
+ [self _insertSections:sections atIndexSet:sectionIndexes withAnimationOptions:animationOptions];
+ }
+
+ // insertItemsAtIndexPaths:
+ [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
+
+ if (isFirstBatch) {
+ // -endUpdates
+ [_mainSerialQueue performBlockOnMainThread:^{
+ [_delegate dataController:self endUpdatesAnimated:NO completion:nil];
+ }];
+ isFirstBatch = NO;
+ }
+ }];
+
+ if (completion) {
+ [_mainSerialQueue performBlockOnMainThread:completion];
+ }
+ });
+ if (synchronously) {
+ [self waitUntilAllUpdatesAreCommitted];
+ }
+}
+
+- (void)waitUntilAllUpdatesAreCommitted
+{
+ ASDisplayNodeAssertMainThread();
+
+ dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
+
+ // Schedule block in main serial queue to wait until all operations are finished that are
+ // where scheduled while waiting for the _editingTransactionQueue to finish
+ [_mainSerialQueue performBlockOnMainThread:^{ }];
+}
+
+#pragma mark - Data Source Access (Calling _dataSource)
+
+/**
+ * Fetches row contexts for the provided sections from the data source.
+ */
+- (NSArray *)_populateNodeContextsFromDataSourceForSections:(NSIndexSet *)sections
+{
+ ASDisplayNodeAssertMainThread();
+
+ __weak id environment = [self.environmentDelegate dataControllerEnvironment];
+
+ std::vector counts = [self itemCountsFromDataSource];
+ NSMutableArray *contexts = [NSMutableArray array];
+ [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) {
+ for (NSUInteger sectionIndex = range.location; sectionIndex < NSMaxRange(range); sectionIndex++) {
+ NSUInteger itemCount = counts[sectionIndex];
+ for (NSUInteger i = 0; i < itemCount; i++) {
+ NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sectionIndex];
+ ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath];
+
+ ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath];
+ [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock
+ indexPath:indexPath
+ supplementaryElementKind:nil
+ constrainedSize:constrainedSize
+ environment:environment]];
+ }
+ }
+ }];
+ return contexts;
+}
+
+- (void)invalidateDataSourceItemCounts
+{
+ ASDisplayNodeAssertMainThread();
+ _itemCountsFromDataSourceAreValid = NO;
+}
+
+- (std::vector)itemCountsFromDataSource
+{
+ ASDisplayNodeAssertMainThread();
+ if (NO == _itemCountsFromDataSourceAreValid) {
+ id source = self.dataSource;
+ NSInteger sectionCount = [source numberOfSectionsInDataController:self];
+ std::vector newCounts;
+ newCounts.reserve(sectionCount);
+ for (NSInteger i = 0; i < sectionCount; i++) {
+ newCounts.push_back([source dataController:self rowsInSection:i]);
+ }
+ _itemCountsFromDataSource = newCounts;
+ _itemCountsFromDataSourceAreValid = YES;
+ }
+ return _itemCountsFromDataSource;
+}
+
+#pragma mark - Batching (External API)
+
+- (void)beginUpdates
+{
+ ASDisplayNodeAssertMainThread();
+ // TODO: make this -waitUntilAllUpdatesAreCommitted?
+ dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
+
+ dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
+ [_mainSerialQueue performBlockOnMainThread:^{
+ // Deep copy _completedNodes to _externalCompletedNodes.
+ // Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate.
+ _externalCompletedNodes = ASTwoDimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]);
+
+ LOG(@"beginUpdates - begin updates call to delegate");
+ [_delegate dataControllerBeginUpdates:self];
+ }];
+ });
+}
+
+- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion
+{
+ LOG(@"endUpdatesWithCompletion - beginning");
+ ASDisplayNodeAssertMainThread();
+
+ // Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates.
+ // Each subsequent command in the queue will also wait on the full asynchronous completion of the prior command's edit transaction.
+ dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
+ [_mainSerialQueue performBlockOnMainThread:^{
+ // Now that the transaction is done, _completedNodes can be accessed externally again.
+ _externalCompletedNodes = nil;
+
+ LOG(@"endUpdatesWithCompletion - calling delegate end");
+ [_delegate dataController:self endUpdatesAnimated:animated completion:completion];
+ }];
+ });
+}
+
+- (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet animated:(BOOL)animated
+{
+ ASDisplayNodeAssertMainThread();
+
+ void (^batchCompletion)(BOOL) = changeSet.completionHandler;
+
+ /**
+ * If the initial reloadData has not been called, just bail because we don't have
+ * our old data source counts.
+ * See ASUICollectionViewTests.testThatIssuingAnUpdateBeforeInitialReloadIsUnacceptable
+ * For the issue that UICollectionView has that we're choosing to workaround.
+ */
+ if (!self.initialReloadDataHasBeenCalled) {
+ if (batchCompletion != nil) {
+ batchCompletion(YES);
+ }
+ return;
+ }
+
+ [self invalidateDataSourceItemCounts];
+
+ // Attempt to mark the update completed. This is when update validation will occur inside the changeset.
+ // If an invalid update exception is thrown, we catch it and inject our "validationErrorSource" object,
+ // which is the table/collection node's data source, into the exception reason to help debugging.
+ @try {
+ [changeSet markCompletedWithNewItemCounts:[self itemCountsFromDataSource]];
+ } @catch (NSException *e) {
+ id responsibleDataSource = self.validationErrorSource;
+ if (e.name == ASCollectionInvalidUpdateException && responsibleDataSource != nil) {
+ [NSException raise:ASCollectionInvalidUpdateException format:@"%@: %@", [responsibleDataSource class], e.reason];
+ } else {
+ @throw e;
+ }
+ }
+
+ ASDataControllerLogEvent(self, @"triggeredUpdate: %@", changeSet);
+
+ [self beginUpdates];
+
+ for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) {
+ [self deleteRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions];
+ }
+
+ for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) {
+ [self deleteSections:change.indexSet withAnimationOptions:change.animationOptions];
+ }
+
+ for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) {
+ [self insertSections:change.indexSet withAnimationOptions:change.animationOptions];
+ }
+
+ for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) {
+ [self insertRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions];
+ }
+
+#if ASEVENTLOG_ENABLE
+ NSString *changeSetDescription = ASObjectDescriptionMakeTiny(changeSet);
+ batchCompletion = ^(BOOL finished) {
+ if (batchCompletion != nil) {
+ batchCompletion(finished);
+ }
+ ASDataControllerLogEvent(self, @"finishedUpdate: %@", changeSetDescription);
+ };
+#endif
+
+ [self endUpdatesAnimated:animated completion:batchCompletion];
+}
+
+#pragma mark - Section Editing (External API)
+
+- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
+{
+ ASDisplayNodeAssertMainThread();
+ LOG(@"Edit Command - insertSections: %@", sections);
+ if (!_initialReloadDataHasBeenCalled) {
+ return;
+ }
+
+ dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
+
+ NSArray *contexts = [self _populateNodeContextsFromDataSourceForSections:sections];
+
+ // Update _nodeContexts
+ {
+ NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count];
+ for (NSUInteger i = 0; i < sections.count; i++) {
+ [sectionArray addObject:[NSMutableArray array]];
+ }
+ NSMutableArray *allRowContexts = _nodeContexts[ASDataControllerRowNodeKind];
+ [allRowContexts insertObjects:sectionArray atIndexes:sections];
+ ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(allRowContexts, [ASIndexedNodeContext indexPathsFromContexts:contexts], contexts);
+ }
+
+ [self prepareForInsertSections:sections];
+
+ dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
+ [self willInsertSections:sections];
+
+ LOG(@"Edit Transaction - insertSections: %@", sections);
+ NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count];
+ for (NSUInteger i = 0; i < sections.count; i++) {
+ [sectionArray addObject:[NSMutableArray array]];
+ }
+
+ [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions];
+
+ [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions];
+ });
+}
+
+- (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
+{
+ ASDisplayNodeAssertMainThread();
+ LOG(@"Edit Command - deleteSections: %@", sections);
+ if (!_initialReloadDataHasBeenCalled) {
+ return;
+ }
+
+ [_nodeContexts[ASDataControllerRowNodeKind] removeObjectsAtIndexes:sections];
+
+ dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
+
+ [self prepareForDeleteSections:sections];
+ dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
+ [self willDeleteSections:sections];
+
+ // remove elements
+ LOG(@"Edit Transaction - deleteSections: %@", sections);
+ [self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions];
+ });
+}
+
+#pragma mark - Backing store manipulation optional hooks (Subclass API)
+
+- (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount
+{
+ // Optional template hook for subclasses (See ASDataController+Subclasses.h)
+}
+
+- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount
+{
+ // Optional template hook for subclasses (See ASDataController+Subclasses.h)
+}
+
+- (void)prepareForInsertSections:(NSIndexSet *)sections
+{
+ // Optional template hook for subclasses (See ASDataController+Subclasses.h)
+}
+
+- (void)prepareForDeleteSections:(NSIndexSet *)sections
+{
+ // Optional template hook for subclasses (See ASDataController+Subclasses.h)
+}
+
+- (void)willInsertSections:(NSIndexSet *)sections
+{
+ // Optional template hook for subclasses (See ASDataController+Subclasses.h)
+}
+
+- (void)willDeleteSections:(NSIndexSet *)sections
+{
+ // Optional template hook for subclasses (See ASDataController+Subclasses.h)
+}
+
+- (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection
+{
+ // Optional template hook for subclasses (See ASDataController+Subclasses.h)
+}
+
+- (void)prepareForInsertRowsAtIndexPaths:(NSArray *)indexPaths
+{
+ // Optional template hook for subclasses (See ASDataController+Subclasses.h)
+}
+
+- (void)willInsertRowsAtIndexPaths:(NSArray *)indexPaths
+{
+ // Optional template hook for subclasses (See ASDataController+Subclasses.h)
+}
+
+- (void)prepareForDeleteRowsAtIndexPaths:(NSArray *)indexPaths
+{
+ // Optional template hook for subclasses (See ASDataController+Subclasses.h)
+}
+
+- (void)willDeleteRowsAtIndexPaths:(NSArray *)indexPaths
+{
+ // Optional template hook for subclasses (See ASDataController+Subclasses.h)
+}
+
+#pragma mark - Row Editing (External API)
+
+- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
+{
+ ASDisplayNodeAssertMainThread();
+ if (!_initialReloadDataHasBeenCalled) {
+ return;
+ }
+
+ LOG(@"Edit Command - insertRows: %@", indexPaths);
+ dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
+
+ // Sort indexPath to avoid messing up the index when inserting in several batches
+ NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
+ NSMutableArray *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
+
+ __weak id environment = [self.environmentDelegate dataControllerEnvironment];
+
+ for (NSIndexPath *indexPath in sortedIndexPaths) {
+ ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath];
+ ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath];
+ [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock
+ indexPath:indexPath
+ supplementaryElementKind:nil
+ constrainedSize:constrainedSize
+ environment:environment]];
+ }
+
+ ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(_nodeContexts[ASDataControllerRowNodeKind], sortedIndexPaths, contexts);
+ [self prepareForInsertRowsAtIndexPaths:indexPaths];
+
+ dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
+ [self willInsertRowsAtIndexPaths:indexPaths];
+
+ LOG(@"Edit Transaction - insertRows: %@", indexPaths);
+ [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions];
+ });
+}
+
+- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
+{
+ ASDisplayNodeAssertMainThread();
+
+ if (!_initialReloadDataHasBeenCalled) {
+ return;
+ }
+
+ LOG(@"Edit Command - deleteRows: %@", indexPaths);
+
+ dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
+
+ // Sort indexPath in order to avoid messing up the index when deleting in several batches.
+ // FIXME: Shouldn't deletes be sorted in descending order?
+ NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
+
+ ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_nodeContexts[ASDataControllerRowNodeKind], sortedIndexPaths);
+ [self prepareForDeleteRowsAtIndexPaths:sortedIndexPaths];
+
+ dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
+ [self willDeleteRowsAtIndexPaths:sortedIndexPaths];
+
+ LOG(@"Edit Transaction - deleteRows: %@", indexPaths);
+ [self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions];
+ });
+}
+
+- (void)relayoutAllNodes
+{
+ ASDisplayNodeAssertMainThread();
+ if (!_initialReloadDataHasBeenCalled) {
+ return;
+ }
+
+ LOG(@"Edit Command - relayoutRows");
+ dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
+
+ // Can't relayout right away because _completedNodes may not be up-to-date,
+ // i.e there might be some nodes that were measured using the old constrained size but haven't been added to _completedNodes
+ // (see _layoutNodes:atIndexPaths:withAnimationOptions:).
+ dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
+ [_mainSerialQueue performBlockOnMainThread:^{
+ for (NSString *kind in _completedNodes) {
+ [self _relayoutNodesOfKind:kind];
+ }
+ }];
+ });
+}
+
+- (void)_relayoutNodesOfKind:(NSString *)kind
+{
+ ASDisplayNodeAssertMainThread();
+ NSArray *nodes = [self completedNodesOfKind:kind];
+ if (!nodes.count) {
+ return;
+ }
+
+ NSUInteger sectionIndex = 0;
+ for (NSMutableArray *section in nodes) {
+ NSUInteger rowIndex = 0;
+ for (ASCellNode *node in section) {
+ RETURN_IF_NO_DATASOURCE();
+ NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex];
+ ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
+ if (ASSizeRangeHasSignificantArea(constrainedSize)) {
+ [self _layoutNode:node withConstrainedSize:constrainedSize];
+ }
+ rowIndex += 1;
+ }
+ sectionIndex += 1;
+ }
+}
+
+#pragma mark - Data Querying (Subclass API)
+
+- (NSMutableArray *)editingNodesOfKind:(NSString *)kind
+{
+ return _editingNodes[kind] ? : [NSMutableArray array];
+}
+
+- (NSMutableArray *)completedNodesOfKind:(NSString *)kind
+{
+ return _completedNodes[kind];
+}
+
+#pragma mark - Data Querying (External API)
+
+- (NSUInteger)numberOfSections
+{
+ ASDisplayNodeAssertMainThread();
+ return [_nodeContexts[ASDataControllerRowNodeKind] count];
+}
+
+- (NSUInteger)numberOfRowsInSection:(NSUInteger)section
+{
+ ASDisplayNodeAssertMainThread();
+ NSArray *contextSections = _nodeContexts[ASDataControllerRowNodeKind];
+ return (section < contextSections.count) ? [contextSections[section] count] : 0;
+}
+
+- (NSUInteger)completedNumberOfSections
+{
+ ASDisplayNodeAssertMainThread();
+ return [[self completedNodes] count];
+}
+
+- (NSUInteger)completedNumberOfRowsInSection:(NSUInteger)section
+{
+ ASDisplayNodeAssertMainThread();
+ NSArray *completedNodes = [self completedNodes];
+ return (section < completedNodes.count) ? [completedNodes[section] count] : 0;
+}
+
+- (ASCellNode *)nodeAtIndexPath:(NSIndexPath *)indexPath
+{
+ ASDisplayNodeAssertMainThread();
+ if (indexPath == nil) {
+ return nil;
+ }
+
+ NSArray *contexts = _nodeContexts[ASDataControllerRowNodeKind];
+ NSInteger section = indexPath.section;
+ NSInteger row = indexPath.row;
+ ASIndexedNodeContext *context = nil;
+
+ if (section >= 0 && row >= 0 && section < contexts.count) {
+ NSArray *completedNodesSection = contexts[section];
+ if (row < completedNodesSection.count) {
+ context = completedNodesSection[row];
+ }
+ }
+ return context.node;
+}
+
+- (ASCellNode *)nodeAtCompletedIndexPath:(NSIndexPath *)indexPath
+{
+ ASDisplayNodeAssertMainThread();
+ if (indexPath == nil) {
+ return nil;
+ }
+
+ NSArray *completedNodes = [self completedNodes];
+ NSInteger section = indexPath.section;
+ NSInteger row = indexPath.row;
+ ASCellNode *node = nil;
+
+ if (section >= 0 && row >= 0 && section < completedNodes.count) {
+ NSArray *completedNodesSection = completedNodes[section];
+ if (row < completedNodesSection.count) {
+ node = completedNodesSection[row];
+ }
+ }
+
+ return node;
+}
+
+- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode;
+{
+ ASDisplayNodeAssertMainThread();
+ if (cellNode == nil) {
+ return nil;
+ }
+
+ NSString *kind = cellNode.supplementaryElementKind ?: ASDataControllerRowNodeKind;
+ NSArray *contexts = _nodeContexts[kind];
+
+ // Check if the cached index path is still correct.
+ NSIndexPath *indexPath = cellNode.cachedIndexPath;
+ if (indexPath != nil) {
+ ASIndexedNodeContext *context = ASGetElementInTwoDimensionalArray(contexts, indexPath);
+ if (context.nodeIfAllocated == cellNode) {
+ return indexPath;
+ } else {
+ indexPath = nil;
+ }
+ }
+
+ // Loop through each section to look for the node context
+ NSInteger section = 0;
+ for (NSArray *nodeContexts in contexts) {
+ NSUInteger item = [nodeContexts indexOfObjectPassingTest:^BOOL(ASIndexedNodeContext * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
+ return obj.nodeIfAllocated == cellNode;
+ }];
+ if (item != NSNotFound) {
+ indexPath = [NSIndexPath indexPathForItem:item inSection:section];
+ break;
+ }
+ section += 1;
+ }
+ cellNode.cachedIndexPath = indexPath;
+ return indexPath;
+}
+
+- (NSIndexPath *)completedIndexPathForNode:(ASCellNode *)cellNode
+{
+ ASDisplayNodeAssertMainThread();
+ if (cellNode == nil) {
+ return nil;
+ }
+
+ NSInteger section = 0;
+ // Loop through each section to look for the cellNode
+ NSString *kind = cellNode.supplementaryElementKind ?: ASDataControllerRowNodeKind;
+ for (NSArray *sectionNodes in [self completedNodesOfKind:kind]) {
+ NSUInteger item = [sectionNodes indexOfObjectIdenticalTo:cellNode];
+ if (item != NSNotFound) {
+ return [NSIndexPath indexPathForItem:item inSection:section];
+ }
+ section += 1;
+ }
+
+ return nil;
+}
+
+/// Returns nodes that can be queried externally. _externalCompletedNodes is used if available, _completedNodes otherwise.
+- (NSArray *)completedNodes
+{
+ ASDisplayNodeAssertMainThread();
+ return _externalCompletedNodes ? : _completedNodes[ASDataControllerRowNodeKind];
+}
+
+- (void)moveCompletedNodeAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
+{
+ ASDisplayNodeAssertMainThread();
+ ASMoveElementInTwoDimensionalArray(_externalCompletedNodes, indexPath, newIndexPath);
+ ASMoveElementInTwoDimensionalArray(_completedNodes[ASDataControllerRowNodeKind], indexPath, newIndexPath);
+}
+
+@end
+
+#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK
+
+static volatile int64_t _totalExpectedItems = 0;
+static volatile int64_t _totalMeasuredNodes = 0;
+
+@implementation ASDataController (WorkMeasuring)
+
++ (void)_didLayoutNode
+{
+ int64_t measured = OSAtomicIncrement64(&_totalMeasuredNodes);
+ int64_t expected = _totalExpectedItems;
+ if (measured % 20 == 0 || measured == expected) {
+ NSLog(@"Data controller avoided work (underestimated): %lld / %lld", measured, expected);
+ }
+}
+
++ (void)_expectToInsertNodes:(NSUInteger)count
+{
+ OSAtomicAdd64((int64_t)count, &_totalExpectedItems);
+}
+
+@end
+#endif
+
+#endif
diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASFlowLayoutController.h b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASFlowLayoutController.h
new file mode 100644
index 0000000000..7eeb4d3533
--- /dev/null
+++ b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASFlowLayoutController.h
@@ -0,0 +1,44 @@
+//
+// ASFlowLayoutController.h
+// AsyncDisplayKit
+//
+// 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.
+//
+#ifndef MINIMAL_ASDK
+#import
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class ASCellNode;
+
+typedef NS_ENUM(NSUInteger, ASFlowLayoutDirection) {
+ ASFlowLayoutDirectionVertical,
+ ASFlowLayoutDirectionHorizontal,
+};
+
+@protocol ASFlowLayoutControllerDataSource
+
+- (NSArray *> *)completedNodes; // This provides access to ASDataController's _completedNodes multidimensional array.
+
+@end
+
+/**
+ * An optimized flow layout controller that supports only vertical or horizontal scrolling, not simultaneously two-dimensional scrolling.
+ * It is used for all ASTableViews, and may be used with ASCollectionView.
+ */
+@interface ASFlowLayoutController : ASAbstractLayoutController
+
+@property (nonatomic, readonly, assign) ASFlowLayoutDirection layoutDirection;
+@property (nonatomic, readwrite, weak) id dataSource;
+
+- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif
diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASFlowLayoutController.mm b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASFlowLayoutController.mm
new file mode 100644
index 0000000000..4a23ec25a6
--- /dev/null
+++ b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASFlowLayoutController.mm
@@ -0,0 +1,203 @@
+//
+// ASFlowLayoutController.mm
+// AsyncDisplayKit
+//
+// 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.
+//
+#ifndef MINIMAL_ASDK
+#import
+#import
+#import
+#import
+#import
+
+#include