Add 'submodules/AsyncDisplayKit/' from commit '02bedc12816e251ad71777f9d2578329b6d2bef6'

git-subtree-dir: submodules/AsyncDisplayKit
git-subtree-mainline: d06f423e0ed3df1fed9bd10d79ee312a9179b632
git-subtree-split: 02bedc12816e251ad71777f9d2578329b6d2bef6
This commit is contained in:
Peter 2019-06-11 18:42:43 +01:00
commit 9bc996374f
2160 changed files with 232035 additions and 0 deletions

View File

@ -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, \

View File

@ -0,0 +1 @@
f399f484bf13b47bcc2bf0f2e092ab5d8de9f6e6

View File

@ -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

View File

@ -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!

View File

@ -0,0 +1,3 @@
issues=false
since-tag=2.8
future-release=2.9

25
submodules/AsyncDisplayKit/.gitignore vendored Normal file
View File

@ -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/*

View File

@ -0,0 +1,5 @@
ci_service: travis_ci
coverage_service: coveralls
xcodeproj: AsyncDisplayKit.xcodeproj
source_directory: AsyncDisplayKit

View File

@ -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

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:AsyncDisplayKit.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
<false/>
</dict>
</plist>

View File

@ -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 <AsyncDisplayKit/ASPagerFlowLayout.h>
@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<UICollectionViewLayoutAttributes *> *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

View File

@ -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 <AsyncDisplayKit/ASCellNode.h>
typedef NS_ENUM(NSInteger, ASLayoutElementPropertyType) {
ASLayoutElementPropertyFlexGrow = 0,
ASLayoutElementPropertyFlexShrink,
ASLayoutElementPropertyAlignSelf,
ASLayoutElementPropertyFlexBasis,
ASLayoutElementPropertySpacingBefore,
ASLayoutElementPropertySpacingAfter,
ASLayoutElementPropertyAscender,
ASLayoutElementPropertyDescender,
ASLayoutElementPropertyCount
};
@interface ASLayoutElementInspectorCell : ASCellNode
- (instancetype)initWithProperty:(ASLayoutElementPropertyType)property layoutElementToEdit:(id<ASLayoutElement>)layoutable NS_DESIGNATED_INITIALIZER;
@end
#endif

View File

@ -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 <AsyncDisplayKit/ASLayoutElementInspectorCell.h>
#import <AsyncDisplayKit/AsyncDisplayKit.h>
typedef NS_ENUM(NSInteger, CellDataType) {
CellDataTypeBool,
CellDataTypeFloat,
};
__weak static ASLayoutElementInspectorCell *__currentlyOpenedCell = nil;
@protocol InspectorCellEditingBubbleProtocol <NSObject>
- (void)valueChangedToIndex:(NSUInteger)index;
@end
@interface ASLayoutElementInspectorCellEditingBubble : ASDisplayNode
@property (nonatomic, strong, readwrite) id<InspectorCellEditingBubbleProtocol> delegate;
- (instancetype)initWithEnumOptions:(BOOL)yes enumStrings:(NSArray<NSString *> *)options currentOptionIndex:(NSUInteger)currentOption;
- (instancetype)initWithSliderMinValue:(CGFloat)min maxValue:(CGFloat)max currentValue:(CGFloat)current
;@end
@interface ASLayoutElementInspectorCell () <InspectorCellEditingBubbleProtocol>
@end
@implementation ASLayoutElementInspectorCell
{
ASLayoutElementPropertyType _propertyType;
CellDataType _dataType;
id<ASLayoutElement> _layoutElementToEdit;
ASButtonNode *_buttonNode;
ASTextNode *_textNode;
ASTextNode *_textNode2;
ASLayoutElementInspectorCellEditingBubble *_textBubble;
}
#pragma mark - Lifecycle
- (instancetype)initWithProperty:(ASLayoutElementPropertyType)property layoutElementToEdit:(id<ASLayoutElement>)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<ASLayoutElement>)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<ASLayoutElement>)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<ASLayoutElement>)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 <NSString *> *)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<ASButtonNode *> *_textNodes;
ASDisplayNode *_slider;
}
- (instancetype)initWithEnumOptions:(BOOL)yes enumStrings:(NSArray<NSString *> *)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

View File

@ -0,0 +1,426 @@
//
// ASLayoutElementInspectorNode.m
// Sample
//
// Created by Hannah Troisi on 3/19/16.
// Copyright © 2016 Facebook. All rights reserved.
//
#import <AsyncDisplayKit/ASLayoutElementInspectorNode.h>
#ifndef MINIMAL_ASDK
#import <AsyncDisplayKit/ASLayoutElementInspectorCell.h>
#endif
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASLayoutSpec+Debug.h>
#import <AsyncDisplayKit/ASTableNode.h>
#import <AsyncDisplayKit/ASDisplayNode.h>
@interface ASLayoutElementInspectorNode ()
#ifndef MINIMAL_ASDK
<ASTableDelegate, ASTableDataSource>
#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<ASLayoutElement>)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 = @"<Layoutable> Item";
} else {
title = @"<Layoutable> 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

View File

@ -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 <AsyncDisplayKit/ASDataController.h>
/**
* @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

View File

@ -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

View File

@ -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 <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASDataController.h>
#import <AsyncDisplayKit/ASDimension.h>
@class ASDisplayNode;
@class ASCollectionDataController;
@protocol ASSectionContext;
NS_ASSUME_NONNULL_BEGIN
@protocol ASCollectionDataControllerSource <ASDataControllerSource>
/**
The constrained size range for layout.
*/
- (ASSizeRange)dataController:(ASCollectionDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
- (NSArray<NSString *> *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController sections:(NSIndexSet *)sections;
- (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section;
- (nullable id<ASSectionContext>)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<ASCollectionDataControllerSource>)dataSource eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER;
- (nullable ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
- (nullable id<ASSectionContext>)contextForSection:(NSInteger)section;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -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 <AsyncDisplayKit/ASCollectionDataController.h>
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASMultidimensionalArrayUtils.h>
#import <AsyncDisplayKit/ASCellNode.h>
#import <AsyncDisplayKit/ASDataController+Subclasses.h>
#import <AsyncDisplayKit/ASIndexedNodeContext.h>
#import <AsyncDisplayKit/ASSection.h>
#import <AsyncDisplayKit/ASSectionContext.h>
#import <AsyncDisplayKit/NSIndexSet+ASHelpers.h>
//#define LOG(...) NSLog(__VA_ARGS__)
#define LOG(...)
@interface ASCollectionDataController () {
BOOL _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath;
NSInteger _nextSectionID;
NSMutableArray<ASSection *> *_sections;
NSArray<ASSection *> *_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<ASCollectionDataControllerSource>)collectionDataSource;
@end
@implementation ASCollectionDataController {
NSMutableDictionary<NSString *, NSMutableArray<ASIndexedNodeContext *> *> *_pendingNodeContexts;
}
- (instancetype)initWithDataSource:(id<ASCollectionDataControllerSource>)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<ASIndexedNodeContext *> *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<ASIndexedNodeContext *> * _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<ASCellNode *> *nodes, NSArray<NSIndexPath *> *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<ASIndexedNodeContext *> *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<ASIndexedNodeContext *> * _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<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}];
}];
[_pendingNodeContexts removeAllObjects];
}
- (void)willDeleteSections:(NSIndexSet *)sections
{
[_sections removeObjectsAtIndexes:sections];
}
- (void)prepareForInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)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<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
[self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts];
_pendingNodeContexts[kind] = contexts;
}
}
- (void)willInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
[_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray<ASIndexedNodeContext *> * _Nonnull contexts, BOOL * _Nonnull stop) {
[self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}];
}];
[_pendingNodeContexts removeAllObjects];
}
- (void)prepareForDeleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
ASDisplayNodeAssertMainThread();
NSIndexSet *sections = [NSIndexSet as_sectionsFromIndexPaths:indexPaths];
_supplementaryKindsForPendingOperation = [self supplementaryKindsInSections:sections];
for (NSString *kind in _supplementaryKindsForPendingOperation) {
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
[self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts];
_pendingNodeContexts[kind] = contexts;
}
}
- (void)willDeleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
{
for (NSString *kind in _supplementaryKindsForPendingOperation) {
NSArray<NSIndexPath *> *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<ASIndexedNodeContext *> *reinsertedContexts = [NSMutableArray array];
for (ASIndexedNodeContext *context in _pendingNodeContexts[kind]) {
if ([deletedIndexPaths containsObject:context.indexPath]) {
[reinsertedContexts addObject:context];
}
}
[self batchLayoutNodesFromContexts:reinsertedContexts batchCompletion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
}];
}
[_pendingNodeContexts removeAllObjects];
_supplementaryKindsForPendingOperation = nil;
}
- (void)_populatePendingSectionsFromDataSource:(NSIndexSet *)sectionIndexes
{
ASDisplayNodeAssertMainThread();
NSMutableArray<ASSection *> *sections = [NSMutableArray arrayWithCapacity:sectionIndexes.count];
[sectionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
id<ASSectionContext> 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<ASIndexedNodeContext *> *)contexts
{
__weak id<ASTraitEnvironment> 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<NSIndexPath *> *)indexPaths mutableContexts:(NSMutableArray<ASIndexedNodeContext *> *)contexts
{
__weak id<ASTraitEnvironment> 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<ASIndexedNodeContext *> *)contexts environment:(id<ASTraitEnvironment>)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<ASSectionContext>)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<ASCollectionDataControllerSource>)collectionDataSource
{
return (id<ASCollectionDataControllerSource>)self.dataSource;
}
- (void)applyPendingSections:(NSIndexSet *)sectionIndexes
{
[_sections insertObjects:_pendingSections atIndexes:sectionIndexes];
_pendingSections = nil;
}
@end
#endif

File diff suppressed because it is too large Load Diff

View File

@ -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 <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASAbstractLayoutController.h>
NS_ASSUME_NONNULL_BEGIN
@class ASCellNode;
typedef NS_ENUM(NSUInteger, ASFlowLayoutDirection) {
ASFlowLayoutDirectionVertical,
ASFlowLayoutDirectionHorizontal,
};
@protocol ASFlowLayoutControllerDataSource
- (NSArray<NSArray <ASCellNode *> *> *)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 <ASFlowLayoutControllerDataSource> dataSource;
- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -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 <AsyncDisplayKit/ASFlowLayoutController.h>
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASDisplayNode.h>
#import <AsyncDisplayKit/ASIndexPath.h>
#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
#include <map>
#include <vector>
@interface ASFlowLayoutController()
{
ASIndexPathRange _visibleRange;
std::vector<ASIndexPathRange> _rangesByType; // All ASLayoutRangeTypes besides visible.
}
@end
@implementation ASFlowLayoutController
- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection
{
if (!(self = [super init])) {
return nil;
}
_layoutDirection = layoutDirection;
_rangesByType = std::vector<ASIndexPathRange>(ASLayoutRangeTypeCount);
return self;
}
#pragma mark - Visible Indices
- (void)setVisibleNodeIndexPaths:(NSArray *)indexPaths
{
_visibleRange = [self indexPathRangeForIndexPaths:indexPaths];
}
/**
* IndexPath array for the element in the working range.
*/
- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType
{
CGSize viewportSize = [self viewportSize];
CGFloat viewportDirectionalSize = 0.0;
ASDirectionalScreenfulBuffer directionalBuffer = { 0, 0 };
ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType];
if (_layoutDirection == ASFlowLayoutDirectionHorizontal) {
viewportDirectionalSize = viewportSize.width;
directionalBuffer = ASDirectionalScreenfulBufferHorizontal(scrollDirection, tuningParameters);
} else {
viewportDirectionalSize = viewportSize.height;
directionalBuffer = ASDirectionalScreenfulBufferVertical(scrollDirection, tuningParameters);
}
ASIndexPath startPath = [self findIndexPathAtDistance:(-directionalBuffer.negativeDirection * viewportDirectionalSize)
fromIndexPath:_visibleRange.start];
ASIndexPath endPath = [self findIndexPathAtDistance:(directionalBuffer.positiveDirection * viewportDirectionalSize)
fromIndexPath:_visibleRange.end];
ASDisplayNodeAssert(startPath.section <= endPath.section, @"startPath should never begin at a further position than endPath");
NSMutableSet *indexPathSet = [[NSMutableSet alloc] init];
NSArray *completedNodes = [_dataSource completedNodes];
ASIndexPath currPath = startPath;
while (!ASIndexPathEqualToIndexPath(currPath, endPath)) {
[indexPathSet addObject:[NSIndexPath indexPathWithASIndexPath:currPath]];
currPath.row++;
// Once we reach the end of the section, advance to the next one. Keep advancing if the next section is zero-sized.
while (currPath.row >= [(NSArray *)completedNodes[currPath.section] count] && currPath.section < endPath.section) {
currPath.row = 0;
currPath.section++;
}
}
ASDisplayNodeAssert(currPath.section <= endPath.section, @"currPath should never reach a further section than endPath");
[indexPathSet addObject:[NSIndexPath indexPathWithASIndexPath:endPath]];
return indexPathSet;
}
#pragma mark - Utility
- (ASIndexPathRange)indexPathRangeForIndexPaths:(NSArray *)indexPaths
{
// Set up an initial value so the MIN and MAX can work in the enumeration.
__block ASIndexPath currentIndexPath = [[indexPaths firstObject] ASIndexPathValue];
__block ASIndexPathRange range;
range.start = currentIndexPath;
range.end = currentIndexPath;
for (NSIndexPath *indexPath in indexPaths) {
currentIndexPath = [indexPath ASIndexPathValue];
range.start = ASIndexPathMinimum(range.start, currentIndexPath);
range.end = ASIndexPathMaximum(range.end, currentIndexPath);
}
return range;
}
- (ASIndexPath)findIndexPathAtDistance:(CGFloat)distance fromIndexPath:(ASIndexPath)start
{
// "end" is the index path we'll advance until we have gone far enough from "start" to reach "distance"
ASIndexPath end = start;
// "previous" will store one iteration before "end", in case we go too far and need to reset "end" to be "previous"
ASIndexPath previous = start;
NSArray *completedNodes = [_dataSource completedNodes];
NSUInteger numberOfSections = [completedNodes count];
NSUInteger numberOfRowsInSection = [(NSArray *)completedNodes[end.section] count];
// If "distance" is negative, advance "end" backwards across rows and sections.
// Otherwise, advance forward. In either case, bring "distance" closer to zero by the dimension of each row passed.
if (distance < 0.0 && end.section >= 0 && end.section < numberOfSections && end.row >= 0 && end.row < numberOfRowsInSection) {
while (distance < 0.0 && end.section >= 0 && end.row >= 0) {
previous = end;
ASDisplayNode *node = completedNodes[end.section][end.row];
CGSize size = node.calculatedSize;
distance += (_layoutDirection == ASFlowLayoutDirectionHorizontal ? size.width : size.height);
end.row--;
// If we've gone to a negative row, set to the last row of the previous section. While loop is required to handle empty sections.
while (end.row < 0 && end.section > 0) {
end.section--;
numberOfRowsInSection = [(NSArray *)completedNodes[end.section] count];
end.row = numberOfRowsInSection - 1;
}
}
if (end.row < 0) {
end = previous;
}
} else {
while (distance > 0.0 && end.section >= 0 && end.section < numberOfSections && end.row >= 0 && end.row < numberOfRowsInSection) {
previous = end;
ASDisplayNode *node = completedNodes[end.section][end.row];
CGSize size = node.calculatedSize;
distance -= _layoutDirection == ASFlowLayoutDirectionHorizontal ? size.width : size.height;
end.row++;
// If we've gone beyond the section, reset to the beginning of the next section. While loop is required to handle empty sections.
while (end.row >= numberOfRowsInSection && end.section < numberOfSections - 1) {
end.row = 0;
end.section++;
numberOfRowsInSection = [(NSArray *)completedNodes[end.section] count];
}
}
if (end.row >= numberOfRowsInSection) {
end = previous;
}
}
return end;
}
- (NSInteger)flowLayoutDistanceForRange:(ASIndexPathRange)range
{
// This method should only be called with the range in proper order (start comes before end).
ASDisplayNodeAssert(ASIndexPathEqualToIndexPath(ASIndexPathMinimum(range.start, range.end), range.start), @"flowLayoutDistanceForRange: called with invalid range");
if (ASIndexPathEqualToIndexPath(range.start, range.end)) {
return 0;
}
NSInteger totalRowCount = 0;
NSUInteger numberOfRowsInSection = 0;
NSArray *completedNodes = [_dataSource completedNodes];
for (NSInteger section = range.start.section; section <= range.end.section; section++) {
numberOfRowsInSection = [(NSArray *)completedNodes[section] count];
totalRowCount += numberOfRowsInSection;
if (section == range.start.section) {
// For the start section, make sure we don't count the rows before the start row.
totalRowCount -= range.start.row;
} else if (section == range.end.section) {
// For the start section, make sure we don't count the rows after the end row.
totalRowCount -= (numberOfRowsInSection - (range.end.row + 1));
}
}
ASDisplayNodeAssert(totalRowCount >= 0, @"totalRowCount in flowLayoutDistanceForRange: should not be negative");
return totalRowCount;
}
@end
#endif

View File

@ -0,0 +1,181 @@
//
// ASDataController+Subclasses.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
#pragma once
#import <vector>
@class ASIndexedNodeContext;
typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths);
@interface ASDataController (Subclasses)
#pragma mark - Internal editing & completed store querying
/**
* Read-only access to the underlying editing nodes of the given kind
*/
- (NSMutableArray *)editingNodesOfKind:(NSString *)kind;
/**
* Read only access to the underlying completed nodes of the given kind
*/
- (NSMutableArray *)completedNodesOfKind:(NSString *)kind;
#pragma mark - Node sizing
/**
* Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`.
*
* This method runs synchronously.
* @param batchCompletion A handler to be run after each batch is completed. It is executed synchronously on the calling thread.
*/
- (void)batchLayoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler;
/**
* Provides the size range for a specific node during the layout process.
*/
- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
#pragma mark - Node & Section Insertion/Deletion API
/**
* Inserts the given nodes of the specified kind into the backing store, calling completion on the main thread when the write finishes.
*/
- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock;
/**
* Deletes the given nodes of the specified kind in the backing store, calling completion on the main thread when the deletion finishes.
*/
- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock;
/**
* Inserts the given sections of the specified kind in the backing store, calling completion on the main thread when finished.
*/
- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock;
/**
* Deletes the given sections in the backing store, calling completion on the main thread when finished.
*/
- (void)deleteSections:(NSIndexSet *)indexSet completion:(void (^)())completionBlock;
#pragma mark - Data Manipulation Hooks
/**
* Notifies the subclass to perform any work needed before the data controller is reloaded entirely
*
* @discussion This method will be performed before the data controller enters its editing queue.
* The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
* data stores before entering into editing the backing store on a background thread.
*/
- (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount;
/**
* Notifies the subclass that the data controller is about to reload its data entirely
*
* @discussion This method will be performed on the data controller's editing background queue before the parent's
* concrete implementation. This is a great place to perform new node creation like supplementary views
* or header/footer nodes.
*/
- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount;
/**
* Notifies the subclass to perform setup before sections are inserted in the data controller
*
* @discussion This method will be performed before the data controller enters its editing queue.
* The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
* data stores before entering into editing the backing store on a background thread.
*
* @param sections Indices of sections to be inserted
*/
- (void)prepareForInsertSections:(NSIndexSet *)sections;
/**
* Notifies the subclass that the data controller will insert new sections at the given position
*
* @discussion This method will be performed on the data controller's editing background queue before the parent's
* concrete implementation. This is a great place to perform any additional transformations like supplementary views
* or header/footer nodes.
*
* @param sections Indices of sections to be inserted
*/
- (void)willInsertSections:(NSIndexSet *)sections;
/**
* Notifies the subclass to perform setup before sections are deleted in the data controller
*
* @discussion This method will be performed before the data controller enters its editing queue.
* The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
* data stores before entering into editing the backing store on a background thread.
*
* @param sections Indices of sections to be inserted
*/
- (void)prepareForDeleteSections:(NSIndexSet *)sections;
/**
* Notifies the subclass that the data controller will delete sections at the given positions
*
* @discussion This method will be performed on the data controller's editing background queue before the parent's
* concrete implementation. This is a great place to perform any additional transformations like supplementary views
* or header/footer nodes.
*
* @param sections Indices of sections to be deleted
*/
- (void)willDeleteSections:(NSIndexSet *)sections;
/**
* Notifies the subclass to perform setup before rows are inserted in the data controller.
*
* @discussion This method will be performed before the data controller enters its editing queue.
* The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
* data stores before entering into editing the backing store on a background thread.
*
* @param indexPaths Index paths for the rows to be inserted.
*/
- (void)prepareForInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
/**
* Notifies the subclass that the data controller will insert new rows at the given index paths.
*
* @discussion This method will be performed on the data controller's editing background queue before the parent's
* concrete implementation. This is a great place to perform any additional transformations like supplementary views
* or header/footer nodes.
*
* @param indexPaths Index paths for the rows to be inserted.
*/
- (void)willInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
/**
* Notifies the subclass to perform setup before rows are deleted in the data controller.
*
* @discussion This method will be performed before the data controller enters its editing queue.
* The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
* data stores before entering into editing the backing store on a background thread.
*
* @param indexPaths Index paths for the rows to be deleted.
*/
- (void)prepareForDeleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
/**
* Notifies the subclass that the data controller will delete rows at the given index paths.
*
* @discussion This method will be performed before the data controller enters its editing queue.
* The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
* data stores before entering into editing the backing store on a background thread.
*
* @param indexPaths Index paths for the rows to be deleted.
*/
- (void)willDeleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
@end
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0940"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B35061D91B010EDF0018CF92"
BuildableName = "AsyncDisplayKit.framework"
BlueprintName = "AsyncDisplayKit"
ReferencedContainer = "container:AsyncDisplayKit_Xcode.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "058D09BB195D04C000B7D73C"
BuildableName = "AsyncDisplayKitTests.xctest"
BlueprintName = "AsyncDisplayKitTests"
ReferencedContainer = "container:AsyncDisplayKit_Xcode.xcodeproj">
</BuildableReference>
<SkippedTests>
<Test
Identifier = "ASTextNodePerformanceTests">
</Test>
</SkippedTests>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B35061D91B010EDF0018CF92"
BuildableName = "AsyncDisplayKit.framework"
BlueprintName = "AsyncDisplayKit"
ReferencedContainer = "container:AsyncDisplayKit_Xcode.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B35061D91B010EDF0018CF92"
BuildableName = "AsyncDisplayKit.framework"
BlueprintName = "AsyncDisplayKit"
ReferencedContainer = "container:AsyncDisplayKit_Xcode.xcodeproj">
</BuildableReference>
</MacroExpansion>
<EnvironmentVariables>
<EnvironmentVariable
key = "FB_REFERENCE_IMAGE_DIR"
value = "$(SOURCE_ROOT)/Tests/ReferenceImages"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B35061D91B010EDF0018CF92"
BuildableName = "AsyncDisplayKit.framework"
BlueprintName = "AsyncDisplayKit"
ReferencedContainer = "container:AsyncDisplayKit_Xcode.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
<<<<<<< HEAD
LastUpgradeVersion = "0900"
=======
LastUpgradeVersion = "0940"
>>>>>>> 565da7d4935740d12fc204aa061faf093831da1e
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B35061D91B010EDF0018CF92"
BuildableName = "AsyncDisplayKit.framework"
BlueprintName = "AsyncDisplayKit"
ReferencedContainer = "container:AsyncDisplayKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "058D09BB195D04C000B7D73C"
BuildableName = "AsyncDisplayKitTests.xctest"
BlueprintName = "AsyncDisplayKitTests"
ReferencedContainer = "container:AsyncDisplayKit.xcodeproj">
</BuildableReference>
<SkippedTests>
<Test
Identifier = "ASTextNodePerformanceTests">
</Test>
</SkippedTests>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B35061D91B010EDF0018CF92"
BuildableName = "AsyncDisplayKit.framework"
BlueprintName = "AsyncDisplayKit"
ReferencedContainer = "container:AsyncDisplayKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B35061D91B010EDF0018CF92"
BuildableName = "AsyncDisplayKit.framework"
BlueprintName = "AsyncDisplayKit"
ReferencedContainer = "container:AsyncDisplayKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<EnvironmentVariables>
<EnvironmentVariable
key = "FB_REFERENCE_IMAGE_DIR"
value = "$(SOURCE_ROOT)/Tests/ReferenceImages"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B35061D91B010EDF0018CF92"
BuildableName = "AsyncDisplayKit.framework"
BlueprintName = "AsyncDisplayKit"
ReferencedContainer = "container:AsyncDisplayKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

64
submodules/AsyncDisplayKit/BUCK Executable file
View File

@ -0,0 +1,64 @@
load('//tools:buck_utils.bzl', 'config_with_updated_linker_flags', 'combined_config', 'configs_with_config')
load('//tools:buck_defs.bzl', 'SHARED_CONFIGS', 'EXTENSION_LIB_SPECIFIC_CONFIG')
COMMON_PREPROCESSOR_FLAGS = [
'-fobjc-arc',
'-DMINIMAL_ASDK',
'-fno-exceptions',
'-fno-objc-arc-exceptions'
]
COMMON_LANG_PREPROCESSOR_FLAGS = {
'C': ['-std=gnu99'],
'CXX': ['-std=c++11', '-stdlib=libc++'],
'OBJCXX': ['-std=c++11', '-stdlib=libc++'],
}
COMMON_LINKER_FLAGS = ['-ObjC++']
ASYNCDISPLAYKIT_EXPORTED_HEADERS = glob([
'Source/*.h',
'Source/Details/**/*.h',
'Source/Layout/*.h',
'Source/Base/*.h',
'Source/Debug/AsyncDisplayKit+Debug.h',
# Most TextKit components are not public because the C++ content
# in the headers will cause build errors when using
# `use_frameworks!` on 0.39.0 & Swift 2.1.
# See https://github.com/facebook/AsyncDisplayKit/issues/1153
'Source/TextKit/ASTextNodeTypes.h',
'Source/TextKit/ASTextKitComponents.h'
])
ASYNCDISPLAYKIT_PRIVATE_HEADERS = glob([
'Source/**/*.h'
],
exclude = ASYNCDISPLAYKIT_EXPORTED_HEADERS,
)
apple_library(
name = "AsyncDisplayKit",
header_path_prefix = 'AsyncDisplayKit',
exported_headers = ASYNCDISPLAYKIT_EXPORTED_HEADERS,
headers = ASYNCDISPLAYKIT_PRIVATE_HEADERS,
srcs = glob([
'Source/**/*.m',
'Source/**/*.mm',
'Source/Base/*.m'
]),
configs = configs_with_config(combined_config([SHARED_CONFIGS, EXTENSION_LIB_SPECIFIC_CONFIG])),
preprocessor_flags = COMMON_PREPROCESSOR_FLAGS,
lang_preprocessor_flags = COMMON_LANG_PREPROCESSOR_FLAGS,
linker_flags = COMMON_LINKER_FLAGS,
modular = True,
compiler_flags = ['-w'],
visibility = ["PUBLIC"],
frameworks = [
'$SDKROOT/System/Library/Frameworks/Foundation.framework',
'$SDKROOT/System/Library/Frameworks/UIKit.framework',
'$SDKROOT/System/Library/Frameworks/QuartzCore.framework',
'$SDKROOT/System/Library/Frameworks/CoreMedia.framework',
'$SDKROOT/System/Library/Frameworks/CoreText.framework',
'$SDKROOT/System/Library/Frameworks/CoreGraphics.framework',
]
)

View File

@ -0,0 +1,685 @@
# Change Log
## [2.8](https://github.com/TextureGroup/Texture/tree/2.8) (2019-02-12)
[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.7...2.8)
**Merged pull requests:**
- Remove duplicate definition of category "YogaDebugging" [\#1331](https://github.com/TextureGroup/Texture/pull/1331) ([nguyenhuy](https://github.com/nguyenhuy))
- Add Yoga layout to ASDKGram Texture cells [\#1315](https://github.com/TextureGroup/Texture/pull/1315) ([maicki](https://github.com/maicki))
- Remove let and var macros now that we're all-C++ [\#1312](https://github.com/TextureGroup/Texture/pull/1312) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Add experiments to skip waiting for updates of collection and table views [\#1311](https://github.com/TextureGroup/Texture/pull/1311) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASCollectionView\] Supplementary nodes should not enter ASHierarchyStateRangeManaged. [\#1310](https://github.com/TextureGroup/Texture/pull/1310) ([appleguy](https://github.com/appleguy))
- Fix deprecated implementations warning [\#1306](https://github.com/TextureGroup/Texture/pull/1306) ([maicki](https://github.com/maicki))
- Improve separation of code for layout method types [\#1305](https://github.com/TextureGroup/Texture/pull/1305) ([maicki](https://github.com/maicki))
- Fix loading items in ASDKGram IGListKit tab [\#1300](https://github.com/TextureGroup/Texture/pull/1300) ([maicki](https://github.com/maicki))
- performance spell correction [\#1298](https://github.com/TextureGroup/Texture/pull/1298) ([wxyong](https://github.com/wxyong))
- Add some snapshot tests for ASTextNode2 truncation modes. [\#1296](https://github.com/TextureGroup/Texture/pull/1296) ([wiseoldduck](https://github.com/wiseoldduck))
- Reduce startup time. [\#1294](https://github.com/TextureGroup/Texture/pull/1294) ([dmaclach](https://github.com/dmaclach))
- Reduce startup time. [\#1293](https://github.com/TextureGroup/Texture/pull/1293) ([dmaclach](https://github.com/dmaclach))
- Reduce startup time. [\#1292](https://github.com/TextureGroup/Texture/pull/1292) ([dmaclach](https://github.com/dmaclach))
- Reduce startup time. [\#1291](https://github.com/TextureGroup/Texture/pull/1291) ([dmaclach](https://github.com/dmaclach))
- Reduce startup time. [\#1288](https://github.com/TextureGroup/Texture/pull/1288) ([dmaclach](https://github.com/dmaclach))
- Add a way to opt out of always-clear-data behavior in ASCollectionView and ASTableView [\#1284](https://github.com/TextureGroup/Texture/pull/1284) ([nguyenhuy](https://github.com/nguyenhuy))
- Copy yogaChildren in accessor method. Avoid using accessor method internally [\#1283](https://github.com/TextureGroup/Texture/pull/1283) ([maicki](https://github.com/maicki))
- Use cell mode while wrapping supplementary nodes [\#1282](https://github.com/TextureGroup/Texture/pull/1282) ([maicki](https://github.com/maicki))
- Access thread safe property to avoid assertion [\#1281](https://github.com/TextureGroup/Texture/pull/1281) ([wiseoldduck](https://github.com/wiseoldduck))
- Match AS\_USE\_VIDEO usage in tests to definitions [\#1280](https://github.com/TextureGroup/Texture/pull/1280) ([wiseoldduck](https://github.com/wiseoldduck))
- Update test imports to use framework import [\#1279](https://github.com/TextureGroup/Texture/pull/1279) ([maicki](https://github.com/maicki))
- Set automaticallyAdjustsContentOffset to ASTableView when view is load [\#1278](https://github.com/TextureGroup/Texture/pull/1278) ([strangeliu](https://github.com/strangeliu))
- Remove UIKit header import in AsyncTransaction file [\#1275](https://github.com/TextureGroup/Texture/pull/1275) ([zhongwuzw](https://github.com/zhongwuzw))
- Disable a11y cache [\#1274](https://github.com/TextureGroup/Texture/pull/1274) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Introduce ASCellLayoutMode [\#1273](https://github.com/TextureGroup/Texture/pull/1273) ([maicki](https://github.com/maicki))
- During yoga layout, escalate directly to yoga root rather than walking up [\#1269](https://github.com/TextureGroup/Texture/pull/1269) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Forward hitTest:withEvent and piontInside:withEvent: to node within \_ASCollectionViewCell [\#1268](https://github.com/TextureGroup/Texture/pull/1268) ([maicki](https://github.com/maicki))
- Wrap supplementary node blocks to enable resizing them. [\#1265](https://github.com/TextureGroup/Texture/pull/1265) ([wiseoldduck](https://github.com/wiseoldduck))
- Move Bluebird to new row. [\#1264](https://github.com/TextureGroup/Texture/pull/1264) ([ay8s](https://github.com/ay8s))
- Added Bluebird [\#1263](https://github.com/TextureGroup/Texture/pull/1263) ([ShihabM](https://github.com/ShihabM))
- Move assertions so they are valid. [\#1261](https://github.com/TextureGroup/Texture/pull/1261) ([wiseoldduck](https://github.com/wiseoldduck))
- Fix isTruncated logic in ASTextNode2 [\#1259](https://github.com/TextureGroup/Texture/pull/1259) ([maicki](https://github.com/maicki))
- Documentation typo, "trying" written two times [\#1258](https://github.com/TextureGroup/Texture/pull/1258) ([tataevr](https://github.com/tataevr))
- \[ASPrimitiveTraitCollection\] Fix ASPrimitiveTraitCollectionMakeDefault and implement containerSize [\#1256](https://github.com/TextureGroup/Texture/pull/1256) ([rcancro](https://github.com/rcancro))
- Yoga debug info [\#1253](https://github.com/TextureGroup/Texture/pull/1253) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Avoid using global Mutex variables [\#1252](https://github.com/TextureGroup/Texture/pull/1252) ([nguyenhuy](https://github.com/nguyenhuy))
- Allow setting build.sh SDK and platform w/ env variables [\#1249](https://github.com/TextureGroup/Texture/pull/1249) ([wiseoldduck](https://github.com/wiseoldduck))
- add more delegate methods for monitoring network image node progress [\#1247](https://github.com/TextureGroup/Texture/pull/1247) ([ernestmama](https://github.com/ernestmama))
- Start a thrash test suite for the collection node [\#1246](https://github.com/TextureGroup/Texture/pull/1246) ([mikezucc](https://github.com/mikezucc))
- Add development docs structure [\#1245](https://github.com/TextureGroup/Texture/pull/1245) ([garrettmoon](https://github.com/garrettmoon))
- Convert YGUndefined back to CGFLOAT\_MAX for Texture layout [\#1244](https://github.com/TextureGroup/Texture/pull/1244) ([tnorman42](https://github.com/tnorman42))
- Add way to compile out ASTextNode + TextKit dependencies [\#1242](https://github.com/TextureGroup/Texture/pull/1242) ([maicki](https://github.com/maicki))
- Add AS\_USE\_VIDEO flag and subspec for Video [\#1240](https://github.com/TextureGroup/Texture/pull/1240) ([maicki](https://github.com/maicki))
- Releases/p6.78 [\#1236](https://github.com/TextureGroup/Texture/pull/1236) ([ernestmama](https://github.com/ernestmama))
- \[ASDisplayNode\] Propagate traits before loading a subnode [\#1234](https://github.com/TextureGroup/Texture/pull/1234) ([rcancro](https://github.com/rcancro))
- Correct some block self references to strongSelf [\#1231](https://github.com/TextureGroup/Texture/pull/1231) ([wiseoldduck](https://github.com/wiseoldduck))
- Update image-node.md [\#1230](https://github.com/TextureGroup/Texture/pull/1230) ([orkhan-huseynov](https://github.com/orkhan-huseynov))
- Have node and controller share lock [\#1227](https://github.com/TextureGroup/Texture/pull/1227) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Initialize mutex assertion variables [\#1226](https://github.com/TextureGroup/Texture/pull/1226) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Remove CHECK\_LOCKING\_SAFETY check [\#1225](https://github.com/TextureGroup/Texture/pull/1225) ([maicki](https://github.com/maicki))
- Clean up our mutex, fix try\_lock not hooking into assert mechanism [\#1219](https://github.com/TextureGroup/Texture/pull/1219) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Fix warning using \_\_builtin\_popcount [\#1218](https://github.com/TextureGroup/Texture/pull/1218) ([maicki](https://github.com/maicki))
- Fix A11Y for horizontal collection nodes in Texture [\#1217](https://github.com/TextureGroup/Texture/pull/1217) ([maicki](https://github.com/maicki))
- ASCATransactionQueue interface trashing improvements [\#1216](https://github.com/TextureGroup/Texture/pull/1216) ([maicki](https://github.com/maicki))
- Fix shouldTruncateForConstrainedSize in ASTextNode2 [\#1214](https://github.com/TextureGroup/Texture/pull/1214) ([maicki](https://github.com/maicki))
- ASThread: Remove Locker, Unlocker, and SharedMutex [\#1213](https://github.com/TextureGroup/Texture/pull/1213) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Cleanup Dangerfile [\#1212](https://github.com/TextureGroup/Texture/pull/1212) ([nguyenhuy](https://github.com/nguyenhuy))
- Rework ASTraitCollection to Fix Warnings and Remove Boilerplate [\#1211](https://github.com/TextureGroup/Texture/pull/1211) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Add -Wno-implicit-retain-self to podspec + smaller cleanups \#trivial [\#1209](https://github.com/TextureGroup/Texture/pull/1209) ([maicki](https://github.com/maicki))
- Address compiler warnings \#trivial [\#1207](https://github.com/TextureGroup/Texture/pull/1207) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Convert the codebase to Objective-C++ [\#1206](https://github.com/TextureGroup/Texture/pull/1206) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Add tests for accessibility [\#1205](https://github.com/TextureGroup/Texture/pull/1205) ([wiseoldduck](https://github.com/wiseoldduck))
- Revert \#1023 \#trivial [\#1204](https://github.com/TextureGroup/Texture/pull/1204) ([maicki](https://github.com/maicki))
- Follow up cleanup \#trivial [\#1203](https://github.com/TextureGroup/Texture/pull/1203) ([maicki](https://github.com/maicki))
- Add experiment flag to skip layoutIfNeeded in enterPreloadState for ASM nodes \#trivial [\#1201](https://github.com/TextureGroup/Texture/pull/1201) ([maicki](https://github.com/maicki))
- Fix logic cleaning data if delegate / dataSource changes and bring over logic to ASTableView [\#1200](https://github.com/TextureGroup/Texture/pull/1200) ([maicki](https://github.com/maicki))
- Tweak a11y label aggregation behavior to enable container label overrides [\#1199](https://github.com/TextureGroup/Texture/pull/1199) ([maicki](https://github.com/maicki))
- Fix shadowed var warning \(and add clarity\) \#trivial [\#1198](https://github.com/TextureGroup/Texture/pull/1198) ([wiseoldduck](https://github.com/wiseoldduck))
- Allow configuring imageCache when initializing ASPINRemoteImageDownloader. [\#1197](https://github.com/TextureGroup/Texture/pull/1197) ([wiseoldduck](https://github.com/wiseoldduck))
- ASTextNode2 to consider both width and height when determining if it is calculating an intrinsic size [\#1196](https://github.com/TextureGroup/Texture/pull/1196) ([ernestmama](https://github.com/ernestmama))
- Remove extraneous ";" \#trivial [\#1194](https://github.com/TextureGroup/Texture/pull/1194) ([wiseoldduck](https://github.com/wiseoldduck))
- Newline character support and truncated line sizing improvement. [\#1193](https://github.com/TextureGroup/Texture/pull/1193) ([wiseoldduck](https://github.com/wiseoldduck))
- Correct linePositionModifier behavior [\#1192](https://github.com/TextureGroup/Texture/pull/1192) ([maicki](https://github.com/maicki))
- Move AS\_TEXT\_ALERT\_UNIMPLEMENTED\_FEATURE into ASTextNodeCommon \#trivial [\#1191](https://github.com/TextureGroup/Texture/pull/1191) ([maicki](https://github.com/maicki))
- A11y for scrollnode [\#1188](https://github.com/TextureGroup/Texture/pull/1188) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Yoga integration improvements [\#1187](https://github.com/TextureGroup/Texture/pull/1187) ([maicki](https://github.com/maicki))
- Remove unnecessary ASWeakProxy import \#trivial [\#1186](https://github.com/TextureGroup/Texture/pull/1186) ([maicki](https://github.com/maicki))
- Directly use \_\_instanceLock\_\_ to lock / unlock without having to create and destroy a MutextUnlocker \#trivial [\#1185](https://github.com/TextureGroup/Texture/pull/1185) ([maicki](https://github.com/maicki))
- Dont handle touches on additional attributed message if passthrough is enabled [\#1184](https://github.com/TextureGroup/Texture/pull/1184) ([maicki](https://github.com/maicki))
- Set the default values for showsVerticalScrollIndicator and showsHorizontalScrollIndicator \#trivial [\#1181](https://github.com/TextureGroup/Texture/pull/1181) ([maicki](https://github.com/maicki))
- Move import of stdatomic to ASRecursiveUnfairLock implementation file \#trivial [\#1180](https://github.com/TextureGroup/Texture/pull/1180) ([maicki](https://github.com/maicki))
- Add NSLocking conformance to ASNodeController [\#1179](https://github.com/TextureGroup/Texture/pull/1179) ([maicki](https://github.com/maicki))
- Only initialize framework once, avoid multiple across tests \#trivial [\#1178](https://github.com/TextureGroup/Texture/pull/1178) ([maicki](https://github.com/maicki))
- Expose a way to determine if a text node will truncate for a given constrained size \#trivial [\#1177](https://github.com/TextureGroup/Texture/pull/1177) ([maicki](https://github.com/maicki))
- Fix define spaces \#trivial [\#1176](https://github.com/TextureGroup/Texture/pull/1176) ([maicki](https://github.com/maicki))
- Expose test\_resetWithConfiguration: for testing \#trivial [\#1175](https://github.com/TextureGroup/Texture/pull/1175) ([maicki](https://github.com/maicki))
- Add way to suppress invalid CollectionUpdateExceptions \#trivial [\#1173](https://github.com/TextureGroup/Texture/pull/1173) ([maicki](https://github.com/maicki))
- Use interface state to manage image loading \#trivial [\#1172](https://github.com/TextureGroup/Texture/pull/1172) ([maicki](https://github.com/maicki))
- ASTableNode init method match checks from ASCollectionNode [\#1171](https://github.com/TextureGroup/Texture/pull/1171) ([maicki](https://github.com/maicki))
- \[ASDisplayNode\] Expose default Texture-set accessibility values as properties [\#1170](https://github.com/TextureGroup/Texture/pull/1170) ([jiawernlim](https://github.com/jiawernlim))
- Fix mismatch in UIAccessibilityAction selector method [\#1169](https://github.com/TextureGroup/Texture/pull/1169) ([maicki](https://github.com/maicki))
- Small fix in ASTextKitRenderer \#trivial [\#1167](https://github.com/TextureGroup/Texture/pull/1167) ([nguyenhuy](https://github.com/nguyenhuy))
- ASTextNode2 to ignore certain text alignments while calculating intrinsic size [\#1166](https://github.com/TextureGroup/Texture/pull/1166) ([nguyenhuy](https://github.com/nguyenhuy))
- Update Jekyll to 3.6.3 [\#1165](https://github.com/TextureGroup/Texture/pull/1165) ([nguyenhuy](https://github.com/nguyenhuy))
- Migrate placeholder example project from 1.0 to 2.x [\#1164](https://github.com/TextureGroup/Texture/pull/1164) ([ay8s](https://github.com/ay8s))
- Update documentation of ASNetworkImageNodeDelegate \#trivial [\#1163](https://github.com/TextureGroup/Texture/pull/1163) ([nguyenhuy](https://github.com/nguyenhuy))
- Make ASEditableTextNode accessible to VoiceOver [\#1162](https://github.com/TextureGroup/Texture/pull/1162) ([ay8s](https://github.com/ay8s))
- Mismatch name experimental features [\#1159](https://github.com/TextureGroup/Texture/pull/1159) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Set default tuning params [\#1158](https://github.com/TextureGroup/Texture/pull/1158) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Clean up timing of layout tree flattening/ copying of unflattened tree for Weaver [\#1157](https://github.com/TextureGroup/Texture/pull/1157) ([mikezucc](https://github.com/mikezucc))
- Only clear ASCollectionView's data during deallocation [\#1154](https://github.com/TextureGroup/Texture/pull/1154) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASTextNode2\] Add improved support for all line-break modes in experimental text node. [\#1150](https://github.com/TextureGroup/Texture/pull/1150) ([wiseoldduck](https://github.com/wiseoldduck))
- \[ASImageNode\] Fix a threading issue which can cause a display completion block to never be executed [\#1148](https://github.com/TextureGroup/Texture/pull/1148) ([nguyenhuy](https://github.com/nguyenhuy))
- Guard photo library with macro for tests [\#1147](https://github.com/TextureGroup/Texture/pull/1147) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Rollout ASDeallocQueueV2 \#trivial [\#1143](https://github.com/TextureGroup/Texture/pull/1143) ([ernestmama](https://github.com/ernestmama))
- Fix crash setting attributed text on multiple threads [\#1141](https://github.com/TextureGroup/Texture/pull/1141) ([maicki](https://github.com/maicki))
- Add missing NS\_NOESCAPE attributes in overwritten methods \#trivial [\#1139](https://github.com/TextureGroup/Texture/pull/1139) ([ejensen](https://github.com/ejensen))
- Add missing comma in ASExperimentalFeatures \#trivial [\#1137](https://github.com/TextureGroup/Texture/pull/1137) ([nguyenhuy](https://github.com/nguyenhuy))
- Add ASExperimentalSkipClearData \#trivial [\#1136](https://github.com/TextureGroup/Texture/pull/1136) ([maicki](https://github.com/maicki))
- Fix RemoteImageDownloader name mismatch \#trivial [\#1134](https://github.com/TextureGroup/Texture/pull/1134) ([ernestmama](https://github.com/ernestmama))
- Fix compilation warnings \#trivial [\#1132](https://github.com/TextureGroup/Texture/pull/1132) ([ejensen](https://github.com/ejensen))
- Remove reliance on shared\_ptr for ASDisplayNodeLayouts [\#1131](https://github.com/TextureGroup/Texture/pull/1131) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Make yoga & layout specs faster by eliminating some copies [\#1128](https://github.com/TextureGroup/Texture/pull/1128) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Remove ASRectMap, which is not worth its own weight [\#1127](https://github.com/TextureGroup/Texture/pull/1127) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASPINRemoteImageDownloader\] Fix +setSharedPreconfiguredRemoteImageManager:'s doc \#trivial [\#1126](https://github.com/TextureGroup/Texture/pull/1126) ([nguyenhuy](https://github.com/nguyenhuy))
- Add a method for setting preconfigured PINRemoteImageManager [\#1124](https://github.com/TextureGroup/Texture/pull/1124) ([ernestmama](https://github.com/ernestmama))
- Don't copy onDidLoadBlocks \#trivial [\#1123](https://github.com/TextureGroup/Texture/pull/1123) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Remove use of NSHashTable for interface state delegates \#trivial [\#1122](https://github.com/TextureGroup/Texture/pull/1122) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Fix typos and minor code cleanups \#trivial [\#1120](https://github.com/TextureGroup/Texture/pull/1120) ([nguyenhuy](https://github.com/nguyenhuy))
- Don't setNeedsDisplay on text node 2 measure \#trivial [\#1116](https://github.com/TextureGroup/Texture/pull/1116) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Don't copy container during ASTextNode2 measure [\#1115](https://github.com/TextureGroup/Texture/pull/1115) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Make interface state delegate non optional [\#1112](https://github.com/TextureGroup/Texture/pull/1112) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Interface state not update correctly during layer thrash. [\#1111](https://github.com/TextureGroup/Texture/pull/1111) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Fix layer backed nodes not update properly [\#1110](https://github.com/TextureGroup/Texture/pull/1110) ([wsdwsd0829](https://github.com/wsdwsd0829))
- changelog fix: let / var macros did not make it to 2.7 [\#1109](https://github.com/TextureGroup/Texture/pull/1109) ([jozsefmihalicza](https://github.com/jozsefmihalicza))
- Improve locking around clearContents [\#1107](https://github.com/TextureGroup/Texture/pull/1107) ([maicki](https://github.com/maicki))
- Add missing argument for calling image download completion block \#trivial [\#1106](https://github.com/TextureGroup/Texture/pull/1106) ([maicki](https://github.com/maicki))
- Fix URL for blog about Pinterest [\#1105](https://github.com/TextureGroup/Texture/pull/1105) ([muukii](https://github.com/muukii))
- Remove necessity to use view to access rangeController in ASTableNode, ASCollectionNode [\#1103](https://github.com/TextureGroup/Texture/pull/1103) ([maicki](https://github.com/maicki))
- Add a -textureDidInitialize delegate callback [\#1100](https://github.com/TextureGroup/Texture/pull/1100) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Reuse interface state delegates when calling out \#trivial [\#1099](https://github.com/TextureGroup/Texture/pull/1099) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Add an explicit cast to satisfy strict compilers \#trivial [\#1098](https://github.com/TextureGroup/Texture/pull/1098) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Fix a couple typos. [\#1092](https://github.com/TextureGroup/Texture/pull/1092) ([jtbthethird](https://github.com/jtbthethird))
- \#trivial Shouldn't hold the lock while adding subnodes [\#1091](https://github.com/TextureGroup/Texture/pull/1091) ([garrettmoon](https://github.com/garrettmoon))
- Allow to add interface state delegate in background. [\#1090](https://github.com/TextureGroup/Texture/pull/1090) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Fix Typo [\#1089](https://github.com/TextureGroup/Texture/pull/1089) ([jtbthethird](https://github.com/jtbthethird))
- Add subnode should not be called with the lock held. \#trivial [\#1088](https://github.com/TextureGroup/Texture/pull/1088) ([garrettmoon](https://github.com/garrettmoon))
- Unlock before cleanup and calling out to subclass hooks for animated images. [\#1087](https://github.com/TextureGroup/Texture/pull/1087) ([maicki](https://github.com/maicki))
- Fix collection editing [\#1081](https://github.com/TextureGroup/Texture/pull/1081) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Fix compiler error in ASLocking \#trivial [\#1079](https://github.com/TextureGroup/Texture/pull/1079) ([nguyenhuy](https://github.com/nguyenhuy))
- Update showcase to add Wishpoke [\#1078](https://github.com/TextureGroup/Texture/pull/1078) ([dhatuna](https://github.com/dhatuna))
- \[License\] Simplify the Texture license to be pure Apache 2 \(removing ASDK-Licenses\). [\#1077](https://github.com/TextureGroup/Texture/pull/1077) ([appleguy](https://github.com/appleguy))
- Fix multiple documentation issues \#trivial [\#1073](https://github.com/TextureGroup/Texture/pull/1073) ([maicki](https://github.com/maicki))
- Refactored `accessibleElements` to `accessibilityElements` [\#1069](https://github.com/TextureGroup/Texture/pull/1069) ([jiawernlim](https://github.com/jiawernlim))
- Readability improvements in ASDataController \#trivial [\#1067](https://github.com/TextureGroup/Texture/pull/1067) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Remove direct ivar access on non-self object to fix mocking case \#trivial [\#1066](https://github.com/TextureGroup/Texture/pull/1066) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Reduce copying in ASTextNode2 stack [\#1065](https://github.com/TextureGroup/Texture/pull/1065) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Add an experimental framesetter cache in ASTextNode2 [\#1063](https://github.com/TextureGroup/Texture/pull/1063) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Remove extra string/attributed string creation in accessibility props [\#1062](https://github.com/TextureGroup/Texture/pull/1062) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Remove objc association & weak proxy from node -\> controller pointer [\#1061](https://github.com/TextureGroup/Texture/pull/1061) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Remove CATransaction signposts [\#1060](https://github.com/TextureGroup/Texture/pull/1060) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASTextNode2\] Simplify allocWithZone: + initialize implementation \#trivial [\#1059](https://github.com/TextureGroup/Texture/pull/1059) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASTextNode\] Fixes in ASTextKitFontSizeAdjuster [\#1056](https://github.com/TextureGroup/Texture/pull/1056) ([ejensen](https://github.com/ejensen))
- Revert "Optimize drawing code + add examples how to round corners \(\#996\) [\#1055](https://github.com/TextureGroup/Texture/pull/1055) ([maicki](https://github.com/maicki))
- Add NS\_DESIGNATED\_INITIALIZER to ASViewController initWithNode: [\#1054](https://github.com/TextureGroup/Texture/pull/1054) ([maicki](https://github.com/maicki))
- Fix headers in markdown [\#1053](https://github.com/TextureGroup/Texture/pull/1053) ([Un3qual](https://github.com/Un3qual))
- Avoid setting frame on a node's backing store while holding its lock [\#1048](https://github.com/TextureGroup/Texture/pull/1048) ([nguyenhuy](https://github.com/nguyenhuy))
- \#trivial Add a comment about tiling mode and issue \#1046 [\#1047](https://github.com/TextureGroup/Texture/pull/1047) ([wiseoldduck](https://github.com/wiseoldduck))
- Add documentation for rounding corners within Texture \#trivial [\#1044](https://github.com/TextureGroup/Texture/pull/1044) ([maicki](https://github.com/maicki))
- Improve locking situation in ASVideoPlayerNode [\#1042](https://github.com/TextureGroup/Texture/pull/1042) ([maicki](https://github.com/maicki))
- Revert unreleased layout debug method name change from \#1030 \#trivial [\#1039](https://github.com/TextureGroup/Texture/pull/1039) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Pin OCMock version to 3.4.1 because 3.4.2 has issues [\#1038](https://github.com/TextureGroup/Texture/pull/1038) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Fix & update ASCollectionNode constrained size doc. \#trivial [\#1037](https://github.com/TextureGroup/Texture/pull/1037) ([ay8s](https://github.com/ay8s))
- Fix warning for ASLayout method override for the designated initializer of the superclass '-init' not found \#trivial [\#1036](https://github.com/TextureGroup/Texture/pull/1036) ([maicki](https://github.com/maicki))
- Fix the bug I introduced in \#1030 \#trivial [\#1035](https://github.com/TextureGroup/Texture/pull/1035) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Turn off exceptions to reduce binary size \(-600KB for arm64\) [\#1033](https://github.com/TextureGroup/Texture/pull/1033) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Turn lock-checking on only when assertions are enabled \#trivial [\#1032](https://github.com/TextureGroup/Texture/pull/1032) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Remove NSMutableArray for retaining sublayout elements [\#1030](https://github.com/TextureGroup/Texture/pull/1030) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Create and set delegate for clip corner layers within ASDisplayNode [\#1029](https://github.com/TextureGroup/Texture/pull/1029) ([maicki](https://github.com/maicki))
- Split framework dependencies into separate subspecs [\#1028](https://github.com/TextureGroup/Texture/pull/1028) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Remove misleading comment and add assertion \#trivial [\#1027](https://github.com/TextureGroup/Texture/pull/1027) ([wiseoldduck](https://github.com/wiseoldduck))
- Address warnings in Xcode \>= 9.3 about using %zd for NSInteger \#trivial [\#1026](https://github.com/TextureGroup/Texture/pull/1026) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Fix 32-bit simulator build on Xcode \>= 9.3 [\#1025](https://github.com/TextureGroup/Texture/pull/1025) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Stricter locking assertions [\#1024](https://github.com/TextureGroup/Texture/pull/1024) ([nguyenhuy](https://github.com/nguyenhuy))
- Make sure -\_completePendingLayoutTransition is called without the node's instance lock \#trivial [\#1023](https://github.com/TextureGroup/Texture/pull/1023) ([nguyenhuy](https://github.com/nguyenhuy))
- Fix misleading/scary stack trace shown when an assertion occurs during node measurement [\#1022](https://github.com/TextureGroup/Texture/pull/1022) ([nguyenhuy](https://github.com/nguyenhuy))
- Add an introduction for ASCornerLayoutSpec in layout2-layoutspec-types.md \#trivial [\#1021](https://github.com/TextureGroup/Texture/pull/1021) ([huang-kun](https://github.com/huang-kun))
- Add showsHorizontal\(Vertical\)ScrollIndicator property applying from pending state \#trivial [\#1016](https://github.com/TextureGroup/Texture/pull/1016) ([maicki](https://github.com/maicki))
- \[IGListKit\] Adds missing UIScrollViewDelegate method to DataSource proxy [\#1015](https://github.com/TextureGroup/Texture/pull/1015) ([wannabehero](https://github.com/wannabehero))
- Introduce let / var macros and some further cleanup [\#1012](https://github.com/TextureGroup/Texture/pull/1012) ([maicki](https://github.com/maicki))
- Properly consider node for responder methods [\#1008](https://github.com/TextureGroup/Texture/pull/1008) ([maicki](https://github.com/maicki))
- Background image load api [\#1007](https://github.com/TextureGroup/Texture/pull/1007) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Add move detection and support to ASLayoutTransition [\#1006](https://github.com/TextureGroup/Texture/pull/1006) ([wiseoldduck](https://github.com/wiseoldduck))
- Fix warnings and a memory leak \#trivial [\#1003](https://github.com/TextureGroup/Texture/pull/1003) ([maicki](https://github.com/maicki))
- Rewrite Swift Example [\#1002](https://github.com/TextureGroup/Texture/pull/1002) ([maicki](https://github.com/maicki))
- Remove yoga layout spec, which has been superseded [\#999](https://github.com/TextureGroup/Texture/pull/999) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Optimize drawing code + add examples how to round corners [\#996](https://github.com/TextureGroup/Texture/pull/996) ([maicki](https://github.com/maicki))
- Fix typo in containers-asviewcontroller.md [\#989](https://github.com/TextureGroup/Texture/pull/989) ([muukii](https://github.com/muukii))
- Create transfer-array method and use it [\#987](https://github.com/TextureGroup/Texture/pull/987) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Add missing instance variables in ASTextNode and warnings cleanup \#trivial [\#984](https://github.com/TextureGroup/Texture/pull/984) ([maicki](https://github.com/maicki))
- Optimize layout flattening [\#982](https://github.com/TextureGroup/Texture/pull/982) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Changed lost images to existing one. \#trivial [\#981](https://github.com/TextureGroup/Texture/pull/981) ([tataevr](https://github.com/tataevr))
- \[texturegroup.org\] Use valid link for Upgrade to 2.0 beta 1 page \#trivial [\#980](https://github.com/TextureGroup/Texture/pull/980) ([mikezucc](https://github.com/mikezucc))
- Adds support for having multiple interface state delegates. [\#979](https://github.com/TextureGroup/Texture/pull/979) ([garrettmoon](https://github.com/garrettmoon))
- Create an experiment to remove extra collection teardown step [\#975](https://github.com/TextureGroup/Texture/pull/975) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Remove unused/unneeded header macros [\#973](https://github.com/TextureGroup/Texture/pull/973) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Standardize "extern" decls on AS\_EXTERN [\#972](https://github.com/TextureGroup/Texture/pull/972) ([Adlai-Holler](https://github.com/Adlai-Holler))
- ASConfiguration version check only when have json dict [\#971](https://github.com/TextureGroup/Texture/pull/971) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Pointer check [\#970](https://github.com/TextureGroup/Texture/pull/970) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Reduce usage of autorelease pools [\#968](https://github.com/TextureGroup/Texture/pull/968) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Update showcase to include Apollo for Reddit [\#967](https://github.com/TextureGroup/Texture/pull/967) ([christianselig](https://github.com/christianselig))
- Fix crash when call needsMainThreadDeallocation on NSProxy instances \#trivial [\#965](https://github.com/TextureGroup/Texture/pull/965) ([nguyenhuy](https://github.com/nguyenhuy))
- Fix name typo \#trivial [\#963](https://github.com/TextureGroup/Texture/pull/963) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Generalize the main thread ivar deallocation system [\#959](https://github.com/TextureGroup/Texture/pull/959) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Add support for acquiring multiple locks at once [\#958](https://github.com/TextureGroup/Texture/pull/958) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Clean up async transaction system a bit [\#955](https://github.com/TextureGroup/Texture/pull/955) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Added 'Waplog' to showcase. [\#953](https://github.com/TextureGroup/Texture/pull/953) ([malikkuru](https://github.com/malikkuru))
- Make ASPerformMainThreadDeallocation visible in C [\#952](https://github.com/TextureGroup/Texture/pull/952) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Cut 2.7 release [\#949](https://github.com/TextureGroup/Texture/pull/949) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Fixed removing node from supernode after layout transition [\#937](https://github.com/TextureGroup/Texture/pull/937) ([atitovdev](https://github.com/atitovdev))
- add ASTextNode2 snapshot test [\#935](https://github.com/TextureGroup/Texture/pull/935) ([wsdwsd0829](https://github.com/wsdwsd0829))
- \[ASTextNode\] One more check variables before calling delegate method \#trivial [\#922](https://github.com/TextureGroup/Texture/pull/922) ([Flatout73](https://github.com/Flatout73))
- Assert node did load before did enter visible way 1 [\#886](https://github.com/TextureGroup/Texture/pull/886) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Renew supplementary node on relayout [\#842](https://github.com/TextureGroup/Texture/pull/842) ([wsdwsd0829](https://github.com/wsdwsd0829))
## [2.7](https://github.com/TextureGroup/Texture/tree/2.7) (2018-05-29)
[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.6...2.7)
**Merged pull requests:**
- Update AppIcon in showcase [\#946](https://github.com/TextureGroup/Texture/pull/946) ([muukii](https://github.com/muukii))
- Update tip-1-nodeBlocks.md [\#943](https://github.com/TextureGroup/Texture/pull/943) ([sagarbhosale](https://github.com/sagarbhosale))
- \[ASTableView\] Generate a new cell layout if existing ones are invalid [\#942](https://github.com/TextureGroup/Texture/pull/942) ([nguyenhuy](https://github.com/nguyenhuy))
- Update to unsplash [\#938](https://github.com/TextureGroup/Texture/pull/938) ([garrettmoon](https://github.com/garrettmoon))
- \[ASTextNode2\] Simplify compare-assign check & lock \_pointScaleFactors accessor \#trivial [\#934](https://github.com/TextureGroup/Texture/pull/934) ([appleguy](https://github.com/appleguy))
- Create a new dealloc queue that is more efficient [\#931](https://github.com/TextureGroup/Texture/pull/931) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASImageNode+AnimatedImage\] Fix early return when animatedImage is nil in setAnimatedImage \#trivial [\#925](https://github.com/TextureGroup/Texture/pull/925) ([flovouin](https://github.com/flovouin))
- Remove assert. fix \#878 \#914 [\#924](https://github.com/TextureGroup/Texture/pull/924) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Always call out to delegate for experiments, whether enabled or not [\#923](https://github.com/TextureGroup/Texture/pull/923) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASTextNode2\] Upgrade lock safety by protecting all ivars \(including rarely-changed ones\). [\#918](https://github.com/TextureGroup/Texture/pull/918) ([appleguy](https://github.com/appleguy))
- \[ASCollectionNode/ASTableNode\] Fix a crash occurs while remeasuring cell nodes [\#917](https://github.com/TextureGroup/Texture/pull/917) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASDisplayNode\] Improve thread-safety of didExitHierarchy \#trivial [\#916](https://github.com/TextureGroup/Texture/pull/916) ([nguyenhuy](https://github.com/nguyenhuy))
- Prevent UITextView from updating contentOffset while deallocating [\#915](https://github.com/TextureGroup/Texture/pull/915) ([maicki](https://github.com/maicki))
- Fix ASDKgram-Swift to avoid 'error parsing JSON within PhotoModel Init' [\#913](https://github.com/TextureGroup/Texture/pull/913) ([kenstir](https://github.com/kenstir))
- \#trivial Add forgotten experiment into Schemas/configuration.json [\#912](https://github.com/TextureGroup/Texture/pull/912) ([garrettmoon](https://github.com/garrettmoon))
- \#trivial Fix the C++ assertion [\#911](https://github.com/TextureGroup/Texture/pull/911) ([garrettmoon](https://github.com/garrettmoon))
- Add 'iDiva - Beauty & Wedding tips' to Showcase [\#909](https://github.com/TextureGroup/Texture/pull/909) ([sudhanshutil](https://github.com/sudhanshutil))
- Issue ASNetworkImageNode callbacks off main thread [\#908](https://github.com/TextureGroup/Texture/pull/908) ([garrettmoon](https://github.com/garrettmoon))
- \[ASTextNode\] Fix a deadlock that could occur when enabling experimental ASTextNode2 via ASConfiguration [\#903](https://github.com/TextureGroup/Texture/pull/903) ([appleguy](https://github.com/appleguy))
- \[Docs\] Add new lightning talk from Buffer \#trivial [\#902](https://github.com/TextureGroup/Texture/pull/902) ([ay8s](https://github.com/ay8s))
- Request std=c++11 dialect again, and add warning [\#900](https://github.com/TextureGroup/Texture/pull/900) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASTextNode\] Check variables before calling delegate method \#trivial [\#898](https://github.com/TextureGroup/Texture/pull/898) ([Jauzee](https://github.com/Jauzee))
- ASDKFastImageNamed UIImage initializer nullability \#trivial [\#897](https://github.com/TextureGroup/Texture/pull/897) ([alexhillc](https://github.com/alexhillc))
- \#trivial Fixes an issue where playback may not start [\#896](https://github.com/TextureGroup/Texture/pull/896) ([garrettmoon](https://github.com/garrettmoon))
- Update configuration schema \#trivial [\#893](https://github.com/TextureGroup/Texture/pull/893) ([Adlai-Holler](https://github.com/Adlai-Holler))
- replace ` with code in containers-overview.md [\#884](https://github.com/TextureGroup/Texture/pull/884) ([everettjf](https://github.com/everettjf))
- \[Docs\] Fix typos in layout specs section \#trivial [\#883](https://github.com/TextureGroup/Texture/pull/883) ([morozkin](https://github.com/morozkin))
- Match interfacestate update sequence to uikit [\#882](https://github.com/TextureGroup/Texture/pull/882) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Add experiment to skip creating UIViews altogether for constants [\#881](https://github.com/TextureGroup/Texture/pull/881) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Fix ASDISPLAYNODE\_ASSERTIONS\_ENABLED and ASDefaultPlaybackButton warnings \#trivial [\#880](https://github.com/TextureGroup/Texture/pull/880) ([maicki](https://github.com/maicki))
- Fix macro definition for AS\_KDEBUG\_ENABLE producing warning \#trivial [\#879](https://github.com/TextureGroup/Texture/pull/879) ([andrewrohn](https://github.com/andrewrohn))
- Fix pager node for interface coalescing [\#877](https://github.com/TextureGroup/Texture/pull/877) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Standardize Property Declaration Style in Core Classes [\#870](https://github.com/TextureGroup/Texture/pull/870) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[NoCopyRendering\] In non-VM case, use calloc to get a zerod buffer \#trivial [\#869](https://github.com/TextureGroup/Texture/pull/869) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Check in Xcode 9.3 "workspace checks" file [\#868](https://github.com/TextureGroup/Texture/pull/868) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Remove Redundant Atomic Store from Recursive Unfair Lock in Recursive Case \#trivial [\#867](https://github.com/TextureGroup/Texture/pull/867) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Update Podspec [\#866](https://github.com/TextureGroup/Texture/pull/866) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[Issue 838\] Update ASCeilPixelValue and ASRoundPixelValue [\#864](https://github.com/TextureGroup/Texture/pull/864) ([rcancro](https://github.com/rcancro))
- Disable interface coalescing [\#862](https://github.com/TextureGroup/Texture/pull/862) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Introduce ASRecursiveUnfairLock and tests [\#858](https://github.com/TextureGroup/Texture/pull/858) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Make NSIndexSet+ASHelpers.h reference local \#trivial [\#857](https://github.com/TextureGroup/Texture/pull/857) ([dmaclach](https://github.com/dmaclach))
- Make ASBatchContext lock-free \#trivial [\#854](https://github.com/TextureGroup/Texture/pull/854) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASNetworkImageNode\] Replace NSUUID sentinel with integer \#trivial [\#852](https://github.com/TextureGroup/Texture/pull/852) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Make objects conform to NSLocking [\#851](https://github.com/TextureGroup/Texture/pull/851) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Make cache support animated image [\#850](https://github.com/TextureGroup/Texture/pull/850) ([wsdwsd0829](https://github.com/wsdwsd0829))
- \[bugfix\] Align timing of interface coalescing and range update. \#trivial [\#847](https://github.com/TextureGroup/Texture/pull/847) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Update layout2-layout-element-properties.md [\#844](https://github.com/TextureGroup/Texture/pull/844) ([arielelkin](https://github.com/arielelkin))
- Use NS\_RETURNS\_RETAINED macro to save time [\#843](https://github.com/TextureGroup/Texture/pull/843) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Handle nil backgroundColor in ASTextNode2 \#trivial [\#841](https://github.com/TextureGroup/Texture/pull/841) ([maicki](https://github.com/maicki))
- Put back VM flag in ASCGImageBuffer [\#839](https://github.com/TextureGroup/Texture/pull/839) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Order items in XCode project navigator by name [\#835](https://github.com/TextureGroup/Texture/pull/835) ([OleksiyA](https://github.com/OleksiyA))
- \[NoCopyRendering\] Use vm instead of malloc [\#833](https://github.com/TextureGroup/Texture/pull/833) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASTextNode2\] Fix background color drawing [\#831](https://github.com/TextureGroup/Texture/pull/831) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Fix Text Node Thread Sanitizer Warning [\#830](https://github.com/TextureGroup/Texture/pull/830) ([Adlai-Holler](https://github.com/Adlai-Holler))
- access view first before checking canBecome/Resign responder in becomeResponder methods [\#829](https://github.com/TextureGroup/Texture/pull/829) ([wsdwsd0829](https://github.com/wsdwsd0829))
- \[\#trivial\] fixes rendered image quality on networked image nodes whic… [\#826](https://github.com/TextureGroup/Texture/pull/826) ([garrettmoon](https://github.com/garrettmoon))
- \[ASTextNode\] Fix locking, add test for issue \#trivial [\#825](https://github.com/TextureGroup/Texture/pull/825) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[\#trivial\] I don't think we need this extra locked method. [\#824](https://github.com/TextureGroup/Texture/pull/824) ([garrettmoon](https://github.com/garrettmoon))
- \#trivial Hopefully made this a bit more readable. [\#823](https://github.com/TextureGroup/Texture/pull/823) ([garrettmoon](https://github.com/garrettmoon))
- \[ASTextNode\] Avoid acquiring instance lock multiple times \#trivial [\#820](https://github.com/TextureGroup/Texture/pull/820) ([nguyenhuy](https://github.com/nguyenhuy))
- \[Showcase\] Fix mensXP showcase and attach Vingle-Tech-Talk Medium [\#818](https://github.com/TextureGroup/Texture/pull/818) ([GeekTree0101](https://github.com/GeekTree0101))
- \[ASDisplayNode\] Add unit tests for layout z-order changes \(with an open issue to fix\). [\#816](https://github.com/TextureGroup/Texture/pull/816) ([appleguy](https://github.com/appleguy))
- \[ASDKGram Example\] image\_url has been changed from URL string to Array by 5… [\#813](https://github.com/TextureGroup/Texture/pull/813) ([kaar3k](https://github.com/kaar3k))
- Replace pthread specifics with C11 thread-local variables [\#811](https://github.com/TextureGroup/Texture/pull/811) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Upgrade dangerfile [\#810](https://github.com/TextureGroup/Texture/pull/810) ([garrettmoon](https://github.com/garrettmoon))
- \[ASDisplayNode\] Fix an issue that causes a node to sometimes return an outdated calculated size or size range [\#808](https://github.com/TextureGroup/Texture/pull/808) ([nguyenhuy](https://github.com/nguyenhuy))
- Avoid triggering main thread assertions in collection/table dealloc \#trivial [\#803](https://github.com/TextureGroup/Texture/pull/803) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Update IGListKit dependency to allow for updated versions [\#802](https://github.com/TextureGroup/Texture/pull/802) ([johntmcintosh](https://github.com/johntmcintosh))
- \[ASDisplayNode\] Consolidate main thread initialization and allow apps to invoke it manually instead of +load. [\#798](https://github.com/TextureGroup/Texture/pull/798) ([appleguy](https://github.com/appleguy))
- \[ASWrapperCellNode\] Introduce a new class allowing more control of UIKit passthrough cells. [\#797](https://github.com/TextureGroup/Texture/pull/797) ([appleguy](https://github.com/appleguy))
- Add missing scrollViewWillEndDragging passthrough delegate [\#796](https://github.com/TextureGroup/Texture/pull/796) ([xezero](https://github.com/xezero))
- Fix ASTextNode2 is accessing backgroundColor off main while sizing / layout is happening [\#794](https://github.com/TextureGroup/Texture/pull/794) ([maicki](https://github.com/maicki))
- \[ASTableNode & ASCollectionNode\] Keepalive reference for node if their view is necessarily alive \(has a superview\). [\#793](https://github.com/TextureGroup/Texture/pull/793) ([wsdwsd0829](https://github.com/wsdwsd0829))
- \[ASDisplayNode layout\] Fix an issue that sometimes causes a node's pending layout to not be applied [\#792](https://github.com/TextureGroup/Texture/pull/792) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASRangeController\] Fix stability of "minimum" rangeMode if the app has more than one layout before scrolling. [\#790](https://github.com/TextureGroup/Texture/pull/790) ([appleguy](https://github.com/appleguy))
- Fix UIResponder handling with view backing ASDisplayNode [\#789](https://github.com/TextureGroup/Texture/pull/789) ([maicki](https://github.com/maicki))
- New runloop queue to coalesce Interface state update calls. [\#788](https://github.com/TextureGroup/Texture/pull/788) ([wsdwsd0829](https://github.com/wsdwsd0829))
- \[Graphics contexts\] Retain the reference color space \#trivial [\#784](https://github.com/TextureGroup/Texture/pull/784) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Get CatDealsCollectionView example running again \#trivial [\#783](https://github.com/TextureGroup/Texture/pull/783) ([maicki](https://github.com/maicki))
- Improve nullable annotations for \_ASDisplayLayer and \_ASDisplayView \#trivial [\#780](https://github.com/TextureGroup/Texture/pull/780) ([maicki](https://github.com/maicki))
- \[ASDisplayNode\] Force a layout pass on a visible node as soon as it enters preload state [\#779](https://github.com/TextureGroup/Texture/pull/779) ([nguyenhuy](https://github.com/nguyenhuy))
- Improve ASNetworkImageNode delegate callout behavior [\#778](https://github.com/TextureGroup/Texture/pull/778) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Fix capturing self in the block while loading image in ASNetworkImageNode [\#777](https://github.com/TextureGroup/Texture/pull/777) ([morozkin](https://github.com/morozkin))
- Fix synchronous state of node if +viewClass or +layerClass is overwritten \#trivial [\#776](https://github.com/TextureGroup/Texture/pull/776) ([maicki](https://github.com/maicki))
- Add support for providing additional info to network image node delegate [\#775](https://github.com/TextureGroup/Texture/pull/775) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Expose asyncdisplaykit\_node in \_ASDisplayView same as in \_ASDisplayLayer \#trivial [\#773](https://github.com/TextureGroup/Texture/pull/773) ([maicki](https://github.com/maicki))
- Improve no-copy rendering experiment, remove +load method [\#771](https://github.com/TextureGroup/Texture/pull/771) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Fix typos in layout2-layoutspec-types.md \#trivial [\#770](https://github.com/TextureGroup/Texture/pull/770) ([morozkin](https://github.com/morozkin))
- Update PINCache [\#769](https://github.com/TextureGroup/Texture/pull/769) ([justinswart](https://github.com/justinswart))
- Fix misprint \#trivial [\#768](https://github.com/TextureGroup/Texture/pull/768) ([Flatout73](https://github.com/Flatout73))
- NoCopyRendering experiment: Fix possible memory leak if image node rendering is canceled \#trivial [\#765](https://github.com/TextureGroup/Texture/pull/765) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Node tint color [\#764](https://github.com/TextureGroup/Texture/pull/764) ([ShogunPhyched](https://github.com/ShogunPhyched))
- Revert "Faster collection operations" [\#759](https://github.com/TextureGroup/Texture/pull/759) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASPrimitiveTraitCollection\] Always treat preferredContentSize as a potential nil \#trivial [\#757](https://github.com/TextureGroup/Texture/pull/757) ([ypogribnyi](https://github.com/ypogribnyi))
- Update subclassing.md [\#753](https://github.com/TextureGroup/Texture/pull/753) ([janechoi6](https://github.com/janechoi6))
- \[ASDisplayNode\] Don't force a layout pass on a visible node that enters preload state [\#751](https://github.com/TextureGroup/Texture/pull/751) ([nguyenhuy](https://github.com/nguyenhuy))
- Fix the dangerfile [\#750](https://github.com/TextureGroup/Texture/pull/750) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASDisplayNode\] Always return the thread-safe cornerRadius property, even in slow CALayer rounding mode [\#749](https://github.com/TextureGroup/Texture/pull/749) ([nguyenhuy](https://github.com/nguyenhuy))
- Faster collection operations [\#748](https://github.com/TextureGroup/Texture/pull/748) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Create a centralized configuration API [\#747](https://github.com/TextureGroup/Texture/pull/747) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Update dangerfile for 2018 \#trivial [\#746](https://github.com/TextureGroup/Texture/pull/746) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Raise deployment target to iOS 9 [\#743](https://github.com/TextureGroup/Texture/pull/743) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Add an experimental "no-copy" renderer [\#741](https://github.com/TextureGroup/Texture/pull/741) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Fixed: completeBatchFetching is called on a background thread [\#731](https://github.com/TextureGroup/Texture/pull/731) ([aaronr93](https://github.com/aaronr93))
- \[tvOS\] Fixes errors when building against tvOS SDK [\#728](https://github.com/TextureGroup/Texture/pull/728) ([alexhillc](https://github.com/alexhillc))
- \[ASCellNode\] focusStyle mapping [\#727](https://github.com/TextureGroup/Texture/pull/727) ([alexhillc](https://github.com/alexhillc))
- \[ASDisplayNode\] Provide safeAreaInsets and layoutMargins bridge [\#685](https://github.com/TextureGroup/Texture/pull/685) ([ypogribnyi](https://github.com/ypogribnyi))
- \[ASTraitCollection\] Add missing properties to ASTraitCollection [\#625](https://github.com/TextureGroup/Texture/pull/625) ([ypogribnyi](https://github.com/ypogribnyi))
## [2.6](https://github.com/TextureGroup/Texture/tree/2.6) (2018-01-12)
[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.5.1...2.6)
**Merged pull requests:**
- Add MensXP to Showcase [\#739](https://github.com/TextureGroup/Texture/pull/739) ([sudhanshutil](https://github.com/sudhanshutil))
- Enable collection node interactive moves [\#735](https://github.com/TextureGroup/Texture/pull/735) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Add Blendle to our showcase page [\#721](https://github.com/TextureGroup/Texture/pull/721) ([nguyenhuy](https://github.com/nguyenhuy))
- \#trivial Fixes image nodes being stuck not being able to download image [\#720](https://github.com/TextureGroup/Texture/pull/720) ([garrettmoon](https://github.com/garrettmoon))
- Reimplement ASRectTable using unordered\_map to avoid obscure NSMapTable exception. [\#719](https://github.com/TextureGroup/Texture/pull/719) ([appleguy](https://github.com/appleguy))
- Add missing flags for ASCollectionDelegate [\#718](https://github.com/TextureGroup/Texture/pull/718) ([ilyailya](https://github.com/ilyailya))
- Add support for toggling logs off and back on at runtime [\#714](https://github.com/TextureGroup/Texture/pull/714) ([johntmcintosh](https://github.com/johntmcintosh))
- \[Update Showcase\] Update Showcase, add Vingle very community [\#711](https://github.com/TextureGroup/Texture/pull/711) ([GeekTree0101](https://github.com/GeekTree0101))
- \[ASCollectionElement\] Check for nil elements on ASTableView as well. [\#710](https://github.com/TextureGroup/Texture/pull/710) ([cesteban](https://github.com/cesteban))
- Ensure an ASM enabled node applies its pending layout when enters preload state [\#706](https://github.com/TextureGroup/Texture/pull/706) ([nguyenhuy](https://github.com/nguyenhuy))
- The ASDKgram example doesn't compile. [\#700](https://github.com/TextureGroup/Texture/pull/700) ([onato](https://github.com/onato))
- Revert Adds support for specifying a quality indexed array of URLs [\#699](https://github.com/TextureGroup/Texture/pull/699) ([garrettmoon](https://github.com/garrettmoon))
- Correct Synchronous Concurrency Talk Link [\#698](https://github.com/TextureGroup/Texture/pull/698) ([ay8s](https://github.com/ay8s))
- \[ASDisplayNode+Layout\] Ensure a pending layout is applied once [\#695](https://github.com/TextureGroup/Texture/pull/695) ([nguyenhuy](https://github.com/nguyenhuy))
- Add missing \</div\> tags in Layout API Sizing docs [\#691](https://github.com/TextureGroup/Texture/pull/691) ([richardhenry](https://github.com/richardhenry))
- Fix bug that breaks ASNodeController docs page [\#690](https://github.com/TextureGroup/Texture/pull/690) ([richardhenry](https://github.com/richardhenry))
- Add a recent talk by @smeis at CocoaHeadsNL [\#687](https://github.com/TextureGroup/Texture/pull/687) ([nguyenhuy](https://github.com/nguyenhuy))
- Update subtree-rasterization.md [\#679](https://github.com/TextureGroup/Texture/pull/679) ([WymzeeLabs](https://github.com/WymzeeLabs))
- Update layer-backing.md [\#678](https://github.com/TextureGroup/Texture/pull/678) ([WymzeeLabs](https://github.com/WymzeeLabs))
- \[iOS11\] Update project settings and fix errors [\#676](https://github.com/TextureGroup/Texture/pull/676) ([Eke](https://github.com/Eke))
- Fix swift sample. [\#669](https://github.com/TextureGroup/Texture/pull/669) ([rwinzhang](https://github.com/rwinzhang))
- Bugfix/fix yoga logging aligning api changes [\#668](https://github.com/TextureGroup/Texture/pull/668) ([wsdwsd0829](https://github.com/wsdwsd0829))
- Make it possible to map between sections even if they're empty [\#660](https://github.com/TextureGroup/Texture/pull/660) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASCornerLayoutSpec\] New layout spec class for declarative corner element layout. [\#657](https://github.com/TextureGroup/Texture/pull/657) ([huang-kun](https://github.com/huang-kun))
- Update layout2-layoutspec-types.md [\#655](https://github.com/TextureGroup/Texture/pull/655) ([TBXark](https://github.com/TBXark))
- \[Minor Breaking API\] Make deallocation queues more reliable [\#651](https://github.com/TextureGroup/Texture/pull/651) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Make the framework backwards compatible with Xcode 8 [\#650](https://github.com/TextureGroup/Texture/pull/650) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Disable this test for now, it's too flakey and no one has time to inv… [\#649](https://github.com/TextureGroup/Texture/pull/649) ([garrettmoon](https://github.com/garrettmoon))
- \[Documentation\] Update Inversion Docs [\#647](https://github.com/TextureGroup/Texture/pull/647) ([GeekTree0101](https://github.com/GeekTree0101))
- Have ASNetworkImageNode report whether images were cached or not [\#639](https://github.com/TextureGroup/Texture/pull/639) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Fix a layout deadlock caused by holding the lock and going up the tree. [\#638](https://github.com/TextureGroup/Texture/pull/638) ([garrettmoon](https://github.com/garrettmoon))
- \[ASScrollNode\] Fix small bugs and add unit tests [\#637](https://github.com/TextureGroup/Texture/pull/637) ([nguyenhuy](https://github.com/nguyenhuy))
- A couple performance tweaks for animated images \#trivial [\#634](https://github.com/TextureGroup/Texture/pull/634) ([garrettmoon](https://github.com/garrettmoon))
- \[Documentation\] Update "Getting Started" page [\#633](https://github.com/TextureGroup/Texture/pull/633) ([nguyenhuy](https://github.com/nguyenhuy))
- \[Tests\] Add test scrollToPageAtIndex ASPagerNode [\#629](https://github.com/TextureGroup/Texture/pull/629) ([remirobert](https://github.com/remirobert))
- \[Tests\] Introducing tests for the ASTabBarController [\#628](https://github.com/TextureGroup/Texture/pull/628) ([remirobert](https://github.com/remirobert))
- \[Tests\] Introducing tests for the ASNavigationController [\#627](https://github.com/TextureGroup/Texture/pull/627) ([remirobert](https://github.com/remirobert))
- \[ASCollectionView\] Call -invalidateFlowLayoutDelegateMetrics when rotating. \#trivial [\#616](https://github.com/TextureGroup/Texture/pull/616) ([appleguy](https://github.com/appleguy))
- Add unit tests for the layout engine [\#424](https://github.com/TextureGroup/Texture/pull/424) ([Adlai-Holler](https://github.com/Adlai-Holler))
## [2.5.1](https://github.com/TextureGroup/Texture/tree/2.5.1) (2017-10-24)
[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.5...2.5.1)
**Merged pull requests:**
- Dispatch batch update to main \#trivial [\#626](https://github.com/TextureGroup/Texture/pull/626) ([garrettmoon](https://github.com/garrettmoon))
- Check if we need to do a batch update [\#624](https://github.com/TextureGroup/Texture/pull/624) ([garrettmoon](https://github.com/garrettmoon))
- Fix naming conflict with YYText \#trivial [\#623](https://github.com/TextureGroup/Texture/pull/623) ([maicki](https://github.com/maicki))
- Fix "This block and function declaration is not a prototype" warning. [\#619](https://github.com/TextureGroup/Texture/pull/619) ([mbesnili](https://github.com/mbesnili))
- update Pinterest CDN URL in example code [\#613](https://github.com/TextureGroup/Texture/pull/613) ([derekargueta](https://github.com/derekargueta))
- \[ASTextKitComponents\] Make sure Main Thread Checker isn't triggered during background calculations \#trivial [\#612](https://github.com/TextureGroup/Texture/pull/612) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASTextKitComponents\] Temporary components can be deallocated off main \#trivial [\#610](https://github.com/TextureGroup/Texture/pull/610) ([nguyenhuy](https://github.com/nguyenhuy))
- Update layout2-layoutspec-types.md [\#608](https://github.com/TextureGroup/Texture/pull/608) ([olcayertas](https://github.com/olcayertas))
- Don't set download results if no longer in preload range. [\#606](https://github.com/TextureGroup/Texture/pull/606) ([garrettmoon](https://github.com/garrettmoon))
- Animated WebP support [\#605](https://github.com/TextureGroup/Texture/pull/605) ([garrettmoon](https://github.com/garrettmoon))
- \[ASVideoNode\] Time observer fix [\#604](https://github.com/TextureGroup/Texture/pull/604) ([flovouin](https://github.com/flovouin))
- Add assertion in dealloc that it is on main in ASTextKitComponents \#trivial [\#603](https://github.com/TextureGroup/Texture/pull/603) ([maicki](https://github.com/maicki))
- ASTextKitComponents needs to be deallocated on main [\#598](https://github.com/TextureGroup/Texture/pull/598) ([maicki](https://github.com/maicki))
- update faq toc links to match the generated html id \#trivial [\#597](https://github.com/TextureGroup/Texture/pull/597) ([romankl](https://github.com/romankl))
- \[PINCache\] Set a default .byteLimit to reduce disk usage & startup time. [\#595](https://github.com/TextureGroup/Texture/pull/595) ([appleguy](https://github.com/appleguy))
- Move clearing out of ASTextKitComponents property delegates into ASTextKitComponents dealloc \#trivial [\#591](https://github.com/TextureGroup/Texture/pull/591) ([maicki](https://github.com/maicki))
- Clear ivar after scheduling for main thread deallocation \#trivial [\#590](https://github.com/TextureGroup/Texture/pull/590) ([maicki](https://github.com/maicki))
- Use Nil for "no class" instead of nil \#trivial [\#589](https://github.com/TextureGroup/Texture/pull/589) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Update showcase.md [\#587](https://github.com/TextureGroup/Texture/pull/587) ([hannahmbanana](https://github.com/hannahmbanana))
- Rolling back CI to known version for now [\#585](https://github.com/TextureGroup/Texture/pull/585) ([garrettmoon](https://github.com/garrettmoon))
- Use node lock instead of separate one to avoid deadlocks. [\#582](https://github.com/TextureGroup/Texture/pull/582) ([garrettmoon](https://github.com/garrettmoon))
- \[\_ASPendingState\] Make sure accessibility strings are not nil before allocating attributed strings for them \#trivial [\#581](https://github.com/TextureGroup/Texture/pull/581) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASTextNode\] Implement an example comparing ASTextNode 1 & 2 behavior. [\#570](https://github.com/TextureGroup/Texture/pull/570) ([wsdwsd0829](https://github.com/wsdwsd0829))
- \[ASCollectionView\] Fix index space translation of Flow Layout Delegate methods. [\#467](https://github.com/TextureGroup/Texture/pull/467) ([appleguy](https://github.com/appleguy))
- \[ASCollectionView\] Improve performance and behavior of rotation / bounds changes. [\#431](https://github.com/TextureGroup/Texture/pull/431) ([appleguy](https://github.com/appleguy))
## [2.5](https://github.com/TextureGroup/Texture/tree/2.5) (2017-09-26)
[Full Changelog](https://github.com/TextureGroup/Texture/compare/v2.5...2.5)
**Merged pull requests:**
- Fix crashes caused by failing to unlock or destroy a static mutex while the app is being terminated [\#577](https://github.com/TextureGroup/Texture/pull/577) ([nguyenhuy](https://github.com/nguyenhuy))
- Update yoga version [\#569](https://github.com/TextureGroup/Texture/pull/569) ([wsdwsd0829](https://github.com/wsdwsd0829))
- \[ASDKgram Example\] fix crash on startup [\#566](https://github.com/TextureGroup/Texture/pull/566) ([hannahmbanana](https://github.com/hannahmbanana))
- Added attributed versions of accessibilityLabel, accessibilityHint, accessibilityValue [\#554](https://github.com/TextureGroup/Texture/pull/554) ([fruitcoder](https://github.com/fruitcoder))
- \[Yoga\] Add insertYogaNode:atIndex: method. Improve handling of relayouts. [\#469](https://github.com/TextureGroup/Texture/pull/469) ([appleguy](https://github.com/appleguy))
- \[ASCornerRounding\] Introduce .cornerRoundingType: CALayer, Precomposited, or Clip Corners. [\#465](https://github.com/TextureGroup/Texture/pull/465) ([appleguy](https://github.com/appleguy))
- \[ASElementMap\] Fix indexPath's section or item is actually negative \#trivial [\#457](https://github.com/TextureGroup/Texture/pull/457) ([Anyewuya](https://github.com/Anyewuya))
## [v2.5](https://github.com/TextureGroup/Texture/tree/v2.5) (2017-09-14)
[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.4...v2.5)
**Merged pull requests:**
- Fix -\[ASPagerNode view\] triggering pendingState + nodeLoaded assert \#trivial [\#564](https://github.com/TextureGroup/Texture/pull/564) ([samhsiung](https://github.com/samhsiung))
- \[ASCollectionLayout\] Exclude content inset on scrollable directions from viewport size [\#562](https://github.com/TextureGroup/Texture/pull/562) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASImageNode\] Always dealloc images in a background queue [\#561](https://github.com/TextureGroup/Texture/pull/561) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASCollectionNode\]\[ASTableNode\] Add content inset bridging property [\#560](https://github.com/TextureGroup/Texture/pull/560) ([nguyenhuy](https://github.com/nguyenhuy))
- Mark ASRunLoopQueue as drained if it contains only NULLs [\#558](https://github.com/TextureGroup/Texture/pull/558) ([cesteban](https://github.com/cesteban))
- Adds support for specifying a quality indexed array of URLs [\#557](https://github.com/TextureGroup/Texture/pull/557) ([garrettmoon](https://github.com/garrettmoon))
- Make ASWeakMapEntry Value Atomic [\#555](https://github.com/TextureGroup/Texture/pull/555) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASDisplayNode\] Deprecate -displayWillStart in favor of -displayWillStartAsynchronously: [\#536](https://github.com/TextureGroup/Texture/pull/536) ([nguyenhuy](https://github.com/nguyenhuy))
- SEP-491 prerequisite: add textViewShouldBeginEditing: to ASEditableTextNodeDelegate [\#535](https://github.com/TextureGroup/Texture/pull/535) ([yans](https://github.com/yans))
- \[Gallery layout\] Include the caller in properties providing methods [\#533](https://github.com/TextureGroup/Texture/pull/533) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASDisplayNode\] Notify rasterized subnodes that render pass has completed [\#532](https://github.com/TextureGroup/Texture/pull/532) ([smeis](https://github.com/smeis))
- \[Cleanup\] Remove deprecated APIs [\#529](https://github.com/TextureGroup/Texture/pull/529) ([nguyenhuy](https://github.com/nguyenhuy))
- Add a function to disable all logging at runtime [\#528](https://github.com/TextureGroup/Texture/pull/528) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[Table and collection views\] Consider content inset when calculating \(default\) element size range [\#525](https://github.com/TextureGroup/Texture/pull/525) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASCollectionNode\] Add -isProcessingUpdates and -onDidFinishProcessingUpdates: APIs. [\#522](https://github.com/TextureGroup/Texture/pull/522) ([appleguy](https://github.com/appleguy))
- ASImageNode+AnimatedImage playbackReadyCallback retain cycle [\#520](https://github.com/TextureGroup/Texture/pull/520) ([plarson](https://github.com/plarson))
- \[CI\] BuildKite to ignore all markdown files [\#517](https://github.com/TextureGroup/Texture/pull/517) ([nguyenhuy](https://github.com/nguyenhuy))
- ASCollectionLayout improvements [\#513](https://github.com/TextureGroup/Texture/pull/513) ([nguyenhuy](https://github.com/nguyenhuy))
- Update changelog and podspec for 2.4 [\#512](https://github.com/TextureGroup/Texture/pull/512) ([Adlai-Holler](https://github.com/Adlai-Holler))
- ASCollectionLayout to return a zero content size if its state is unavailable [\#509](https://github.com/TextureGroup/Texture/pull/509) ([nguyenhuy](https://github.com/nguyenhuy))
- Update corner-rounding.md [\#482](https://github.com/TextureGroup/Texture/pull/482) ([oferRounds](https://github.com/oferRounds))
- \[Accessibility\] Add .isAccessibilityContainer property, allowing automatic aggregation of children's a11y labels. [\#468](https://github.com/TextureGroup/Texture/pull/468) ([appleguy](https://github.com/appleguy))
- \[ASImageNode\] Enable .clipsToBounds by default \(fix .cornerRadius, GIFs overflow\). [\#466](https://github.com/TextureGroup/Texture/pull/466) ([appleguy](https://github.com/appleguy))
## [2.4](https://github.com/TextureGroup/Texture/tree/2.4) (2017-08-15)
[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.3.4...2.4)
**Merged pull requests:**
- Avoid re-entrant call to self.view when applying initial pending state [\#510](https://github.com/TextureGroup/Texture/pull/510) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[examples/ASCollectionView\] Register supplementary kinds \#trivial [\#508](https://github.com/TextureGroup/Texture/pull/508) ([nguyenhuy](https://github.com/nguyenhuy))
- Rename the field again to nodeModel [\#504](https://github.com/TextureGroup/Texture/pull/504) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Rename -\[ASCellNode viewModel\] to -\[ASCellNode nodeViewModel\] to avoid collisions [\#499](https://github.com/TextureGroup/Texture/pull/499) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Fixed typo `UIKIt` [\#497](https://github.com/TextureGroup/Texture/pull/497) ([nixzhu](https://github.com/nixzhu))
- Improvements in ASCollectionGalleryLayoutDelegate [\#496](https://github.com/TextureGroup/Texture/pull/496) ([nguyenhuy](https://github.com/nguyenhuy))
- \[Showcase\] Update showcase - add blog post link to ClassDojo icon \#trivial [\#493](https://github.com/TextureGroup/Texture/pull/493) ([Kaspik](https://github.com/Kaspik))
- \[ASCoreAnimationExtras\] Update documentation for resizbale images \#trivial [\#492](https://github.com/TextureGroup/Texture/pull/492) ([Kaspik](https://github.com/Kaspik))
- \[ASStackLayoutSpec\] Fix interitem spacing not being reset on new lines and add snapshot tests \#trivial [\#491](https://github.com/TextureGroup/Texture/pull/491) ([nguyenhuy](https://github.com/nguyenhuy))
- \[Layout Transition\] Avoid calling didComplete method if pending layout transition is nil [\#490](https://github.com/TextureGroup/Texture/pull/490) ([nguyenhuy](https://github.com/nguyenhuy))
- \[LayoutTransition\] Call \_locked\_constrainedSizeForLayoutPass with the lock actually held \#trivial [\#488](https://github.com/TextureGroup/Texture/pull/488) ([nguyenhuy](https://github.com/nguyenhuy))
- iOS 11 UITableView automatic height estimation fix [\#485](https://github.com/TextureGroup/Texture/pull/485) ([christianselig](https://github.com/christianselig))
- Update scroll-node.md [\#484](https://github.com/TextureGroup/Texture/pull/484) ([oferRounds](https://github.com/oferRounds))
- Update adoption-guide-2-0-beta1.md [\#483](https://github.com/TextureGroup/Texture/pull/483) ([oferRounds](https://github.com/oferRounds))
- Update subclassing.md [\#479](https://github.com/TextureGroup/Texture/pull/479) ([oferRounds](https://github.com/oferRounds))
- Invalidate layouts more aggressively when transitioning with animation [\#476](https://github.com/TextureGroup/Texture/pull/476) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Update image-modification-block.md [\#474](https://github.com/TextureGroup/Texture/pull/474) ([oferRounds](https://github.com/oferRounds))
- \[ASStackLayoutSpec\] Flex wrap fix and lineSpacing property [\#472](https://github.com/TextureGroup/Texture/pull/472) ([flovouin](https://github.com/flovouin))
- \[ASNodeController\] Add -nodeDidLayout callback. Allow switching retain behavior at runtime. [\#470](https://github.com/TextureGroup/Texture/pull/470) ([appleguy](https://github.com/appleguy))
- \[Layout transition\] Invalidate calculated layout if transitioning using the same size range [\#464](https://github.com/TextureGroup/Texture/pull/464) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASTableNode\]\[ASCollectionNode\] Add content offset bridging property [\#460](https://github.com/TextureGroup/Texture/pull/460) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASDisplayNode\] Fix infinite layout loop [\#455](https://github.com/TextureGroup/Texture/pull/455) ([nguyenhuy](https://github.com/nguyenhuy))
- Add ASPagerNode+Beta to umbrella header \#trivial [\#454](https://github.com/TextureGroup/Texture/pull/454) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASPagerNode\] Remove unused flow layout reference \#trivial [\#452](https://github.com/TextureGroup/Texture/pull/452) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASCollectionLayout\] Add ASCollectionGalleryLayoutSizeProviding [\#451](https://github.com/TextureGroup/Texture/pull/451) ([nguyenhuy](https://github.com/nguyenhuy))
- fix SIMULATE\_WEB\_RESPONSE not imported \#449 [\#450](https://github.com/TextureGroup/Texture/pull/450) ([wsdwsd0829](https://github.com/wsdwsd0829))
- \[ASDataController \] Merge willUpdateWithChangeSet and didUpdateWithChangeSet delegate methods \#trivial [\#445](https://github.com/TextureGroup/Texture/pull/445) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASDataController\] Clean up [\#443](https://github.com/TextureGroup/Texture/pull/443) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASDataController\] Avoid asking for size ranges of soon-to-be-delete elements during relayouts [\#442](https://github.com/TextureGroup/Texture/pull/442) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASCollectionView\] Add delegate bridging and index space translation for missing UICollectionViewLayout properties. [\#440](https://github.com/TextureGroup/Texture/pull/440) ([appleguy](https://github.com/appleguy))
- \[ASDisplayNode\] Fix some gaps in the bridging of new contents\* properties. [\#435](https://github.com/TextureGroup/Texture/pull/435) ([appleguy](https://github.com/appleguy))
- \[ASDisplayNode\] Allow setting stretchable contents on nodes; add bridged properties. \#trivial [\#429](https://github.com/TextureGroup/Texture/pull/429) ([appleguy](https://github.com/appleguy))
- Use a sentinel NSUInteger for node layout data \#trivial [\#428](https://github.com/TextureGroup/Texture/pull/428) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Workaround clang4.0 \<stdatomic.h\> initialization \#trivial [\#426](https://github.com/TextureGroup/Texture/pull/426) ([bkase](https://github.com/bkase))
- Add missing import in ASDisplayNode+AsyncDisplay \#trivial [\#423](https://github.com/TextureGroup/Texture/pull/423) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASCollectionElement\] Add checks for nil element, prior to other PRs landing. [\#421](https://github.com/TextureGroup/Texture/pull/421) ([appleguy](https://github.com/appleguy))
- \[ASDataController\] Apply new visible map inside batch updates block [\#420](https://github.com/TextureGroup/Texture/pull/420) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASVideoPlayerNode\] Check that the video player's delegate implements the didTapFullScreenButtonNode method before calling it \#trivial [\#418](https://github.com/TextureGroup/Texture/pull/418) ([tnev](https://github.com/tnev))
- \[ASDataController\] Fix a crash in table view caused by executing an empty change set during layoutSubviews [\#416](https://github.com/TextureGroup/Texture/pull/416) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASDisplayNode+Layout\] In layoutThatFits:, check and use \_pending layout if valid. [\#413](https://github.com/TextureGroup/Texture/pull/413) ([appleguy](https://github.com/appleguy))
- Integrate Weaver into ASDKGram [\#412](https://github.com/TextureGroup/Texture/pull/412) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASDisplayNode\] -didEnterPreloadState does not need to call -layoutIfNeeded \#trivial [\#411](https://github.com/TextureGroup/Texture/pull/411) ([appleguy](https://github.com/appleguy))
- \[ASTextNode2\] Provide compiler flag to enable ASTextNode2 for all usages. [\#410](https://github.com/TextureGroup/Texture/pull/410) ([appleguy](https://github.com/appleguy))
- \[Yoga\] Refine the handling of measurement functions when Yoga is used. [\#408](https://github.com/TextureGroup/Texture/pull/408) ([appleguy](https://github.com/appleguy))
- \[ASCollectionView\] Small improvements [\#407](https://github.com/TextureGroup/Texture/pull/407) ([nguyenhuy](https://github.com/nguyenhuy))
- \[Documentation\] Improve description of synchronous concurrency with screenshot and video link. [\#406](https://github.com/TextureGroup/Texture/pull/406) ([appleguy](https://github.com/appleguy))
- Introduce ASIntegerMap, improve our changeset handling \#trivial [\#405](https://github.com/TextureGroup/Texture/pull/405) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Fix issue where supplementary elements don't track section changes [\#404](https://github.com/TextureGroup/Texture/pull/404) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Overhaul our logging, add activity tracing support. [\#399](https://github.com/TextureGroup/Texture/pull/399) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASTextNode2\] Add initial implementation for link handling. [\#396](https://github.com/TextureGroup/Texture/pull/396) ([appleguy](https://github.com/appleguy))
- Introduce ASCollectionGalleryLayoutDelegate [\#76](https://github.com/TextureGroup/Texture/pull/76) ([nguyenhuy](https://github.com/nguyenhuy))
## [2.3.4](https://github.com/TextureGroup/Texture/tree/2.3.4) (2017-06-30)
[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.3.3...2.3.4)
**Merged pull requests:**
- Update to the latest betas of PINRemoteImage and PINCache [\#403](https://github.com/TextureGroup/Texture/pull/403) ([garrettmoon](https://github.com/garrettmoon))
- A bit of minor cleanup \#trivial [\#402](https://github.com/TextureGroup/Texture/pull/402) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASLayout\] Revisit the flattening algorithm [\#395](https://github.com/TextureGroup/Texture/pull/395) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASLayout\] If a layout has no sublayouts, don't bother initializing its rect table [\#394](https://github.com/TextureGroup/Texture/pull/394) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASLayout\] Fix documentation of retainSublayoutLayoutElements \#trivial [\#393](https://github.com/TextureGroup/Texture/pull/393) ([nguyenhuy](https://github.com/nguyenhuy))
- Fix compiling ASDimension if Yoga enabled \#trivial [\#389](https://github.com/TextureGroup/Texture/pull/389) ([maicki](https://github.com/maicki))
- comments to reflect code \#trivial [\#388](https://github.com/TextureGroup/Texture/pull/388) ([benjamin-chang](https://github.com/benjamin-chang))
- \[ASCellNode\] Remove unnecessary frame setting \#trivial [\#387](https://github.com/TextureGroup/Texture/pull/387) ([nguyenhuy](https://github.com/nguyenhuy))
- Horrible spelling mistake \#trivial [\#384](https://github.com/TextureGroup/Texture/pull/384) ([nguyenhuy](https://github.com/nguyenhuy))
- Fix for Video Table Example Building [\#383](https://github.com/TextureGroup/Texture/pull/383) ([ay8s](https://github.com/ay8s))
- ASDimensionMake to be more lenient \#trivial [\#382](https://github.com/TextureGroup/Texture/pull/382) ([nguyenhuy](https://github.com/nguyenhuy))
- Gate orphaned node detector behind YOGA flag \#trivial [\#380](https://github.com/TextureGroup/Texture/pull/380) ([nguyenhuy](https://github.com/nguyenhuy))
- \[Event Log\] Log ASM flag when modify subnodes \#trivial [\#379](https://github.com/TextureGroup/Texture/pull/379) ([nguyenhuy](https://github.com/nguyenhuy))
- Add new workspaces for tests for different integrations \#trivial [\#377](https://github.com/TextureGroup/Texture/pull/377) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Fix imageModificationBlock doc \#trivial [\#376](https://github.com/TextureGroup/Texture/pull/376) ([maicki](https://github.com/maicki))
- Fix double-load issue with ASCollectionNode [\#372](https://github.com/TextureGroup/Texture/pull/372) ([Adlai-Holler](https://github.com/Adlai-Holler))
- FIXED Typo in Layout Transition Documentation [\#371](https://github.com/TextureGroup/Texture/pull/371) ([martinjkelly](https://github.com/martinjkelly))
- \[Yoga\] Delete YOGA\_TREE\_CONTIGOUS gating and permanently enable. \#trivial [\#370](https://github.com/TextureGroup/Texture/pull/370) ([appleguy](https://github.com/appleguy))
- \[Yoga\] Minimize number of nodes that have MeasureFunc set on them. [\#369](https://github.com/TextureGroup/Texture/pull/369) ([appleguy](https://github.com/appleguy))
- Improve System Trace Implementation \#trivial [\#368](https://github.com/TextureGroup/Texture/pull/368) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Updates ASDKGram to use IGListKit 3.0.0 [\#367](https://github.com/TextureGroup/Texture/pull/367) ([ay8s](https://github.com/ay8s))
- Update link to AsyncDisplayKit 2.0 Launch Talk [\#363](https://github.com/TextureGroup/Texture/pull/363) ([appleguy](https://github.com/appleguy))
- \[ASTableView\] Use ASTableView tableNode property instead of calling ASViewToDisplayNode \#trivial [\#361](https://github.com/TextureGroup/Texture/pull/361) ([maicki](https://github.com/maicki))
- Add section-object support to new tests, improve test confinement. \#trivial [\#360](https://github.com/TextureGroup/Texture/pull/360) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[Docs\] Update 'Corner Rounding' document for Texture 2 [\#359](https://github.com/TextureGroup/Texture/pull/359) ([ArchimboldiMao](https://github.com/ArchimboldiMao))
- Add support for keeping letting cell nodes update to new view models when reloaded. \#trivial [\#357](https://github.com/TextureGroup/Texture/pull/357) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Add first-pass view model support to collection node. \#trivial [\#356](https://github.com/TextureGroup/Texture/pull/356) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASTraitCollection\] Convert ASPrimitiveTraitCollection from lock to atomic. [\#355](https://github.com/TextureGroup/Texture/pull/355) ([appleguy](https://github.com/appleguy))
- Improve collection node testing, reveal double-load issue. \#trivial [\#352](https://github.com/TextureGroup/Texture/pull/352) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Fix title in changelog [\#350](https://github.com/TextureGroup/Texture/pull/350) ([levi](https://github.com/levi))
- Add a Flag to Disable Main Thread Assertions \#trivial [\#348](https://github.com/TextureGroup/Texture/pull/348) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Migrate to Latest OCMock, Demonstrate Improved Unit Testing [\#347](https://github.com/TextureGroup/Texture/pull/347) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Upgrade ASLayoutElementContext to an Object \#trivial [\#344](https://github.com/TextureGroup/Texture/pull/344) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[Yoga\] Rewrite YOGA\_TREE\_CONTIGUOUS mode with improved behavior and cleaner integration [\#343](https://github.com/TextureGroup/Texture/pull/343) ([appleguy](https://github.com/appleguy))
- Fix internal Linter warnings \#trivial [\#340](https://github.com/TextureGroup/Texture/pull/340) ([maicki](https://github.com/maicki))
- Small changes required by the coming layout debugger [\#337](https://github.com/TextureGroup/Texture/pull/337) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASDataController\] Add event logging for transaction queue flush duration \#trivial [\#334](https://github.com/TextureGroup/Texture/pull/334) ([hannahmbanana](https://github.com/hannahmbanana))
- \[ASCollectionView\] synchronous mode [\#332](https://github.com/TextureGroup/Texture/pull/332) ([hannahmbanana](https://github.com/hannahmbanana))
- \[Performance\] Convert ASLayoutElementSize to atomic \#trivial [\#331](https://github.com/TextureGroup/Texture/pull/331) ([hannahmbanana](https://github.com/hannahmbanana))
- \[Yoga\] Refer to proper path name and use module import [\#306](https://github.com/TextureGroup/Texture/pull/306) ([weibel](https://github.com/weibel))
- \[ASImageNode\] Add documentation for image effects \#trivial [\#263](https://github.com/TextureGroup/Texture/pull/263) ([maicki](https://github.com/maicki))
## [2.3.3](https://github.com/TextureGroup/Texture/tree/2.3.3) (2017-06-06)
[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.3.2...2.3.3)
**Merged pull requests:**
- Updating to 2.3.3 \#trivial [\#338](https://github.com/TextureGroup/Texture/pull/338) ([garrettmoon](https://github.com/garrettmoon))
- \[ASDisplayNode+Layout\] Add check for orphaned nodes after layout transition to clean up. [\#336](https://github.com/TextureGroup/Texture/pull/336) ([appleguy](https://github.com/appleguy))
- Update PINRemoteImage [\#328](https://github.com/TextureGroup/Texture/pull/328) ([garrettmoon](https://github.com/garrettmoon))
- Fix typo \#trivial [\#327](https://github.com/TextureGroup/Texture/pull/327) ([vitalybaev](https://github.com/vitalybaev))
- Fixes an issue with GIFs that would always be covered by their placeh… [\#326](https://github.com/TextureGroup/Texture/pull/326) ([garrettmoon](https://github.com/garrettmoon))
- Replace NSMutableSet with NSHashTable when Appropriate \#trivial [\#321](https://github.com/TextureGroup/Texture/pull/321) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[Cleanup\] Small fixes to improve conformance for strict compiler settings \#trivial [\#320](https://github.com/TextureGroup/Texture/pull/320) ([appleguy](https://github.com/appleguy))
- Rejigger Cell Visibility Tracking [\#317](https://github.com/TextureGroup/Texture/pull/317) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Clean Up ASAsyncTransaction \#trivial [\#316](https://github.com/TextureGroup/Texture/pull/316) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Clean Up ASDisplayLayer \#trivial [\#315](https://github.com/TextureGroup/Texture/pull/315) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASDisplayNode\] Revise assertion to log until Issue \#145 is addressed. \#trivial [\#313](https://github.com/TextureGroup/Texture/pull/313) ([appleguy](https://github.com/appleguy))
- \[Docs\] Fixed typo in carthage project name \#trivial [\#310](https://github.com/TextureGroup/Texture/pull/310) ([george-gw](https://github.com/george-gw))
- Fix non layout [\#309](https://github.com/TextureGroup/Texture/pull/309) ([garrettmoon](https://github.com/garrettmoon))
- Catch Invalid Layer Bounds in a Nonfatal Assertion \#trivial [\#308](https://github.com/TextureGroup/Texture/pull/308) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASCollectionNode\] Fix missing properties and layoutInspector \#trivial [\#305](https://github.com/TextureGroup/Texture/pull/305) ([flovouin](https://github.com/flovouin))
- \[Examples\] Fixed crash on SocialAppLayout-Inverted + behaviour comments [\#304](https://github.com/TextureGroup/Texture/pull/304) ([dimazen](https://github.com/dimazen))
- IGListKit related headers need to be in the module all time now \#trivial [\#300](https://github.com/TextureGroup/Texture/pull/300) ([maicki](https://github.com/maicki))
- \[ASDisplayNode\] Remove assertion in calculateSizeThatFits: and log an event \#trivial [\#299](https://github.com/TextureGroup/Texture/pull/299) ([maicki](https://github.com/maicki))
- ASBatchFetching to not round scroll velocity \#trivial [\#294](https://github.com/TextureGroup/Texture/pull/294) ([nguyenhuy](https://github.com/nguyenhuy))
- \[ASVideoNodeDelegate\] fix for \#291 crash [\#292](https://github.com/TextureGroup/Texture/pull/292) ([SergeyPetrachkov](https://github.com/SergeyPetrachkov))
- Fix Alignment of Hashed Structs [\#287](https://github.com/TextureGroup/Texture/pull/287) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[IGListKit\] Add IGListKit headers to public section of Xcode project [\#286](https://github.com/TextureGroup/Texture/pull/286) ([maicki](https://github.com/maicki))
- Only call -layout and -layoutDidFinish if the node is already loaded [\#285](https://github.com/TextureGroup/Texture/pull/285) ([nguyenhuy](https://github.com/nguyenhuy))
- \[Batch Fetching\] Add ASBatchFetchingDelegate [\#281](https://github.com/TextureGroup/Texture/pull/281) ([nguyenhuy](https://github.com/nguyenhuy))
- Ignore Relayout Requests for Deleted Cell Nodes [\#279](https://github.com/TextureGroup/Texture/pull/279) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Remove Unused Node Code \#trivial [\#278](https://github.com/TextureGroup/Texture/pull/278) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Fix Documentation Warnings \#trivial [\#276](https://github.com/TextureGroup/Texture/pull/276) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[Examples\] Fix LayoutSpecExamples and LayoutSpecExamples-Swift: image URLs were still pointing to asyncdisplaykit.org [\#275](https://github.com/TextureGroup/Texture/pull/275) ([cesteban](https://github.com/cesteban))
- \[Layout\] Extract layout implementation code into it's own subcategories [\#272](https://github.com/TextureGroup/Texture/pull/272) ([maicki](https://github.com/maicki))
- Fix Release Builds \#trivial [\#271](https://github.com/TextureGroup/Texture/pull/271) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[Yoga\] Implement ASYogaLayoutSpec, a simplified integration strategy for Yoga. [\#270](https://github.com/TextureGroup/Texture/pull/270) ([appleguy](https://github.com/appleguy))
- Simplify Layout Transition State \#trivial [\#269](https://github.com/TextureGroup/Texture/pull/269) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Store ASLayoutElementContext in Thread-Local Storage \#trivial [\#268](https://github.com/TextureGroup/Texture/pull/268) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[Examples\] Fix a couple of examples due to API changes recently \#trivial [\#267](https://github.com/TextureGroup/Texture/pull/267) ([maicki](https://github.com/maicki))
- Fix Collection Item Index Path Conversion [\#262](https://github.com/TextureGroup/Texture/pull/262) ([Adlai-Holler](https://github.com/Adlai-Holler))
- added error reporting callback to ASVideoNode [\#260](https://github.com/TextureGroup/Texture/pull/260) ([SergeyPetrachkov](https://github.com/SergeyPetrachkov))
- Add Experimental Text Node Implementation [\#259](https://github.com/TextureGroup/Texture/pull/259) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Add missing import and define in ASLog \#trivial [\#257](https://github.com/TextureGroup/Texture/pull/257) ([nguyenhuy](https://github.com/nguyenhuy))
- Simplify Override Checking, Only Do It When Assertions Are Enabled \#trivial [\#253](https://github.com/TextureGroup/Texture/pull/253) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASTextKitFontSizeAdjuster\] Replace use of boundingRectWithSize:options:context: with boundingRectForGlyphRange: inTextContainer: [\#251](https://github.com/TextureGroup/Texture/pull/251) ([rcancro](https://github.com/rcancro))
- Improve Ancestry Handling, Avoid Assertion Failure [\#246](https://github.com/TextureGroup/Texture/pull/246) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[Yoga\] Increment Yoga version to current, 1.5.0. [\#91](https://github.com/TextureGroup/Texture/pull/91) ([appleguy](https://github.com/appleguy))
- \[example/CustomCollectionView\] Implement MosaicCollectionLayoutDelegate [\#28](https://github.com/TextureGroup/Texture/pull/28) ([nguyenhuy](https://github.com/nguyenhuy))
## [2.3.2](https://github.com/TextureGroup/Texture/tree/2.3.2) (2017-05-09)
[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.3.1...2.3.2)
**Merged pull requests:**
- \[ASDisplayNode\] Pass drawParameter in rendering context callbacks [\#248](https://github.com/TextureGroup/Texture/pull/248) ([maicki](https://github.com/maicki))
- Assert only once we know URL has changed [\#247](https://github.com/TextureGroup/Texture/pull/247) ([garrettmoon](https://github.com/garrettmoon))
- \[ASImageNode\] Move to class method of displayWithParameters:isCancelled: for drawing [\#244](https://github.com/TextureGroup/Texture/pull/244) ([maicki](https://github.com/maicki))
- Don't Use Associated Objects for Drawing Priority \#trivial [\#239](https://github.com/TextureGroup/Texture/pull/239) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASDimension\] Remove warning about float precision using CGFloat and … \#trivial [\#237](https://github.com/TextureGroup/Texture/pull/237) ([amegias](https://github.com/amegias))
- \[ASImageNode\] Move debug label and will- / didDisplayNodeContentWithRenderingContext out of drawing method \#trivial [\#235](https://github.com/TextureGroup/Texture/pull/235) ([maicki](https://github.com/maicki))
- Fixes assertion on startup in social app layout example [\#233](https://github.com/TextureGroup/Texture/pull/233) ([garrettmoon](https://github.com/garrettmoon))
- \[ASTextNode\] Move to class method of drawRect:withParameters:isCancelled:isRasterizing: for drawing [\#232](https://github.com/TextureGroup/Texture/pull/232) ([maicki](https://github.com/maicki))
- \[ASImageNode\] Remove unneeded pointer star \#trivial [\#231](https://github.com/TextureGroup/Texture/pull/231) ([maicki](https://github.com/maicki))
- \[ASTwoDimensionalArrayUtils\] Fix extern C function definition to fix compiler issue. \#trivial [\#229](https://github.com/TextureGroup/Texture/pull/229) ([appleguy](https://github.com/appleguy))
- Move Last Few Properties from ASTableView,ASCollectionView to Node [\#225](https://github.com/TextureGroup/Texture/pull/225) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Fix Issues in the Project File \#trivial [\#224](https://github.com/TextureGroup/Texture/pull/224) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Improve Our Handling of Subnodes [\#223](https://github.com/TextureGroup/Texture/pull/223) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Extract ASLayoutElement and ASLayoutElementStylability into categories \#trivial [\#131](https://github.com/TextureGroup/Texture/pull/131) ([maicki](https://github.com/maicki))
- \[Layout\] Remove finalLayoutElement [\#96](https://github.com/TextureGroup/Texture/pull/96) ([maicki](https://github.com/maicki))
- \[Docs\] Add workaround for setting a custom lineSpacing and maxNumberOfLines to ASTextNode docs [\#92](https://github.com/TextureGroup/Texture/pull/92) ([maicki](https://github.com/maicki))
- \[ASDisplayNode\] Implement a std::atomic-based flag system for superb performance [\#89](https://github.com/TextureGroup/Texture/pull/89) ([appleguy](https://github.com/appleguy))
- \[Yoga\] Ensure that calculated layout is nil'd in invalidate\*Layout [\#87](https://github.com/TextureGroup/Texture/pull/87) ([appleguy](https://github.com/appleguy))
- Simplify Hashing Code [\#86](https://github.com/TextureGroup/Texture/pull/86) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Fix site header [\#84](https://github.com/TextureGroup/Texture/pull/84) ([levi](https://github.com/levi))
- Tighten Rasterization API, Undeprecate It [\#82](https://github.com/TextureGroup/Texture/pull/82) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Implement ASPageTable [\#81](https://github.com/TextureGroup/Texture/pull/81) ([nguyenhuy](https://github.com/nguyenhuy))
- Make Cell Node Properties Atomic [\#74](https://github.com/TextureGroup/Texture/pull/74) ([Adlai-Holler](https://github.com/Adlai-Holler))
- \[ASNodeController+Beta\] Provide an option to allow nodes to own their controllers. [\#61](https://github.com/TextureGroup/Texture/pull/61) ([appleguy](https://github.com/appleguy))
- \[Yoga Beta\] Improvements to the experimental support for Yoga layout. [\#59](https://github.com/TextureGroup/Texture/pull/59) ([appleguy](https://github.com/appleguy))
- Fix issue with swipe to delete cell gesture. \#trivial [\#46](https://github.com/TextureGroup/Texture/pull/46) ([rewcraig](https://github.com/rewcraig))
- Fix CustomCollectionView-Swift sample [\#22](https://github.com/TextureGroup/Texture/pull/22) ([george-gw](https://github.com/george-gw))
- Automatically resume ASVideoNode after returning from background [\#13](https://github.com/TextureGroup/Texture/pull/13) ([plarson](https://github.com/plarson))
## [2.3.1](https://github.com/TextureGroup/Texture/tree/2.3.1) (2017-04-27)
[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.2.1...2.3.1)
**Merged pull requests:**
- Don't run tests for the docs directory. [\#79](https://github.com/TextureGroup/Texture/pull/79) ([garrettmoon](https://github.com/garrettmoon))
- Fix SCSS build [\#78](https://github.com/TextureGroup/Texture/pull/78) ([levi](https://github.com/levi))
- Fix documentation warning on ASCollectionLayoutState.h \#trivial [\#77](https://github.com/TextureGroup/Texture/pull/77) ([garrettmoon](https://github.com/garrettmoon))
- ASLayoutSpec to use more default implementations \#trivial [\#73](https://github.com/TextureGroup/Texture/pull/73) ([nguyenhuy](https://github.com/nguyenhuy))
- Update the CI to the new ruby version [\#71](https://github.com/TextureGroup/Texture/pull/71) ([garrettmoon](https://github.com/garrettmoon))
- Missing a word [\#68](https://github.com/TextureGroup/Texture/pull/68) ([djblake](https://github.com/djblake))
- Update license v2 [\#67](https://github.com/TextureGroup/Texture/pull/67) ([garrettmoon](https://github.com/garrettmoon))
- \[ASCollectionView\] Prevent prefetching from being enabled to eliminate overhead. [\#65](https://github.com/TextureGroup/Texture/pull/65) ([appleguy](https://github.com/appleguy))
- \[CGPointNull\] Rename globally exported C function to avoid collisions \#trivial [\#62](https://github.com/TextureGroup/Texture/pull/62) ([appleguy](https://github.com/appleguy))
- \[RTL\] Bridge the UISemanticContentAttribute property for more convenient RTL support. [\#60](https://github.com/TextureGroup/Texture/pull/60) ([appleguy](https://github.com/appleguy))
- Fixes a potential deadlock; it's not safe to message likely super nod… [\#56](https://github.com/TextureGroup/Texture/pull/56) ([garrettmoon](https://github.com/garrettmoon))
- Fix \_\_has\_include check in ASLog.h \#trivial [\#55](https://github.com/TextureGroup/Texture/pull/55) ([fsmorygo](https://github.com/fsmorygo))
- GLKit workaround \#trivial [\#54](https://github.com/TextureGroup/Texture/pull/54) ([stephenkopylov](https://github.com/stephenkopylov))
- Layout debugger proposal [\#52](https://github.com/TextureGroup/Texture/pull/52) ([nguyenhuy](https://github.com/nguyenhuy))
- Fixes header check to accept the 'new' header for modified files. [\#50](https://github.com/TextureGroup/Texture/pull/50) ([garrettmoon](https://github.com/garrettmoon))
- Remove References to IGListSectionType, Now that It's Gone [\#49](https://github.com/TextureGroup/Texture/pull/49) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Move doc stylesheets to sass [\#47](https://github.com/TextureGroup/Texture/pull/47) ([levi](https://github.com/levi))
- Fixes for Dangerfile header checks [\#45](https://github.com/TextureGroup/Texture/pull/45) ([garrettmoon](https://github.com/garrettmoon))
- Enforce header file changes [\#44](https://github.com/TextureGroup/Texture/pull/44) ([garrettmoon](https://github.com/garrettmoon))
- Use \_ASCollectionReusableView inside ASIGListSupplementaryViewSourceMethods [\#40](https://github.com/TextureGroup/Texture/pull/40) ([plarson](https://github.com/plarson))
- Bump Cartfile versions to match podspec [\#37](https://github.com/TextureGroup/Texture/pull/37) ([dymv](https://github.com/dymv))
- \[Site\] Remove hero drop shadow [\#35](https://github.com/TextureGroup/Texture/pull/35) ([levi](https://github.com/levi))
- Add announcement banner to documentation site [\#31](https://github.com/TextureGroup/Texture/pull/31) ([levi](https://github.com/levi))
- \[ASCollectionLayout\] Manually set size to measured cells [\#24](https://github.com/TextureGroup/Texture/pull/24) ([nguyenhuy](https://github.com/nguyenhuy))
- Create a Pluggable "Tips" System to Help in Development [\#19](https://github.com/TextureGroup/Texture/pull/19) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Add danger [\#18](https://github.com/TextureGroup/Texture/pull/18) ([garrettmoon](https://github.com/garrettmoon))
- Fix Case where Network Image Node Stays Locked [\#17](https://github.com/TextureGroup/Texture/pull/17) ([Adlai-Holler](https://github.com/Adlai-Holler))
- Update the homepage URL [\#10](https://github.com/TextureGroup/Texture/pull/10) ([garrettmoon](https://github.com/garrettmoon))
## [2.2.1](https://github.com/TextureGroup/Texture/tree/2.2.1) (2017-04-14)
[Full Changelog](https://github.com/TextureGroup/Texture/compare/2.3...2.2.1)
**Merged pull requests:**
- Add blog post [\#9](https://github.com/TextureGroup/Texture/pull/9) ([garrettmoon](https://github.com/garrettmoon))
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*

View File

@ -0,0 +1,4 @@
#!/bin/bash
set -eo pipefail
./build.sh all

View File

@ -0,0 +1,6 @@
[
"^plans/",
"^docs/",
"^CI/exclude-from-build.json$",
"^**/*.md$"
]

View File

@ -0,0 +1,261 @@
# Contribution Guidelines
Texture is the most actively developed open source project at [Pinterest](https://www.pinterest.com) and is used extensively to deliver a world-class Pinner experience. This document is setup to ensure contributing to the project is easy and transparent.
# Questions
If you are having difficulties using Texture or have a question about usage, please ask a
question in our [Slack channel](http://texturegroup.org/slack.html). **Please do not ask for help by filing Github issues.**
# Core Team
The Core Team reviews and helps iterate on the RFC Issues from the community at large and acting as the approver of these RFCs. Team members help drive Texture forward in a coherent direction consistent with the goal of creating the best possible general purpose UI framework for iOS. Team members will have merge permissions on the repository.
Members of the core team are appointed based on their technical expertise and proven contribution to the community. The current core team members are:
- Adlai Holler ([@](http://github.com/adlai-holler)[adlai-holler](http://github.com/adlai-holler))
- Garrett Moon [(](https://github.com/garrettmoon)[@](https://github.com/garrettmoon)[garrett](https://github.com/garrettmoon)[moon](https://github.com/garrettmoon))
- Huy Nguyen ([@](https://github.com/nguyenhuy)[nguyenhuy](https://github.com/nguyenhuy))
- Michael Schneider ([@maicki](https://github.com/maicki))
- Scott Goodson ([@appleguy](https://github.com/appleguy))
Over time, exceptional community members from a much more diverse background will be appointed based on their record of community involvement and contributions.
# Issues
Think you've found a bug or have a new feature to suggest? [Let us know](https://github.com/TextureGroup/Texture/issues/new)!
## Where to Find Known Issues
We use [GitHub Issues](https://github.com/texturegroup/texture/issues) for all bug tracking. We keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new issue, try to make sure your problem doesn't already exist.
## Reporting New Issues
1. Update to the most recent master release if possible. We may have already fixed your bug.
2. Search for similar [issues](https://github.com/TextureGroup/Texture/issues). It's possible somebody has encountered this bug already.
3. Provide a reduced test case that specifically shows the problem. This demo should be fully operational with the exception of the bug you want to demonstrate. The more pared down, the better. If it is not possible to produce a test case, please make sure you provide very specific steps to reproduce the error. If we cannot reproduce it, and there is no other evidence to help us figure out a fix we will close the issue.
4. Your issue will be verified. The provided example will be tested for correctness. The Texture team will work with you until your issue can be verified.
5. Keep up to date with feedback from the Texture team on your issue. Your issue may be closed if it becomes stale.
6. If possible, submit a Pull Request with a failing test. Better yet, take a stab at fixing the bug yourself if you can!
The more information you provide, the easier it is for us to validate that there is a bug and the faster we'll be able to take action.
## Issues Triaging
- You might be requested to provide a reproduction or extra information. In that case, the issue will be labeled as **N****eeds More Info**. If we did not get any response after fourteen days, we will ping you to remind you about it. We might close the issue if we do not hear from you after two weeks since the original notice.
- If you submit a feature request as a GitHub issue, you will be invited to follow the instructions in this section otherwise the issue will be closed.
- Issues that become inactive will be labelled accordingly to inform the original poster and Texture contributors that the issue should be closed since the issue is no longer actionable. The issue can be reopened at a later time if needed, e.g. becomes actionable again.
- If possible, issues will be labeled to indicate the status or priority. For example, labels may have a prefix for Status: X, or Priority: X. Statuses may include: In Progress, On Hold. Priorities may include: P1, P2 or P3 (high to low priority).
# Requesting a Feature
If you intend to change the public API, or make any non-trivial changes to the implementation, we recommend filing an RFC [issue](https://github.com/TextureGroup/Texture/issues/new) outlined below. This lets us reach an agreement on your proposal before you put significant effort into implementing it.
If you're only fixing a bug, it's fine to submit a pull request right away, but we still recommend to file an issue detailing what you're fixing. This is helpful in case we don't accept that specific fix but want to keep track of the issue.
## RFC Issue process
1. Texture has an RFC process for feature requests. To begin the discussion either gather feedback on the Texture Slack channel or draft an Texture RFC as a Github Issue.
2. The title of the GitHub RFC Issue should have `[RFC]` as prefix: `[RFC] Add new cool feature`
3. Provide a clear and detailed explanation of the feature you want and why it's important to add. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on library for Texture.
4. If the feature is complex, consider writing an Texture RFC issue. Even if you cant implement the feature yourself, consider writing an RFC issue. If we do end up accepting the feature, the issue provides the needed documentation for contributors to develop the feature according the specification accepted by the core team. We will tag accepted RFC issues with **Needs Volunteer**.
5. After discussing the feature you may choose to attempt a Pull Request. If you're at all able, start writing some code. We always have more work to do than time to do it. If you can write some code then that will speed the process along.
In short, if you have an idea that would be nice to have, create an issue on the [TextureGroup](https://github.com/TextureGroup/Texture)[/](https://github.com/TextureGroup/Texture)[Texture](https://github.com/TextureGroup/Texture) repo. If you have a question about requesting a feature, start a discussion in our [Slack channel](http://texturegroup.org/slack.html).
# Our Development Process
All work on Texture happens directly on [GitHub](https://github.com/TextureGroup/Texture). Both core team members and external contributors send pull requests which go through the same review process.
## `master` is under active development
We will do our best to keep master in good shape, with tests passing at all times. But in order to move fast, we will make API changes that your application might not be compatible with. We will do our best to communicate these changes and version appropriately so you can lock into a specific version if need be.
## Pull Requests
If you send a pull request, please do it against the master branch. We maintain stable branches for major versions separately but we don't accept pull requests to them directly. Instead, we cherry-pick non-breaking changes from master to the latest stable major version.
Before submitting a pull request, please make sure the following is done…
1. Search GitHub for an open or closed [pull request](https://github.com/TextureGroup/Texture/pulls?utf8=✓&q=is%3Apr) that relates to your submission. You don't want to duplicate effort.
2. Fork the [repo](https://github.com/TextureGroup/Texture) and create your branch from master:
git checkout -b my-fix-branch master
3. Create your patch, including appropriate test cases. Please follow our Coding Guidelines.
4. Please make sure every commit message are meaningful so it that makes it clearer for people to review and easier to understand your intention
5. Ensure tests pass CI on GitHub for your Pull Request.
6. If you haven't already, sign the CLA.
**Copyright Notice for files**
Copy and paste this to the top of your new file(s):
```objc
//
// ASDisplayNode.mm
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
```
If youve modified an existing file, change the header to:
```objc
//
// ASDisplayNode.mm
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
```
# Semantic Versioning
Texture follows semantic versioning. We release patch versions for bug fixes, minor versions for new features (and rarely, clear and easy to fix breaking changes), and major versions for any major breaking changes. When we make breaking changes, we also introduce deprecation warnings in a minor version so that our users learn about the upcoming changes and migrate their code in advance.
We tag every pull request with a label marking whether the change should go in the next patch, minor, or a major version. We release new versions pretty regularly usually every few weeks. The version will be a patch or minor version if it does not contain major new features or breaking API changes and a major version if it does.
# Coding Guidelines
- Indent using 2 spaces (this conserves space in print and makes line wrapping less likely). Never indent with tabs. Be sure to set this preference in Xcode.
- Do your best to keep it around 120 characters line length
- End files with a newline.
- Dont leave trailing whitespace.
- Space after `@property` declarations and conform to ordering of attributes
```objc
@property (nonatomic, readonly, assign, getter=isTracking) BOOL tracking;
@property (nonatomic, readwrite, strong, nullable) NSAttributedString *attributedText;
```
- In method signatures, there should be a space after the method type (-/+ symbol). There should be a space between the method segments (matching Apple's style). Always include a keyword and be descriptive with the word before the argument which describes the argument.
```objc
@interface SomeClass
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
- (void)setExampleText:(NSString *)text image:(UIImage *)image;
- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;
- (id)viewWithTag:(NSInteger)tag;
@end
```
- Internal methods should be prefixed with a `_`
```objc
- (void)_internalMethodWithParameter:(id)param;
```
- Method braces and other braces (if/else/switch/while etc.) always open on the same line as the statement but close on a new line.
```objc
if (foo == bar) {
//..
} else {
//..
}
```
- Method, `@interface` , and `@implementation` brackets on the following line
```objc
@implementation SomeClass
- (void)someMethod
{
// Implementation
}
@end
```
- Function brackets on the same line
```objc
static void someFunction() {
// Implementation
}
```
- Operator goes with the variable name
```objc
NSAttributedString *attributedText = self.textNode.attributedText;
```
- Locking
- Add a `_locked_` in front of the method name that needs to be called with a lock held
```objc
- (void)_locked_needsToBeCalledWithLock {}
```
- Locking safety:
- It is fine for a `_locked_` method to call other `_locked_` methods.
- On the other hand, the following should not be done:
- Calling normal, unlocked methods inside a `_locked_` method
- Calling subclass hooks that are meant to be overridden by developers inside a `_locked_` method.
- Subclass hooks
- that are meant to be overwritten by users should not be called with a lock held.
- that are used internally the same conventions as above apply.
- There are multiple ways to acquire a lock:
1. Explicitly call `.lock()` and `.unlock()` :
```objc
- (void)setContentSpacing:(CGFloat)contentSpacing
{
__instanceLock__.lock();
BOOL needsUpdate = (contentSpacing != _contentSpacing);
if (needsUpdate) {
_contentSpacing = contentSpacing;
}
__instanceLock__.unlock();
if (needsUpdate) {
[self setNeedsLayout];
}
}
- (CGFloat)contentSpacing
{
CGFloat contentSpacing = 0.0;
__instanceLock__.lock();
contentSpacing = _contentSpacing;
__instanceLock__.unlock();
return contentSpacing;
}
```
2. Create an `ASDN::MutexLocker` :
```objc
- (void)setContentSpacing:(CGFloat)contentSpacing
{
{
ASDN::MutexLocker l(__instanceLock__);
if (contentSpacing == _contentSpacing) {
return;
}
_contentSpacing = contentSpacing;
}
[self setNeedsLayout];
}
- (CGFloat)contentSpacing
{
ASDN::MutexLocker l(__instanceLock__);
return _contentSpacing;
}
```
- Nullability
- The adoption of annotations is straightforward. The standard we adopt is using the `NS_ASSUME_NONNULL_BEGIN` and `NS_ASSUME_NONNULL_END` on all headers. Then indicate nullability for the pointers that can be so.
- There is mostly no sense using nullability annotations outside of interface declarations.
```objc
// Properties
// Never include: `atomic`, `readwrite`, `strong`, `assign`.
// Only specify nullability if it isn't assumed from NS_ASSUME.
// (nullability, atomicity, storage class, writability, custom getter, custom setter)
@property (nullable, copy) NSNumber *status
// Methods
- (nullable NSNumber *)doSomethingWithString:(nullable NSString *)str;
// Functions
NSString * _Nullable ASStringWithQuotesIfMultiword(NSString * _Nullable string);
// Typedefs
typedef void (^RemoteCallback)(id _Nullable result, NSError * _Nullable error);
// Block as parameter
- (void)reloadDataWithCompletion:(void (^ _Nullable)())completion;
// Block as parameter with parameter and return value
- (void)convertObject:(id _Nonnull (^ _Nullable)(id _Nullable input))handler;
// More complex pointer types
- (void)allElementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSSet<ASCollectionElement *> *__autoreleasing _Nullable *)displaySet preloadSet:(NSSet<ASCollectionElement *> *__autoreleasing _Nullable *)preloadSet map:(ASElementMap *)map;
```
# Contributor License Agreement (CLA)
Please sign our Contributor License Agreement (CLA) before sending pull requests. For any code changes to be accepted, the CLA must be signed.
Complete your CLA [here](https://cla-assistant.io/TextureGroup/Texture)
# License
By contributing to Texture, you agree that your contributions will be licensed under its Apache 2 license.

View File

@ -0,0 +1,2 @@
github "pinterest/PINRemoteImage" "3.0.0-beta.14"
github "pinterest/PINCache" "3.0.1-beta.7"

View File

@ -0,0 +1,83 @@
require 'open-uri'
source_pattern = /(\.m|\.mm|\.h)$/
modified_source_files = git.modified_files.grep(source_pattern)
has_modified_source_files = !modified_source_files.empty?
added_source_files = git.added_files.grep(source_pattern)
has_added_source_files = !added_source_files.empty?
# Make it more obvious that a PR is a work in progress and shouldn't be merged yet
warn("PR is classed as Work in Progress") if github.pr_title.include? "[WIP]"
# Warn when there is a big PR
warn("This is a big PR, please consider splitting it up to ease code review.") if git.lines_of_code > 500
# Modifying the changelog will probably get overwritten.
if git.modified_files.include?("CHANGELOG.md") && !github.pr_title.include?("#changelog")
warn("PR modifies CHANGELOG.md, which is a generated file. Add #changelog to the title to suppress this warning.")
end
def full_license(partial_license, filename)
license_header = <<-HEREDOC
//
HEREDOC
license_header += "// " + filename + "\n"
license_header += <<-HEREDOC
// Texture
//
HEREDOC
license_header += partial_license
return license_header
end
def check_file_header(files_to_check, licenses)
repo_name = github.pr_json["base"]["repo"]["full_name"]
pr_number = github.pr_json["number"]
files = github.api.pull_request_files(repo_name, pr_number)
files.each do |file|
if files_to_check.include?(file["filename"])
filename = File.basename(file["filename"])
data = ""
contents = github.api.get file["contents_url"]
open(contents["download_url"]) { |io|
data += io.read
}
correct_license = false
licenses.each do |license|
license_header = full_license(license, filename)
if data.include? "Pinterest, Inc."
correct_license = true
end
end
if correct_license == false
warn ("Please ensure license is correct for #{filename}: \n```\n" + full_license(licenses[0], filename) + "```")
end
end
end
end
# Ensure new files have proper header
new_source_license_header = <<-HEREDOC
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
HEREDOC
if has_added_source_files
check_file_header(added_source_files, [new_source_license_header])
end
# Ensure modified files have proper header
modified_source_license_header = <<-HEREDOC
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
HEREDOC
if has_modified_source_files
check_file_header(modified_source_files, [modified_source_license_header, new_source_license_header])
end

View File

@ -0,0 +1,4 @@
source 'https://rubygems.org'
gem 'danger'
gem 'danger-slack'

View File

@ -0,0 +1,181 @@
The Texture project was created by Pinterest as a continuation, under a different
name and license, of the AsyncDisplayKit codebase originally developed by Facebook.
All code in Texture is covered by the Apache License, Version 2.0.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -0,0 +1,31 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
target :'AsyncDisplayKitTests' do
pod 'OCMock', '=3.4.1' # 3.4.2 currently has issues.
pod 'FBSnapshotTestCase/Core', '~> 2.1'
pod 'JGMethodSwizzler', :git => 'https://github.com/JonasGessner/JGMethodSwizzler', :branch => 'master'
# Only for buck build
pod 'PINRemoteImage', '3.0.0-beta.14'
end
#TODO CocoaPods plugin instead?
post_install do |installer|
require 'fileutils'
# Assuming we're at the root dir
buck_files_dir = 'buck-files'
if File.directory?(buck_files_dir)
installer.pod_targets.flat_map do |pod_target|
pod_name = pod_target.pod_name
# Copy the file at buck-files/BUCK_pod_name to Pods/pod_name/BUCK,
# override existing file if needed
buck_file = buck_files_dir + '/BUCK_' + pod_name
if File.file?(buck_file)
FileUtils.cp(buck_file, 'Pods/' + pod_name + '/BUCK', :preserve => false)
end
end
end
end

View File

@ -0,0 +1,55 @@
PODS:
- FBSnapshotTestCase/Core (2.1.4)
- JGMethodSwizzler (2.0.1)
- OCMock (3.4.1)
- PINCache (3.0.1-beta.7):
- PINCache/Arc-exception-safe (= 3.0.1-beta.7)
- PINCache/Core (= 3.0.1-beta.7)
- PINCache/Arc-exception-safe (3.0.1-beta.7):
- PINCache/Core
- PINCache/Core (3.0.1-beta.7):
- PINOperation (~> 1.1.1)
- PINOperation (1.1.1)
- PINRemoteImage (3.0.0-beta.14):
- PINRemoteImage/PINCache (= 3.0.0-beta.14)
- PINRemoteImage/Core (3.0.0-beta.14):
- PINOperation
- PINRemoteImage/PINCache (3.0.0-beta.14):
- PINCache (= 3.0.1-beta.7)
- PINRemoteImage/Core
DEPENDENCIES:
- FBSnapshotTestCase/Core (~> 2.1)
- JGMethodSwizzler (from `https://github.com/JonasGessner/JGMethodSwizzler`, branch `master`)
- OCMock (= 3.4.1)
- PINRemoteImage (= 3.0.0-beta.14)
SPEC REPOS:
https://github.com/cocoapods/specs.git:
- FBSnapshotTestCase
- OCMock
- PINCache
- PINOperation
- PINRemoteImage
EXTERNAL SOURCES:
JGMethodSwizzler:
:branch: master
:git: https://github.com/JonasGessner/JGMethodSwizzler
CHECKOUT OPTIONS:
JGMethodSwizzler:
:commit: 8791eccc5342224bd293b5867348657e3a240c7f
:git: https://github.com/JonasGessner/JGMethodSwizzler
SPEC CHECKSUMS:
FBSnapshotTestCase: 094f9f314decbabe373b87cc339bea235a63e07a
JGMethodSwizzler: 7328146117fffa8a4038c42eb7cd3d4c75006f97
OCMock: 2cd0716969bab32a2283ff3a46fd26a8c8b4c5e3
PINCache: 7cb9ae068c8f655717f7c644ef1dff9fd573e979
PINOperation: a6219e6fc9db9c269eb7a7b871ac193bcf400aac
PINRemoteImage: 81bbff853acc71c6de9e106e9e489a791b8bbb08
PODFILE CHECKSUM: 445046ac151568c694ff286684322273f0b597d6
COCOAPODS: 1.6.0

View File

@ -0,0 +1,51 @@
## Coming from AsyncDisplayKit? Learn more [here](https://medium.com/@Pinterest_Engineering/introducing-texture-a-new-home-for-asyncdisplaykit-e7c003308f50)
![Texture](https://github.com/texturegroup/texture/raw/master/docs/static/images/logo.png)
[![Apps Using](https://img.shields.io/cocoapods/at/Texture.svg?label=Apps%20Using%20Texture&colorB=28B9FE)](http://cocoapods.org/pods/Texture)
[![Downloads](https://img.shields.io/cocoapods/dt/Texture.svg?label=Total%20Downloads&colorB=28B9FE)](http://cocoapods.org/pods/Texture)
[![Platform](https://img.shields.io/badge/platforms-iOS%20%7C%20tvOS-orange.svg)](http://texturegroup.org)
[![Languages](https://img.shields.io/badge/languages-ObjC%20%7C%20Swift-orange.svg)](http://texturegroup.org)
[![Version](https://img.shields.io/cocoapods/v/Texture.svg)](http://cocoapods.org/pods/Texture)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-59C939.svg?style=flat)](https://github.com/Carthage/Carthage)
[![License](https://img.shields.io/cocoapods/l/Texture.svg)](https://github.com/texturegroup/texture/blob/master/LICENSE)
## Installation
Texture is available via CocoaPods or Carthage. See our [Installation](http://texturegroup.org/docs/installation.html) guide for instructions.
## Performance Gains
Texture's basic unit is the `node`. An ASDisplayNode is an abstraction over `UIView`, which in turn is an abstraction over `CALayer`. Unlike views, which can only be used on the main thread, nodes are thread-safe: you can instantiate and configure entire hierarchies of them in parallel on background threads.
To keep its user interface smooth and responsive, your app should render at 60 frames per second — the gold standard on iOS. This means the main thread has one-sixtieth of a second to push each frame. That's 16 milliseconds to execute all layout and drawing code! And because of system overhead, your code usually has less than ten milliseconds to run before it causes a frame drop.
Texture lets you move image decoding, text sizing and rendering, layout, and other expensive UI operations off the main thread, to keep the main thread available to respond to user interaction.
## Advanced Developer Features
As the framework has grown, many features have been added that can save developers tons of time by eliminating common boilerplate style structures common in modern iOS apps. If you've ever dealt with cell reuse bugs, tried to performantly preload data for a page or scroll style interface or even just tried to keep your app from dropping too many frames you can benefit from integrating Texture.
## Learn More
* Read the our [Getting Started](http://texturegroup.org/docs/getting-started.html) guide
* Get the [sample projects](https://github.com/texturegroup/texture/tree/master/examples)
* Browse the [API reference](http://texturegroup.org/appledocs.html)
## Getting Help
We use Slack for real-time debugging, community updates, and general talk about Texture. [Signup](http://asdk-slack-auto-invite.herokuapp.com) yourself or email textureframework@gmail.com to get an invite.
## Release process
For the release process see the [RELEASE] (https://github.com/texturegroup/texture/blob/master/RELEASE.md) file.
## Contributing
We welcome any contributions. See the [CONTRIBUTING](https://github.com/texturegroup/texture/blob/master/CONTRIBUTING.md) file for how to get involved.
## License
The Texture project is available for free use, as described by the [LICENSE](https://github.com/texturegroup/texture/blob/master/LICENSE) (Apache 2.0).

View File

@ -0,0 +1,15 @@
# Release Process
This document describes the process for a public Texture release.
### Preparation
- Install [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator): `sudo gem install github_changelog_generator`
- Generate a GitHub Personal Access Token to prevent running into public GitHub API rate limits: https://github.com/github-changelog-generator/github-changelog-generator#github-token
### Process
- Run `github_changelog_generator` in Texture project directory: `github_changelog_generator --token <generated personal token> TextureGroup/Texture`. To avoid hitting rate limit, the generator will replace the entire file with just the changes from this version revert that giant deletion to get the entire new changelog.
- Update `spec.version` within `Texture.podspec` and the `since-tag` and `future-release` fields in `.github_changelog_generator`.
- Create a new PR with the updated `Texture.podspec` and the newly generated changelog, add `#changelog` to the PR message so the CI will not prevent merging it.
- After merging in the PR, [create a new GitHub release](https://github.com/TextureGroup/Texture/releases/new). Use the generated changelog for the new release.
### Problems
- Sometimes we will still run into GitHub rate limit issues although using a personal token to generate the changelog. For now there is no solution for this. The issue to track is: [Triggering Github Rate limits #656](https://github.com/github-changelog-generator/github-changelog-generator/issues/656)

View File

@ -0,0 +1,33 @@
{
"id": "configuration.json",
"title": "configuration",
"description" : "Schema definition of a Texture Configuration",
"$schema": "http://json-schema.org/schema#",
"type": "object",
"properties": {
"version" : {
"type" : "number"
},
"experimental_features": {
"type": "array",
"items": {
"type": "string",
"enum": [
"exp_graphics_contexts",
"exp_text_node",
"exp_interface_state_coalesce",
"exp_unfair_lock",
"exp_infer_layer_defaults",
"exp_collection_teardown",
"exp_framesetter_cache",
"exp_skip_clear_data",
"exp_did_enter_preload_skip_asm_layout",
"exp_disable_a11y_cache",
"exp_dispatch_apply",
"exp_image_downloader_priority",
"exp_text_drawing"
]
}
}
}
}

View File

@ -0,0 +1,20 @@
//
// ASBlockTypes.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <Foundation/Foundation.h>
@class ASCellNode;
/**
* ASCellNode creation block. Used to lazily create the ASCellNode instance for a specified indexPath.
*/
typedef ASCellNode * _Nonnull(^ASCellNodeBlock)(void);
// Type for the cancellation checker block passed into the async display blocks. YES means the operation has been cancelled, NO means continue.
typedef BOOL(^asdisplaynode_iscancelled_block_t)(void);

View File

@ -0,0 +1,44 @@
//
// ASButtonNode+Private.h
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASButtonNode.h>
#import <AsyncDisplayKit/ASTextNode.h>
#import <AsyncDisplayKit/ASImageNode.h>
#import <AsyncDisplayKit/ASStackLayoutDefines.h>
@interface ASButtonNode() {
NSAttributedString *_normalAttributedTitle;
NSAttributedString *_highlightedAttributedTitle;
NSAttributedString *_selectedAttributedTitle;
NSAttributedString *_selectedHighlightedAttributedTitle;
NSAttributedString *_disabledAttributedTitle;
UIImage *_normalImage;
UIImage *_highlightedImage;
UIImage *_selectedImage;
UIImage *_selectedHighlightedImage;
UIImage *_disabledImage;
UIImage *_normalBackgroundImage;
UIImage *_highlightedBackgroundImage;
UIImage *_selectedBackgroundImage;
UIImage *_selectedHighlightedBackgroundImage;
UIImage *_disabledBackgroundImage;
CGFloat _contentSpacing;
BOOL _laysOutHorizontally;
ASVerticalAlignment _contentVerticalAlignment;
ASHorizontalAlignment _contentHorizontalAlignment;
UIEdgeInsets _contentEdgeInsets;
ASButtonNodeImageAlignment _imageAlignment;
ASTextNode *_titleNode;
ASImageNode *_imageNode;
ASImageNode *_backgroundImageNode;
}
@end

View File

@ -0,0 +1,20 @@
//
// ASButtonNode+Yoga.h
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASButtonNode.h>
NS_ASSUME_NONNULL_BEGIN
@interface ASButtonNode (Yoga)
- (void)updateYogaLayoutIfNeeded;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,106 @@
//
// ASButtonNode+Yoga.mm
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASAvailability.h>
#import "ASButtonNode+Yoga.h"
#import <AsyncDisplayKit/ASButtonNode+Private.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <AsyncDisplayKit/ASStackLayoutSpecUtilities.h>
#import <AsyncDisplayKit/ASThread.h>
#if YOGA
static void ASButtonNodeResolveHorizontalAlignmentForStyle(ASLayoutElementStyle *style, ASStackLayoutDirection _direction, ASHorizontalAlignment _horizontalAlignment, ASStackLayoutJustifyContent _justifyContent, ASStackLayoutAlignItems _alignItems) {
if (_direction == ASStackLayoutDirectionHorizontal) {
style.justifyContent = justifyContent(_horizontalAlignment, _justifyContent);
} else {
style.alignItems = alignment(_horizontalAlignment, _alignItems);
}
}
static void ASButtonNodeResolveVerticalAlignmentForStyle(ASLayoutElementStyle *style, ASStackLayoutDirection _direction, ASVerticalAlignment _verticalAlignment, ASStackLayoutJustifyContent _justifyContent, ASStackLayoutAlignItems _alignItems) {
if (_direction == ASStackLayoutDirectionHorizontal) {
style.alignItems = alignment(_verticalAlignment, _alignItems);
} else {
style.justifyContent = justifyContent(_verticalAlignment, _justifyContent);
}
}
@implementation ASButtonNode (Yoga)
- (void)updateYogaLayoutIfNeeded
{
NSMutableArray<ASDisplayNode *> *children = [[NSMutableArray alloc] initWithCapacity:2];
{
ASLockScopeSelf();
// Build up yoga children for button node again
unowned ASLayoutElementStyle *style = [self _locked_style];
[style yogaNodeCreateIfNeeded];
// Setup stack layout values
style.flexDirection = _laysOutHorizontally ? ASStackLayoutDirectionHorizontal : ASStackLayoutDirectionVertical;
// Resolve horizontal and vertical alignment
ASButtonNodeResolveHorizontalAlignmentForStyle(style, style.flexDirection, _contentHorizontalAlignment, style.justifyContent, style.alignItems);
ASButtonNodeResolveVerticalAlignmentForStyle(style, style.flexDirection, _contentVerticalAlignment, style.justifyContent, style.alignItems);
// Setup new yoga children
if (_imageNode.image != nil) {
[_imageNode.style yogaNodeCreateIfNeeded];
[children addObject:_imageNode];
}
if (_titleNode.attributedText.length > 0) {
[_titleNode.style yogaNodeCreateIfNeeded];
if (_imageAlignment == ASButtonNodeImageAlignmentBeginning) {
[children addObject:_titleNode];
} else {
[children insertObject:_titleNode atIndex:0];
}
}
// Add spacing between title and button
if (children.count == 2) {
unowned ASLayoutElementStyle *firstChildStyle = children.firstObject.style;
if (_laysOutHorizontally) {
firstChildStyle.margin = ASEdgeInsetsMake(UIEdgeInsetsMake(0, 0, 0, _contentSpacing));
} else {
firstChildStyle.margin = ASEdgeInsetsMake(UIEdgeInsetsMake(0, 0, _contentSpacing, 0));
}
}
// Add padding to button
if (UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, _contentEdgeInsets) == NO) {
style.padding = ASEdgeInsetsMake(_contentEdgeInsets);
}
// Add background node
if (_backgroundImageNode.image) {
[_backgroundImageNode.style yogaNodeCreateIfNeeded];
[children insertObject:_backgroundImageNode atIndex:0];
_backgroundImageNode.style.positionType = YGPositionTypeAbsolute;
_backgroundImageNode.style.position = ASEdgeInsetsMake(UIEdgeInsetsZero);
}
}
// Update new children
[self setYogaChildren:children];
}
@end
#else
@implementation ASButtonNode (Yoga)
- (void)updateYogaLayoutIfNeeded {}
@end
#endif

View File

@ -0,0 +1,130 @@
//
// ASButtonNode.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASControlNode.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class ASImageNode, ASTextNode;
/**
Image alignment defines where the image will be placed relative to the text.
*/
typedef NS_ENUM(NSInteger, ASButtonNodeImageAlignment) {
/** Places the image before the text. */
ASButtonNodeImageAlignmentBeginning,
/** Places the image after the text. */
ASButtonNodeImageAlignmentEnd
};
@interface ASButtonNode : ASControlNode
@property (readonly) ASTextNode * titleNode;
@property (readonly) ASImageNode * imageNode;
@property (readonly) ASImageNode * backgroundImageNode;
/**
Spacing between image and title. Defaults to 8.0.
*/
@property CGFloat contentSpacing;
/**
Whether button should be laid out vertically (image on top of text) or horizontally (image to the left of text).
ASButton node does not yet support RTL but it should be fairly easy to implement.
Defaults to YES.
*/
@property BOOL laysOutHorizontally;
/** Horizontally align content (text or image).
Defaults to ASHorizontalAlignmentMiddle.
*/
@property ASHorizontalAlignment contentHorizontalAlignment;
/** Vertically align content (text or image).
Defaults to ASVerticalAlignmentCenter.
*/
@property ASVerticalAlignment contentVerticalAlignment;
/**
* @discussion The insets used around the title and image node
*/
@property UIEdgeInsets contentEdgeInsets;
/**
* @discusstion Whether the image should be aligned at the beginning or at the end of node. Default is `ASButtonNodeImageAlignmentBeginning`.
*/
@property ASButtonNodeImageAlignment imageAlignment;
/**
* Returns the styled title associated with the specified state.
*
* @param state The control state that uses the styled title.
*
* @return The title for the specified state.
*/
- (nullable NSAttributedString *)attributedTitleForState:(UIControlState)state AS_WARN_UNUSED_RESULT;
/**
* Sets the styled title to use for the specified state. This will reset styled title previously set with -setTitle:withFont:withColor:forState.
*
* @param title The styled text string to use for the title.
* @param state The control state that uses the specified title.
*/
- (void)setAttributedTitle:(nullable NSAttributedString *)title forState:(UIControlState)state;
#if TARGET_OS_IOS
/**
* Sets the title to use for the specified state. This will reset styled title previously set with -setAttributedTitle:forState.
*
* @param title The styled text string to use for the title.
* @param font The font to use for the title.
* @param color The color to use for the title.
* @param state The control state that uses the specified title.
*/
- (void)setTitle:(NSString *)title withFont:(nullable UIFont *)font withColor:(nullable UIColor *)color forState:(UIControlState)state;
#endif
/**
* Returns the image used for a button state.
*
* @param state The control state that uses the image.
*
* @return The image used for the specified state.
*/
- (nullable UIImage *)imageForState:(UIControlState)state AS_WARN_UNUSED_RESULT;
/**
* Sets the image to use for the specified state.
*
* @param image The image to use for the specified state.
* @param state The control state that uses the specified title.
*/
- (void)setImage:(nullable UIImage *)image forState:(UIControlState)state;
/**
* Sets the background image to use for the specified state.
*
* @param image The image to use for the specified state.
* @param state The control state that uses the specified title.
*/
- (void)setBackgroundImage:(nullable UIImage *)image forState:(UIControlState)state;
/**
* Returns the background image used for a button state.
*
* @param state The control state that uses the image.
*
* @return The background image used for the specified state.
*/
- (nullable UIImage *)backgroundImageForState:(UIControlState)state AS_WARN_UNUSED_RESULT;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,557 @@
//
// ASButtonNode.mm
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASButtonNode+Private.h>
#import <AsyncDisplayKit/ASButtonNode+Yoga.h>
#import <AsyncDisplayKit/ASStackLayoutSpec.h>
#import <AsyncDisplayKit/ASThread.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASBackgroundLayoutSpec.h>
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
#import <AsyncDisplayKit/ASAbsoluteLayoutSpec.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
@implementation ASButtonNode
@synthesize contentSpacing = _contentSpacing;
@synthesize laysOutHorizontally = _laysOutHorizontally;
@synthesize contentVerticalAlignment = _contentVerticalAlignment;
@synthesize contentHorizontalAlignment = _contentHorizontalAlignment;
@synthesize contentEdgeInsets = _contentEdgeInsets;
@synthesize imageAlignment = _imageAlignment;
@synthesize titleNode = _titleNode;
@synthesize imageNode = _imageNode;
@synthesize backgroundImageNode = _backgroundImageNode;
#pragma mark - Lifecycle
- (instancetype)init
{
if (self = [super init]) {
self.automaticallyManagesSubnodes = YES;
_contentSpacing = 8.0;
_laysOutHorizontally = YES;
_contentHorizontalAlignment = ASHorizontalAlignmentMiddle;
_contentVerticalAlignment = ASVerticalAlignmentCenter;
_contentEdgeInsets = UIEdgeInsetsZero;
_imageAlignment = ASButtonNodeImageAlignmentBeginning;
self.accessibilityTraits = self.defaultAccessibilityTraits;
[self updateYogaLayoutIfNeeded];
}
return self;
}
- (ASTextNode *)titleNode
{
ASLockScopeSelf();
if (!_titleNode) {
_titleNode = [[ASTextNode alloc] init];
#if TARGET_OS_IOS
// tvOS needs access to the underlying view
// of the button node to add a touch handler.
[_titleNode setLayerBacked:YES];
#endif
_titleNode.style.flexShrink = 1.0;
}
return _titleNode;
}
#pragma mark - Public Getter
- (ASImageNode *)imageNode
{
ASLockScopeSelf();
if (!_imageNode) {
_imageNode = [[ASImageNode alloc] init];
[_imageNode setLayerBacked:YES];
}
return _imageNode;
}
- (ASImageNode *)backgroundImageNode
{
ASLockScopeSelf();
if (!_backgroundImageNode) {
_backgroundImageNode = [[ASImageNode alloc] init];
[_backgroundImageNode setLayerBacked:YES];
[_backgroundImageNode setContentMode:UIViewContentModeScaleToFill];
}
return _backgroundImageNode;
}
- (void)setLayerBacked:(BOOL)layerBacked
{
ASDisplayNodeAssert(!layerBacked, @"ASButtonNode must not be layer backed!");
[super setLayerBacked:layerBacked];
}
- (void)setEnabled:(BOOL)enabled
{
if (self.enabled != enabled) {
[super setEnabled:enabled];
self.accessibilityTraits = self.defaultAccessibilityTraits;
[self updateButtonContent];
}
}
- (void)setHighlighted:(BOOL)highlighted
{
if (self.highlighted != highlighted) {
[super setHighlighted:highlighted];
[self updateButtonContent];
}
}
- (void)setSelected:(BOOL)selected
{
if (self.selected != selected) {
[super setSelected:selected];
[self updateButtonContent];
}
}
- (void)updateButtonContent
{
[self updateBackgroundImage];
[self updateImage];
[self updateTitle];
}
- (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously
{
[super setDisplaysAsynchronously:displaysAsynchronously];
[self.backgroundImageNode setDisplaysAsynchronously:displaysAsynchronously];
[self.imageNode setDisplaysAsynchronously:displaysAsynchronously];
[self.titleNode setDisplaysAsynchronously:displaysAsynchronously];
}
- (void)updateImage
{
[self lock];
UIImage *newImage;
if (self.enabled == NO && _disabledImage) {
newImage = _disabledImage;
} else if (self.highlighted && self.selected && _selectedHighlightedImage) {
newImage = _selectedHighlightedImage;
} else if (self.highlighted && _highlightedImage) {
newImage = _highlightedImage;
} else if (self.selected && _selectedImage) {
newImage = _selectedImage;
} else {
newImage = _normalImage;
}
if ((_imageNode != nil || newImage != nil) && newImage != self.imageNode.image) {
_imageNode.image = newImage;
[self unlock];
[self updateYogaLayoutIfNeeded];
[self setNeedsLayout];
return;
}
[self unlock];
}
- (void)updateTitle
{
[self lock];
NSAttributedString *newTitle;
if (self.enabled == NO && _disabledAttributedTitle) {
newTitle = _disabledAttributedTitle;
} else if (self.highlighted && self.selected && _selectedHighlightedAttributedTitle) {
newTitle = _selectedHighlightedAttributedTitle;
} else if (self.highlighted && _highlightedAttributedTitle) {
newTitle = _highlightedAttributedTitle;
} else if (self.selected && _selectedAttributedTitle) {
newTitle = _selectedAttributedTitle;
} else {
newTitle = _normalAttributedTitle;
}
// Calling self.titleNode is essential here because _titleNode is lazily created by the getter.
if ((_titleNode != nil || newTitle.length > 0) && [self.titleNode.attributedText isEqualToAttributedString:newTitle] == NO) {
_titleNode.attributedText = newTitle;
[self unlock];
self.accessibilityLabel = self.defaultAccessibilityLabel;
[self updateYogaLayoutIfNeeded];
[self setNeedsLayout];
return;
}
[self unlock];
}
- (void)updateBackgroundImage
{
[self lock];
UIImage *newImage;
if (self.enabled == NO && _disabledBackgroundImage) {
newImage = _disabledBackgroundImage;
} else if (self.highlighted && self.selected && _selectedHighlightedBackgroundImage) {
newImage = _selectedHighlightedBackgroundImage;
} else if (self.highlighted && _highlightedBackgroundImage) {
newImage = _highlightedBackgroundImage;
} else if (self.selected && _selectedBackgroundImage) {
newImage = _selectedBackgroundImage;
} else {
newImage = _normalBackgroundImage;
}
if ((_backgroundImageNode != nil || newImage != nil) && newImage != self.backgroundImageNode.image) {
_backgroundImageNode.image = newImage;
[self unlock];
[self updateYogaLayoutIfNeeded];
[self setNeedsLayout];
return;
}
[self unlock];
}
- (CGFloat)contentSpacing
{
ASLockScopeSelf();
return _contentSpacing;
}
- (void)setContentSpacing:(CGFloat)contentSpacing
{
if (ASLockedSelfCompareAssign(_contentSpacing, contentSpacing)) {
[self updateYogaLayoutIfNeeded];
[self setNeedsLayout];
}
}
- (BOOL)laysOutHorizontally
{
ASLockScopeSelf();
return _laysOutHorizontally;
}
- (void)setLaysOutHorizontally:(BOOL)laysOutHorizontally
{
if (ASLockedSelfCompareAssign(_laysOutHorizontally, laysOutHorizontally)) {
[self updateYogaLayoutIfNeeded];
[self setNeedsLayout];
}
}
- (ASVerticalAlignment)contentVerticalAlignment
{
ASLockScopeSelf();
return _contentVerticalAlignment;
}
- (void)setContentVerticalAlignment:(ASVerticalAlignment)contentVerticalAlignment
{
ASLockScopeSelf();
_contentVerticalAlignment = contentVerticalAlignment;
}
- (ASHorizontalAlignment)contentHorizontalAlignment
{
ASLockScopeSelf();
return _contentHorizontalAlignment;
}
- (void)setContentHorizontalAlignment:(ASHorizontalAlignment)contentHorizontalAlignment
{
ASLockScopeSelf();
_contentHorizontalAlignment = contentHorizontalAlignment;
}
- (UIEdgeInsets)contentEdgeInsets
{
ASLockScopeSelf();
return _contentEdgeInsets;
}
- (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets
{
ASLockScopeSelf();
_contentEdgeInsets = contentEdgeInsets;
}
- (ASButtonNodeImageAlignment)imageAlignment
{
ASLockScopeSelf();
return _imageAlignment;
}
- (void)setImageAlignment:(ASButtonNodeImageAlignment)imageAlignment
{
ASLockScopeSelf();
_imageAlignment = imageAlignment;
}
#if TARGET_OS_IOS
- (void)setTitle:(NSString *)title withFont:(UIFont *)font withColor:(UIColor *)color forState:(UIControlState)state
{
NSDictionary *attributes = @{
NSFontAttributeName: font ? : [UIFont systemFontOfSize:[UIFont buttonFontSize]],
NSForegroundColorAttributeName : color ? : [UIColor blackColor]
};
NSAttributedString *string = [[NSAttributedString alloc] initWithString:title attributes:attributes];
[self setAttributedTitle:string forState:state];
}
#endif
- (NSAttributedString *)attributedTitleForState:(UIControlState)state
{
ASLockScopeSelf();
switch (state) {
case UIControlStateNormal:
return _normalAttributedTitle;
case UIControlStateHighlighted:
return _highlightedAttributedTitle;
case UIControlStateSelected:
return _selectedAttributedTitle;
case UIControlStateSelected | UIControlStateHighlighted:
return _selectedHighlightedAttributedTitle;
case UIControlStateDisabled:
return _disabledAttributedTitle;
default:
return _normalAttributedTitle;
}
}
- (void)setAttributedTitle:(NSAttributedString *)title forState:(UIControlState)state
{
{
ASLockScopeSelf();
switch (state) {
case UIControlStateNormal:
_normalAttributedTitle = [title copy];
break;
case UIControlStateHighlighted:
_highlightedAttributedTitle = [title copy];
break;
case UIControlStateSelected:
_selectedAttributedTitle = [title copy];
break;
case UIControlStateSelected | UIControlStateHighlighted:
_selectedHighlightedAttributedTitle = [title copy];
break;
case UIControlStateDisabled:
_disabledAttributedTitle = [title copy];
break;
default:
break;
}
}
[self updateTitle];
}
- (UIImage *)imageForState:(UIControlState)state
{
ASLockScopeSelf();
switch (state) {
case UIControlStateNormal:
return _normalImage;
case UIControlStateHighlighted:
return _highlightedImage;
case UIControlStateSelected:
return _selectedImage;
case UIControlStateSelected | UIControlStateHighlighted:
return _selectedHighlightedImage;
case UIControlStateDisabled:
return _disabledImage;
default:
return _normalImage;
}
}
- (void)setImage:(UIImage *)image forState:(UIControlState)state
{
{
ASLockScopeSelf();
switch (state) {
case UIControlStateNormal:
_normalImage = image;
break;
case UIControlStateHighlighted:
_highlightedImage = image;
break;
case UIControlStateSelected:
_selectedImage = image;
break;
case UIControlStateSelected | UIControlStateHighlighted:
_selectedHighlightedImage = image;
break;
case UIControlStateDisabled:
_disabledImage = image;
break;
default:
break;
}
}
[self updateImage];
}
- (UIImage *)backgroundImageForState:(UIControlState)state
{
ASLockScopeSelf();
switch (state) {
case UIControlStateNormal:
return _normalBackgroundImage;
case UIControlStateHighlighted:
return _highlightedBackgroundImage;
case UIControlStateSelected:
return _selectedBackgroundImage;
case UIControlStateSelected | UIControlStateHighlighted:
return _selectedHighlightedBackgroundImage;
case UIControlStateDisabled:
return _disabledBackgroundImage;
default:
return _normalBackgroundImage;
}
}
- (void)setBackgroundImage:(UIImage *)image forState:(UIControlState)state
{
{
ASLockScopeSelf();
switch (state) {
case UIControlStateNormal:
_normalBackgroundImage = image;
break;
case UIControlStateHighlighted:
_highlightedBackgroundImage = image;
break;
case UIControlStateSelected:
_selectedBackgroundImage = image;
break;
case UIControlStateSelected | UIControlStateHighlighted:
_selectedHighlightedBackgroundImage = image;
break;
case UIControlStateDisabled:
_disabledBackgroundImage = image;
break;
default:
break;
}
}
[self updateBackgroundImage];
}
- (NSString *)defaultAccessibilityLabel
{
ASLockScopeSelf();
return _titleNode.defaultAccessibilityLabel;
}
- (UIAccessibilityTraits)defaultAccessibilityTraits
{
return self.enabled ? UIAccessibilityTraitButton
: (UIAccessibilityTraitButton | UIAccessibilityTraitNotEnabled);
}
#pragma mark - Layout
#if !YOGA
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
UIEdgeInsets contentEdgeInsets;
ASButtonNodeImageAlignment imageAlignment;
ASLayoutSpec *spec;
ASStackLayoutSpec *stack = [[ASStackLayoutSpec alloc] init];
{
ASLockScopeSelf();
stack.direction = _laysOutHorizontally ? ASStackLayoutDirectionHorizontal : ASStackLayoutDirectionVertical;
stack.spacing = _contentSpacing;
stack.horizontalAlignment = _contentHorizontalAlignment;
stack.verticalAlignment = _contentVerticalAlignment;
contentEdgeInsets = _contentEdgeInsets;
imageAlignment = _imageAlignment;
}
NSMutableArray *children = [[NSMutableArray alloc] initWithCapacity:2];
if (_imageNode.image) {
[children addObject:_imageNode];
}
if (_titleNode.attributedText.length > 0) {
if (imageAlignment == ASButtonNodeImageAlignmentBeginning) {
[children addObject:_titleNode];
} else {
[children insertObject:_titleNode atIndex:0];
}
}
stack.children = children;
spec = stack;
if (UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, contentEdgeInsets) == NO) {
spec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:contentEdgeInsets child:spec];
}
if (_backgroundImageNode.image) {
spec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:spec background:_backgroundImageNode];
}
return spec;
}
#endif
- (void)layout
{
[super layout];
_backgroundImageNode.hidden = (_backgroundImageNode.image == nil);
_imageNode.hidden = (_imageNode.image == nil);
_titleNode.hidden = (_titleNode.attributedText.length == 0);
}
@end

View File

@ -0,0 +1,28 @@
//
// ASCGImageBuffer.h
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <CoreGraphics/CGDataProvider.h>
NS_ASSUME_NONNULL_BEGIN
AS_SUBCLASSING_RESTRICTED
@interface ASCGImageBuffer : NSObject
/// Init a zero-filled buffer with the given length.
- (instancetype)initWithLength:(NSUInteger)length;
@property (readonly) void *mutableBytes NS_RETURNS_INNER_POINTER;
/// Don't do any drawing or call any methods after calling this.
- (CGDataProviderRef)createDataProviderAndInvalidate CF_RETURNS_RETAINED;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,88 @@
//
// ASCGImageBuffer.mm
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import "ASCGImageBuffer.h"
#import <sys/mman.h>
#import <mach/mach_init.h>
#import <mach/vm_map.h>
#import <mach/vm_statistics.h>
/**
* The behavior of this class is modeled on the private function
* _CGDataProviderCreateWithCopyOfData, which is the function used
* by CGBitmapContextCreateImage.
*
* If the buffer is larger than a page, we use mmap and mark it as
* read-only when they are finished drawing. Then we wrap the VM
* in an NSData
*/
@implementation ASCGImageBuffer {
BOOL _createdData;
BOOL _isVM;
NSUInteger _length;
}
- (instancetype)initWithLength:(NSUInteger)length
{
if (self = [super init]) {
_length = length;
_isVM = (length >= vm_page_size);
if (_isVM) {
_mutableBytes = mmap(NULL, length, PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, VM_MAKE_TAG(VM_MEMORY_COREGRAPHICS_DATA), 0);
if (_mutableBytes == MAP_FAILED) {
NSAssert(NO, @"Failed to map for CG image data.");
_isVM = NO;
}
}
// Check the VM flag again because we may have failed above.
if (!_isVM) {
_mutableBytes = calloc(1, length);
}
}
return self;
}
- (void)dealloc
{
if (!_createdData) {
[ASCGImageBuffer deallocateBuffer:_mutableBytes length:_length isVM:_isVM];
}
}
- (CGDataProviderRef)createDataProviderAndInvalidate
{
NSAssert(!_createdData, @"Should not create data provider from buffer multiple times.");
_createdData = YES;
// Mark the pages as read-only.
if (_isVM) {
__unused kern_return_t result = vm_protect(mach_task_self(), (vm_address_t)_mutableBytes, _length, true, VM_PROT_READ);
NSAssert(result == noErr, @"Error marking buffer as read-only: %@", [NSError errorWithDomain:NSMachErrorDomain code:result userInfo:nil]);
}
// Wrap in an NSData
BOOL isVM = _isVM;
NSData *d = [[NSData alloc] initWithBytesNoCopy:_mutableBytes length:_length deallocator:^(void * _Nonnull bytes, NSUInteger length) {
[ASCGImageBuffer deallocateBuffer:bytes length:length isVM:isVM];
}];
return CGDataProviderCreateWithCFData((__bridge CFDataRef)d);
}
+ (void)deallocateBuffer:(void *)buf length:(NSUInteger)length isVM:(BOOL)isVM
{
if (isVM) {
__unused kern_return_t result = vm_deallocate(mach_task_self(), (vm_address_t)buf, length);
NSAssert(result == noErr, @"Failed to unmap cg image buffer: %@", [NSError errorWithDomain:NSMachErrorDomain code:result userInfo:nil]);
} else {
free(buf);
}
}
@end

View File

@ -0,0 +1,263 @@
//
// ASCellNode.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#ifndef MINIMAL_ASDK
#import <AsyncDisplayKit/ASDisplayNode.h>
NS_ASSUME_NONNULL_BEGIN
@class ASCellNode, ASTextNode;
@protocol ASRangeManagingNode;
typedef NSUInteger ASCellNodeAnimation;
typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) {
/**
* Indicates a cell has just became visible
*/
ASCellNodeVisibilityEventVisible,
/**
* Its position (determined by scrollView.contentOffset) has changed while at least 1px remains visible.
* It is possible that 100% of the cell is visible both before and after and only its position has changed,
* or that the position change has resulted in more or less of the cell being visible.
* Use CGRectIntersect between cellFrame and scrollView.bounds to get this rectangle
*/
ASCellNodeVisibilityEventVisibleRectChanged,
/**
* Indicates a cell is no longer visible
*/
ASCellNodeVisibilityEventInvisible,
/**
* Indicates user has started dragging the visible cell
*/
ASCellNodeVisibilityEventWillBeginDragging,
/**
* Indicates user has ended dragging the visible cell
*/
ASCellNodeVisibilityEventDidEndDragging,
};
/**
* Generic cell node. Subclass this instead of `ASDisplayNode` to use with `ASTableView` and `ASCollectionView`.
* @note When a cell node is contained inside a collection view (or table view),
* calling `-setNeedsLayout` will also notify the collection on the main thread
* so that the collection can update its item layout if the cell's size changed.
*/
@interface ASCellNode : ASDisplayNode
/**
* @abstract When enabled, ensures that the cell is completely displayed before allowed onscreen.
*
* @default NO
* @discussion Normally, ASCellNodes are preloaded and have finished display before they are onscreen.
* However, if the Table or Collection's rangeTuningParameters are set to small values (or 0),
* or if the user is scrolling rapidly on a slow device, it is possible for a cell's display to
* be incomplete when it becomes visible.
*
* In this case, normally placeholder states are shown and scrolling continues uninterrupted.
* The finished, drawn content is then shown as soon as it is ready.
*
* With this property set to YES, the main thread will be blocked until display is complete for
* the cell. This is more similar to UIKit, and in fact makes AsyncDisplayKit scrolling visually
* indistinguishable from UIKit's, except being faster.
*
* Using this option does not eliminate all of the performance advantages of AsyncDisplayKit.
* Normally, a cell has been preloading and is almost done when it reaches the screen, so the
* blocking time is very short. If the rangeTuningParameters are set to 0, still this option
* outperforms UIKit: while the main thread is waiting, subnode display executes concurrently.
*/
@property BOOL neverShowPlaceholders;
/*
* The kind of supplementary element this node represents, if any.
*
* @return The supplementary element kind, or @c nil if this node does not represent a supplementary element.
*/
@property (nullable, copy, readonly) NSString *supplementaryElementKind;
/*
* The layout attributes currently assigned to this node, if any.
*
* @discussion This property is useful because it is set before @c collectionView:willDisplayNode:forItemAtIndexPath:
* is called, when the node is not yet in the hierarchy and its frame cannot be converted to/from other nodes. Instead
* you can use the layout attributes object to learn where and how the cell will be displayed.
*/
@property (nullable, copy, readonly) UICollectionViewLayoutAttributes *layoutAttributes;
/**
* A Boolean value that is synchronized with the underlying collection or tableView cell property.
* Setting this value is equivalent to calling selectItem / deselectItem on the collection or table.
*/
@property (getter=isSelected) BOOL selected;
/**
* A Boolean value that is synchronized with the underlying collection or tableView cell property.
* Setting this value is equivalent to calling highlightItem / unHighlightItem on the collection or table.
*/
@property (getter=isHighlighted) BOOL highlighted;
/**
* The current index path of this cell node, or @c nil if this node is
* not a valid item inside a table node or collection node.
*/
@property (nullable, copy, readonly) NSIndexPath *indexPath;
/**
* BETA: API is under development. We will attempt to provide an easy migration pathway for any changes.
*
* The view-model currently assigned to this node, if any.
*
* This property may be set off the main thread, but this method will never be invoked concurrently on the
*/
@property (nullable) id nodeModel;
/**
* Asks the node whether it can be updated to the given node model.
*
* The default implementation returns YES if the class matches that of the current view-model.
*/
- (BOOL)canUpdateToNodeModel:(id)nodeModel;
/**
* The backing view controller, or @c nil if the node wasn't initialized with backing view controller
* @note This property must be accessed on the main thread.
*/
@property (nullable, nonatomic, readonly) UIViewController *viewController;
/**
* The table- or collection-node that this cell is a member of, if any.
*/
@property (nullable, weak, readonly) id<ASRangeManagingNode> owningNode;
/*
* ASCellNode must forward touch events in order for UITableView and UICollectionView tap handling to work. Overriding
* these methods (e.g. for highlighting) requires the super method be called.
*/
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
- (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
/**
* Called by the system when ASCellNode is used with an ASCollectionNode. It will not be called by ASTableNode.
* When the UICollectionViewLayout object returns a new UICollectionViewLayoutAttributes object, the corresponding ASCellNode will be updated.
* See UICollectionViewCell's applyLayoutAttributes: for a full description.
*/
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes;
/**
* @abstract Initializes a cell with a given view controller block.
*
* @param viewControllerBlock The block that will be used to create the backing view controller.
* @param didLoadBlock The block that will be called after the view controller's view is loaded.
*
* @return An ASCellNode created using the root view of the view controller provided by the viewControllerBlock.
* The view controller's root view is resized to match the calculated size produced during layout.
*
*/
- (instancetype)initWithViewControllerBlock:(ASDisplayNodeViewControllerBlock)viewControllerBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock;
/**
* @abstract Notifies the cell node of certain visibility events, such as changing visible rect.
*
* @warning In cases where an ASCellNode is used as a plain node i.e. not returned from the
* nodeBlockForItemAtIndexPath/nodeForItemAtIndexPath data source methods this method will
* deliver only the `Visible` and `Invisible` events, `scrollView` will be nil, and
* `cellFrame` will be the zero rect.
*/
- (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(nullable UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame;
#pragma mark - UITableViewCell specific passthrough properties
/* @abstract The selection style when a tap on a cell occurs
* @default UITableViewCellSelectionStyleDefault
* ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes.
*/
@property UITableViewCellSelectionStyle selectionStyle;
/* @abstract The focus style when a cell is focused
* @default UITableViewCellFocusStyleDefault
* ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes.
*/
@property UITableViewCellFocusStyle focusStyle;
/* @abstract The view used as the background of the cell when it is selected.
* ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes.
* ASCollectionView uses these properties when configuring UICollectionViewCells that host ASCellNodes.
*/
@property (nullable) UIView *selectedBackgroundView;
/* @abstract The view used as the background of the cell.
* ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes.
* ASCollectionView uses these properties when configuring UICollectionViewCells that host ASCellNodes.
*/
@property (nullable) UIView *backgroundView;
/* @abstract The accessory type view on the right side of the cell. Please take care of your ASLayoutSpec so that doesn't overlay the accessoryView
* @default UITableViewCellAccessoryNone
* ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes.
*/
@property UITableViewCellAccessoryType accessoryType;
/* @abstract The inset of the cell separator line
* ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes.
*/
@property UIEdgeInsets separatorInset;
@end
@interface ASCellNode (Unavailable)
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE;
- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE;
- (void)setLayerBacked:(BOOL)layerBacked AS_UNAVAILABLE("ASCellNode does not support layer-backing, although subnodes may be layer-backed.");
@end
/**
* Simple label-style cell node. Read its source for an example of custom <ASCellNode>s.
*/
@interface ASTextCellNode : ASCellNode
/**
* Initializes a text cell with given text attributes and text insets
*/
- (instancetype)initWithAttributes:(NSDictionary *)textAttributes insets:(UIEdgeInsets)textInsets;
/**
* Text to display.
*/
@property (nullable, copy) NSString *text;
/**
* A dictionary containing key-value pairs for text attributes. You can specify the font, text color, text shadow color, and text shadow offset using the keys listed in NSString UIKit Additions Reference.
*/
@property (copy) NSDictionary<NSAttributedStringKey, id> *textAttributes;
/**
* The text inset or outset for each edge. The default value is 15.0 horizontal and 11.0 vertical padding.
*/
@property UIEdgeInsets textInsets;
/**
* The text node used by this cell node.
*/
@property (readonly) ASTextNode *textNode;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,488 @@
//
// ASCellNode.mm
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#ifndef MINIMAL_ASDK
#import <AsyncDisplayKit/ASCellNode+Internal.h>
#import <AsyncDisplayKit/ASEqualityHelpers.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
#import <AsyncDisplayKit/ASCollectionView+Undeprecated.h>
#import <AsyncDisplayKit/ASCollectionElement.h>
#import <AsyncDisplayKit/ASTableView+Undeprecated.h>
#import <AsyncDisplayKit/_ASDisplayView.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASTextNode.h>
#import <AsyncDisplayKit/ASCollectionNode.h>
#import <AsyncDisplayKit/ASTableNode.h>
#import <AsyncDisplayKit/ASViewController.h>
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#pragma mark -
#pragma mark ASCellNode
@interface ASCellNode ()
{
ASDisplayNodeViewControllerBlock _viewControllerBlock;
ASDisplayNodeDidLoadBlock _viewControllerDidLoadBlock;
ASDisplayNode *_viewControllerNode;
UIViewController *_viewController;
BOOL _suspendInteractionDelegate;
BOOL _selected;
BOOL _highlighted;
UICollectionViewLayoutAttributes *_layoutAttributes;
}
@end
@implementation ASCellNode
@synthesize interactionDelegate = _interactionDelegate;
- (instancetype)init
{
if (!(self = [super init]))
return nil;
// Use UITableViewCell defaults
_selectionStyle = UITableViewCellSelectionStyleDefault;
_focusStyle = UITableViewCellFocusStyleDefault;
self.clipsToBounds = YES;
return self;
}
- (instancetype)initWithViewControllerBlock:(ASDisplayNodeViewControllerBlock)viewControllerBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock
{
if (!(self = [super init]))
return nil;
ASDisplayNodeAssertNotNil(viewControllerBlock, @"should initialize with a valid block that returns a UIViewController");
_viewControllerBlock = viewControllerBlock;
_viewControllerDidLoadBlock = didLoadBlock;
return self;
}
- (void)didLoad
{
[super didLoad];
if (_viewControllerBlock != nil) {
_viewController = _viewControllerBlock();
_viewControllerBlock = nil;
if ([_viewController isKindOfClass:[ASViewController class]]) {
ASViewController *asViewController = (ASViewController *)_viewController;
_viewControllerNode = asViewController.node;
[_viewController loadViewIfNeeded];
} else {
// Careful to avoid retain cycle
UIViewController *viewController = _viewController;
_viewControllerNode = [[ASDisplayNode alloc] initWithViewBlock:^{
return viewController.view;
}];
}
[self addSubnode:_viewControllerNode];
// Since we just loaded our node, and added _viewControllerNode as a subnode,
// _viewControllerNode must have just loaded its view, so now is an appropriate
// time to execute our didLoadBlock, if we were given one.
if (_viewControllerDidLoadBlock != nil) {
_viewControllerDidLoadBlock(self);
_viewControllerDidLoadBlock = nil;
}
}
}
- (void)layout
{
[super layout];
_viewControllerNode.frame = self.bounds;
}
- (void)_rootNodeDidInvalidateSize
{
if (_interactionDelegate != nil) {
[_interactionDelegate nodeDidInvalidateSize:self];
} else {
[super _rootNodeDidInvalidateSize];
}
}
- (void)_layoutTransitionMeasurementDidFinish
{
if (_interactionDelegate != nil) {
[_interactionDelegate nodeDidInvalidateSize:self];
} else {
[super _layoutTransitionMeasurementDidFinish];
}
}
- (BOOL)isSelected
{
return ASLockedSelf(_selected);
}
- (void)setSelected:(BOOL)selected
{
if (ASLockedSelfCompareAssign(_selected, selected)) {
if (!_suspendInteractionDelegate) {
ASPerformBlockOnMainThread(^{
[_interactionDelegate nodeSelectedStateDidChange:self];
});
}
}
}
- (BOOL)isHighlighted
{
return ASLockedSelf(_highlighted);
}
- (void)setHighlighted:(BOOL)highlighted
{
if (ASLockedSelfCompareAssign(_highlighted, highlighted)) {
if (!_suspendInteractionDelegate) {
ASPerformBlockOnMainThread(^{
[_interactionDelegate nodeHighlightedStateDidChange:self];
});
}
}
}
- (void)__setSelectedFromUIKit:(BOOL)selected;
{
// Note: Race condition could mean redundant sets. Risk is low.
if (ASLockedSelf(_selected != selected)) {
_suspendInteractionDelegate = YES;
self.selected = selected;
_suspendInteractionDelegate = NO;
}
}
- (void)__setHighlightedFromUIKit:(BOOL)highlighted;
{
// Note: Race condition could mean redundant sets. Risk is low.
if (ASLockedSelf(_highlighted != highlighted)) {
_suspendInteractionDelegate = YES;
self.highlighted = highlighted;
_suspendInteractionDelegate = NO;
}
}
- (BOOL)canUpdateToNodeModel:(id)nodeModel
{
return [self.nodeModel class] == [nodeModel class];
}
- (NSIndexPath *)indexPath
{
return [self.owningNode indexPathForNode:self];
}
- (UIViewController *)viewController
{
ASDisplayNodeAssertMainThread();
// Force the view to load so that we will create the
// view controller if we haven't already.
if (self.isNodeLoaded == NO) {
[self view];
}
return _viewController;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-missing-super-calls"
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert([self.view isKindOfClass:_ASDisplayView.class], @"ASCellNode views must be of type _ASDisplayView");
[(_ASDisplayView *)self.view __forwardTouchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert([self.view isKindOfClass:_ASDisplayView.class], @"ASCellNode views must be of type _ASDisplayView");
[(_ASDisplayView *)self.view __forwardTouchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert([self.view isKindOfClass:_ASDisplayView.class], @"ASCellNode views must be of type _ASDisplayView");
[(_ASDisplayView *)self.view __forwardTouchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert([self.view isKindOfClass:_ASDisplayView.class], @"ASCellNode views must be of type _ASDisplayView");
[(_ASDisplayView *)self.view __forwardTouchesCancelled:touches withEvent:event];
}
#pragma clang diagnostic pop
- (UICollectionViewLayoutAttributes *)layoutAttributes
{
return ASLockedSelf(_layoutAttributes);
}
- (void)setLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
ASDisplayNodeAssertMainThread();
if (ASLockedSelfCompareAssignObjects(_layoutAttributes, layoutAttributes)) {
if (layoutAttributes != nil) {
[self applyLayoutAttributes:layoutAttributes];
}
}
}
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
// To be overriden by subclasses
}
- (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame
{
// To be overriden by subclasses
}
- (void)didEnterVisibleState
{
[super didEnterVisibleState];
if (self.neverShowPlaceholders) {
[self recursivelyEnsureDisplaySynchronously:YES];
}
[self handleVisibilityChange:YES];
}
- (void)didExitVisibleState
{
[super didExitVisibleState];
[self handleVisibilityChange:NO];
}
+ (BOOL)requestsVisibilityNotifications
{
static NSCache<Class, NSNumber *> *cache;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cache = [[NSCache alloc] init];
});
NSNumber *result = [cache objectForKey:self];
if (result == nil) {
BOOL overrides = ASSubclassOverridesSelector([ASCellNode class], self, @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:));
result = overrides ? (NSNumber *)kCFBooleanTrue : (NSNumber *)kCFBooleanFalse;
[cache setObject:result forKey:self];
}
return (result == (NSNumber *)kCFBooleanTrue);
}
- (void)handleVisibilityChange:(BOOL)isVisible
{
if ([self.class requestsVisibilityNotifications] == NO) {
return; // The work below is expensive, and only valuable for subclasses watching visibility events.
}
// NOTE: This assertion is failing in some apps and will be enabled soon.
// ASDisplayNodeAssert(self.isNodeLoaded, @"Node should be loaded in order for it to become visible or invisible. If not in this situation, we shouldn't trigger creating the view.");
UIView *view = self.view;
CGRect cellFrame = CGRectZero;
// Ensure our _scrollView is still valid before converting. It's also possible that we have already been removed from the _scrollView,
// in which case it is not valid to perform a convertRect (this actually crashes on iOS 8).
UIScrollView *scrollView = (_scrollView != nil && view.superview != nil && [view isDescendantOfView:_scrollView]) ? _scrollView : nil;
if (scrollView) {
cellFrame = [view convertRect:view.bounds toView:_scrollView];
}
// If we did not convert, we'll pass along CGRectZero and a nil scrollView. The EventInvisible call is thus equivalent to
// didExitVisibileState, but is more convenient for the developer than implementing multiple methods.
[self cellNodeVisibilityEvent:isVisible ? ASCellNodeVisibilityEventVisible
: ASCellNodeVisibilityEventInvisible
inScrollView:scrollView
withCellFrame:cellFrame];
}
- (NSMutableArray<NSDictionary *> *)propertiesForDebugDescription
{
NSMutableArray *result = [super propertiesForDebugDescription];
UIScrollView *scrollView = self.scrollView;
ASDisplayNode *owningNode = scrollView.asyncdisplaykit_node;
if ([owningNode isKindOfClass:[ASCollectionNode class]]) {
NSIndexPath *ip = [(ASCollectionNode *)owningNode indexPathForNode:self];
if (ip != nil) {
[result addObject:@{ @"indexPath" : ip }];
}
[result addObject:@{ @"collectionNode" : owningNode }];
} else if ([owningNode isKindOfClass:[ASTableNode class]]) {
NSIndexPath *ip = [(ASTableNode *)owningNode indexPathForNode:self];
if (ip != nil) {
[result addObject:@{ @"indexPath" : ip }];
}
[result addObject:@{ @"tableNode" : owningNode }];
} else if ([scrollView isKindOfClass:[ASCollectionView class]]) {
NSIndexPath *ip = [(ASCollectionView *)scrollView indexPathForNode:self];
if (ip != nil) {
[result addObject:@{ @"indexPath" : ip }];
}
[result addObject:@{ @"collectionView" : ASObjectDescriptionMakeTiny(scrollView) }];
} else if ([scrollView isKindOfClass:[ASTableView class]]) {
NSIndexPath *ip = [(ASTableView *)scrollView indexPathForNode:self];
if (ip != nil) {
[result addObject:@{ @"indexPath" : ip }];
}
[result addObject:@{ @"tableView" : ASObjectDescriptionMakeTiny(scrollView) }];
}
return result;
}
- (NSString *)supplementaryElementKind
{
return self.collectionElement.supplementaryElementKind;
}
- (BOOL)supportsLayerBacking
{
return NO;
}
- (BOOL)shouldUseUIKitCell
{
return NO;
}
@end
#pragma mark -
#pragma mark ASWrapperCellNode
// TODO: Consider if other calls, such as willDisplayCell, should be bridged to this class.
@implementation ASWrapperCellNode : ASCellNode
- (BOOL)shouldUseUIKitCell
{
return YES;
}
@end
#pragma mark -
#pragma mark ASTextCellNode
@implementation ASTextCellNode {
NSDictionary<NSAttributedStringKey, id> *_textAttributes;
UIEdgeInsets _textInsets;
NSString *_text;
}
static const CGFloat kASTextCellNodeDefaultFontSize = 18.0f;
static const CGFloat kASTextCellNodeDefaultHorizontalPadding = 15.0f;
static const CGFloat kASTextCellNodeDefaultVerticalPadding = 11.0f;
- (instancetype)init
{
return [self initWithAttributes:[ASTextCellNode defaultTextAttributes] insets:[ASTextCellNode defaultTextInsets]];
}
- (instancetype)initWithAttributes:(NSDictionary *)textAttributes insets:(UIEdgeInsets)textInsets
{
self = [super init];
if (self) {
_textInsets = textInsets;
_textAttributes = [textAttributes copy];
_textNode = [[ASTextNode alloc] init];
self.automaticallyManagesSubnodes = YES;
}
return self;
}
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:self.textInsets child:self.textNode];
}
+ (NSDictionary *)defaultTextAttributes
{
return @{NSFontAttributeName : [UIFont systemFontOfSize:kASTextCellNodeDefaultFontSize]};
}
+ (UIEdgeInsets)defaultTextInsets
{
return UIEdgeInsetsMake(kASTextCellNodeDefaultVerticalPadding, kASTextCellNodeDefaultHorizontalPadding, kASTextCellNodeDefaultVerticalPadding, kASTextCellNodeDefaultHorizontalPadding);
}
- (NSDictionary *)textAttributes
{
return ASLockedSelf(_textAttributes);
}
- (void)setTextAttributes:(NSDictionary *)textAttributes
{
ASDisplayNodeAssertNotNil(textAttributes, @"Invalid text attributes");
ASLockScopeSelf();
if (ASCompareAssignCopy(_textAttributes, textAttributes)) {
[self locked_updateAttributedText];
}
}
- (UIEdgeInsets)textInsets
{
return ASLockedSelf(_textInsets);
}
- (void)setTextInsets:(UIEdgeInsets)textInsets
{
if (ASLockedSelfCompareAssignCustom(_textInsets, textInsets, UIEdgeInsetsEqualToEdgeInsets)) {
[self setNeedsLayout];
}
}
- (NSString *)text
{
return ASLockedSelf(_text);
}
- (void)setText:(NSString *)text
{
ASLockScopeSelf();
if (ASCompareAssignCopy(_text, text)) {
[self locked_updateAttributedText];
}
}
- (void)locked_updateAttributedText
{
if (_text == nil) {
_textNode.attributedText = nil;
return;
}
_textNode.attributedText = [[NSAttributedString alloc] initWithString:_text attributes:_textAttributes];
[self setNeedsLayout];
}
@end
#endif

View File

@ -0,0 +1,82 @@
//
// ASCollectionNode+Beta.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#ifndef MINIMAL_ASDK
#import <AsyncDisplayKit/ASCollectionNode.h>
@protocol ASCollectionViewLayoutFacilitatorProtocol, ASCollectionLayoutDelegate, ASBatchFetchingDelegate;
@class ASElementMap;
NS_ASSUME_NONNULL_BEGIN
@interface ASCollectionNode (Beta)
/**
* Allows providing a custom subclass of ASCollectionView to be managed by ASCollectionNode.
*
* @default [ASCollectionView class] is used whenever this property is unset or nil.
*/
@property (nullable, nonatomic) Class collectionViewClass;
/**
* The elements that are currently displayed. The "UIKit index space". Must be accessed on main thread.
*/
@property (nonatomic, readonly) ASElementMap *visibleElements;
@property (nullable, readonly) id<ASCollectionLayoutDelegate> layoutDelegate;
@property (nullable, nonatomic, weak) id<ASBatchFetchingDelegate> batchFetchingDelegate;
/**
* When this mode is enabled, ASCollectionView matches the timing of UICollectionView as closely as
* possible, ensuring that all reload and edit operations are performed on the main thread as
* blocking calls.
*
* This mode is useful for applications that are debugging issues with their collection view
* implementation. In particular, some applications do not properly conform to the API requirement
* of UICollectionView, and these applications may experience difficulties with ASCollectionView.
* Providing this mode allows for developers to work towards resolving technical debt in their
* collection view data source, while ramping up asynchronous collection layout.
*
* NOTE: Because this mode results in expensive operations like cell layout being performed on the
* main thread, it should be used as a tool to resolve data source conformance issues with Apple
* collection view API.
*
* @default defaults to ASCellLayoutModeNone.
*/
@property (nonatomic) ASCellLayoutMode cellLayoutMode;
/**
* Returns YES if the ASCollectionNode contents are completely synchronized with the underlying collection-view layout.
*/
@property (nonatomic, readonly, getter=isSynchronized) BOOL synchronized;
/**
* Schedules a block to be performed (on the main thread) as soon as the completion block is called
* on performBatchUpdates:.
*
* When isSynchronized == YES, the block is run block immediately (before the method returns).
*/
- (void)onDidFinishSynchronizing:(void (^)(void))didFinishSynchronizing;
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id<ASCollectionViewLayoutFacilitatorProtocol>)layoutFacilitator;
- (instancetype)initWithLayoutDelegate:(id<ASCollectionLayoutDelegate>)layoutDelegate layoutFacilitator:(nullable id<ASCollectionViewLayoutFacilitatorProtocol>)layoutFacilitator;
- (void)beginUpdates ASDISPLAYNODE_DEPRECATED_MSG("Use -performBatchUpdates:completion: instead.");
- (void)endUpdatesAnimated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use -performBatchUpdates:completion: instead.");
- (void)endUpdatesAnimated:(BOOL)animated completion:(nullable void (^)(BOOL))completion ASDISPLAYNODE_DEPRECATED_MSG("Use -performBatchUpdates:completion: instead.");
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,954 @@
//
// ASCollectionNode.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#ifndef MINIMAL_ASDK
#import <UIKit/UICollectionView.h>
#import <AsyncDisplayKit/ASDisplayNode.h>
#import <AsyncDisplayKit/ASRangeControllerUpdateRangeProtocol+Beta.h>
#import <AsyncDisplayKit/ASCollectionView.h>
#import <AsyncDisplayKit/ASBlockTypes.h>
#import <AsyncDisplayKit/ASRangeManagingNode.h>
@protocol ASCollectionViewLayoutFacilitatorProtocol;
@protocol ASCollectionDelegate;
@protocol ASCollectionDataSource;
@class ASCollectionView;
NS_ASSUME_NONNULL_BEGIN
/**
* ASCollectionNode is a node based class that wraps an ASCollectionView. It can be used
* as a subnode of another node, and provide room for many (great) features and improvements later on.
*/
@interface ASCollectionNode : ASDisplayNode <ASRangeControllerUpdateRangeProtocol, ASRangeManagingNode>
- (instancetype)init NS_UNAVAILABLE;
/**
* Initializes an ASCollectionNode
*
* @discussion Initializes and returns a newly allocated collection node object with the specified layout.
*
* @param layout The layout object to use for organizing items. The collection view stores a strong reference to the specified object. Must not be nil.
*/
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout;
/**
* Initializes an ASCollectionNode
*
* @discussion Initializes and returns a newly allocated collection node object with the specified frame and layout.
*
* @param frame The frame rectangle for the collection view, measured in points. The origin of the frame is relative to the superview in which you plan to add it. This frame is passed to the superclass during initialization.
* @param layout The layout object to use for organizing items. The collection view stores a strong reference to the specified object. Must not be nil.
*/
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
/**
* Returns the corresponding ASCollectionView
*
* @return view The corresponding ASCollectionView.
*/
@property (readonly) ASCollectionView *view;
/**
* The object that acts as the asynchronous delegate of the collection view
*
* @discussion The delegate must adopt the ASCollectionDelegate protocol. The collection view maintains a weak reference to the delegate object.
*
* The delegate object is responsible for providing size constraints for nodes and indicating whether batch fetching should begin.
* @note This is a convenience method which sets the asyncDelegate on the collection node's collection view.
*/
@property (nullable, weak) id <ASCollectionDelegate> delegate;
/**
* The object that acts as the asynchronous data source of the collection view
*
* @discussion The datasource must adopt the ASCollectionDataSource protocol. The collection view maintains a weak reference to the datasource object.
*
* The datasource object is responsible for providing nodes or node creation blocks to the collection view.
* @note This is a convenience method which sets the asyncDatasource on the collection node's collection view.
*/
@property (nullable, weak) id <ASCollectionDataSource> dataSource;
/**
* The number of screens left to scroll before the delegate -collectionNode:beginBatchFetchingWithContext: is called.
*
* Defaults to two screenfuls.
*/
@property (nonatomic) CGFloat leadingScreensForBatching;
/*
* A Boolean value that determines whether the collection node will be flipped.
* If the value of this property is YES, the first cell node will be at the bottom of the collection node (as opposed to the top by default). This is useful for chat/messaging apps. The default value is NO.
*/
@property (nonatomic) BOOL inverted;
/**
* A Boolean value that indicates whether users can select items in the collection node.
* If the value of this property is YES (the default), users can select items. If you want more fine-grained control over the selection of items, you must provide a delegate object and implement the appropriate methods of the UICollectionNodeDelegate protocol.
*/
@property (nonatomic) BOOL allowsSelection;
/**
* A Boolean value that determines whether users can select more than one item in the collection node.
* This property controls whether multiple items can be selected simultaneously. The default value of this property is NO.
* When the value of this property is YES, tapping a cell adds it to the current selection (assuming the delegate permits the cell to be selected). Tapping the cell again removes it from the selection.
*/
@property (nonatomic) BOOL allowsMultipleSelection;
/**
* A Boolean value that determines whether bouncing always occurs when vertical scrolling reaches the end of the content.
* The default value of this property is NO.
*/
@property (nonatomic) BOOL alwaysBounceVertical;
/**
* A Boolean value that determines whether bouncing always occurs when horizontal scrolling reaches the end of the content view.
* The default value of this property is NO.
*/
@property (nonatomic) BOOL alwaysBounceHorizontal;
/**
* A Boolean value that controls whether the vertical scroll indicator is visible.
* The default value of this property is YES.
*/
@property (nonatomic) BOOL showsVerticalScrollIndicator;
/**
* A Boolean value that controls whether the horizontal scroll indicator is visible.
* The default value of this property is NO.
*/
@property (nonatomic) BOOL showsHorizontalScrollIndicator;
/**
* The layout used to organize the node's items.
*
* @discussion Assigning a new layout object to this property causes the new layout to be applied (without animations) to the nodes items.
*/
@property (nonatomic) UICollectionViewLayout *collectionViewLayout;
/**
* Optional introspection object for the collection node's layout.
*
* @discussion Since supplementary and decoration nodes are controlled by the layout, this object
* is used as a bridge to provide information to the internal data controller about the existence of these views and
* their associated index paths. For collections using `UICollectionViewFlowLayout`, a default inspector
* implementation `ASCollectionViewFlowLayoutInspector` is created and set on this property by default. Custom
* collection layout subclasses will need to provide their own implementation of an inspector object for their
* supplementary elements to be compatible with `ASCollectionNode`'s supplementary node support.
*/
@property (nonatomic, weak) id<ASCollectionViewLayoutInspecting> layoutInspector;
/**
* The distance that the content view is inset from the collection node edges. Defaults to UIEdgeInsetsZero.
*/
@property (nonatomic) UIEdgeInsets contentInset;
/**
* The offset of the content view's origin from the collection node's origin. Defaults to CGPointZero.
*/
@property (nonatomic) CGPoint contentOffset;
/**
* Sets the offset from the content nodes origin to the collection nodes origin.
*
* @param contentOffset The offset
*
* @param animated YES to animate to this new offset at a constant velocity, NO to not aniamte and immediately make the transition.
*/
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;
/**
* Tuning parameters for a range type in full mode.
*
* @param rangeType The range type to get the tuning parameters for.
*
* @return A tuning parameter value for the given range type in full mode.
*
* @see ASLayoutRangeMode
* @see ASLayoutRangeType
*/
- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT;
/**
* Set the tuning parameters for a range type in full mode.
*
* @param tuningParameters The tuning parameters to store for a range type.
* @param rangeType The range type to set the tuning parameters for.
*
* @see ASLayoutRangeMode
* @see ASLayoutRangeType
*/
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType;
/**
* Tuning parameters for a range type in the specified mode.
*
* @param rangeMode The range mode to get the running parameters for.
* @param rangeType The range type to get the tuning parameters for.
*
* @return A tuning parameter value for the given range type in the given mode.
*
* @see ASLayoutRangeMode
* @see ASLayoutRangeType
*/
- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT;
/**
* Set the tuning parameters for a range type in the specified mode.
*
* @param tuningParameters The tuning parameters to store for a range type.
* @param rangeMode The range mode to set the running parameters for.
* @param rangeType The range type to set the tuning parameters for.
*
* @see ASLayoutRangeMode
* @see ASLayoutRangeType
*/
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType;
/**
* Scrolls the collection to the given item.
*
* @param indexPath The index path of the item.
* @param scrollPosition Where the item should end up after the scroll.
* @param animated Whether the scroll should be animated or not.
*
* This method must be called on the main thread.
*/
- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated;
/**
* Determines collection node's current scroll direction. Supports 2-axis collection nodes.
*
* @return a bitmask of ASScrollDirection values.
*/
@property (nonatomic, readonly) ASScrollDirection scrollDirection;
/**
* Determines collection node's scrollable directions.
*
* @return a bitmask of ASScrollDirection values.
*/
@property (nonatomic, readonly) ASScrollDirection scrollableDirections;
#pragma mark - Editing
/**
* Registers the given kind of supplementary node for use in creating node-backed supplementary elements.
*
* @param elementKind The kind of supplementary node that will be requested through the data source.
*
* @discussion Use this method to register support for the use of supplementary nodes in place of the default
* `registerClass:forSupplementaryViewOfKind:withReuseIdentifier:` and `registerNib:forSupplementaryViewOfKind:withReuseIdentifier:`
* methods. This method will register an internal backing view that will host the contents of the supplementary nodes
* returned from the data source.
*/
- (void)registerSupplementaryNodeOfKind:(NSString *)elementKind;
/**
* Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread.
* The data source must be updated to reflect the changes before the update block completes.
*
* @param animated NO to disable animations for this batch
* @param updates The block that performs the relevant insert, delete, reload, or move operations.
* @param completion A completion handler block to execute when all of the operations are finished. This block takes a single
* Boolean parameter that contains the value YES if all of the related animations completed successfully or
* NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread.
*/
- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion;
/**
* Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread.
* The data source must be updated to reflect the changes before the update block completes.
*
* @param updates The block that performs the relevant insert, delete, reload, or move operations.
* @param completion A completion handler block to execute when all of the operations are finished. This block takes a single
* Boolean parameter that contains the value YES if all of the related animations completed successfully or
* NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread.
*/
- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion;
/**
* Returns YES if the ASCollectionNode is still processing changes from performBatchUpdates:.
* This is typically the concurrent allocation (calling nodeBlocks) and layout of newly inserted
* ASCellNodes. If YES is returned, then calling -waitUntilAllUpdatesAreProcessed may take tens of
* milliseconds to return as it blocks on these concurrent operations.
*
* Returns NO if ASCollectionNode is fully synchronized with the underlying UICollectionView. This
* means that until the next performBatchUpdates: is called, it is safe to compare UIKit values
* (such as from UICollectionViewLayout) with your app's data source.
*
* This method will always return NO if called immediately after -waitUntilAllUpdatesAreProcessed.
*/
@property (nonatomic, readonly) BOOL isProcessingUpdates;
/**
* Schedules a block to be performed (on the main thread) after processing of performBatchUpdates:
* is finished (completely synchronized to UIKit). The blocks will be run at the moment that
* -isProcessingUpdates changes from YES to NO;
*
* When isProcessingUpdates == NO, the block is run block immediately (before the method returns).
*
* Blocks scheduled by this mechanism are NOT guaranteed to run in the order they are scheduled.
* They may also be delayed if performBatchUpdates continues to be called; the blocks will wait until
* all running updates are finished.
*
* Calling -waitUntilAllUpdatesAreProcessed is one way to flush any pending update completion blocks.
*/
- (void)onDidFinishProcessingUpdates:(void (^)(void))didFinishProcessingUpdates;
/**
* Blocks execution of the main thread until all section and item updates are committed to the view. This method must be called from the main thread.
*/
- (void)waitUntilAllUpdatesAreProcessed;
/**
* Inserts one or more sections.
*
* @param sections An index set that specifies the sections to insert.
*
* @discussion This method must be called from the main thread. The data source must be updated to reflect the changes
* before this method is called.
*/
- (void)insertSections:(NSIndexSet *)sections;
/**
* Deletes one or more sections.
*
* @param sections An index set that specifies the sections to delete.
*
* @discussion This method must be called from the main thread. The data source must be updated to reflect the changes
* before this method is called.
*/
- (void)deleteSections:(NSIndexSet *)sections;
/**
* Reloads the specified sections.
*
* @param sections An index set that specifies the sections to reload.
*
* @discussion This method must be called from the main thread. The data source must be updated to reflect the changes
* before this method is called.
*/
- (void)reloadSections:(NSIndexSet *)sections;
/**
* Moves a section to a new location.
*
* @param section The index of the section to move.
*
* @param newSection The index that is the destination of the move for the section.
*
* @discussion This method must be called from the main thread. The data source must be updated to reflect the changes
* before this method is called.
*/
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection;
/**
* Inserts items at the locations identified by an array of index paths.
*
* @param indexPaths An array of NSIndexPath objects, each representing an item index and section index that together identify an item.
*
* @discussion This method must be called from the main thread. The data source must be updated to reflect the changes
* before this method is called.
*/
- (void)insertItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
/**
* Deletes the items specified by an array of index paths.
*
* @param indexPaths An array of NSIndexPath objects identifying the items to delete.
*
* @discussion This method must be called from the main thread. The data source must be updated to reflect the changes
* before this method is called.
*/
- (void)deleteItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
/**
* Reloads the specified items.
*
* @param indexPaths An array of NSIndexPath objects identifying the items to reload.
*
* @discussion This method must be called from the main thread. The data source must be updated to reflect the changes
* before this method is called.
*/
- (void)reloadItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
/**
* Moves the item at a specified location to a destination location.
*
* @param indexPath The index path identifying the item to move.
*
* @param newIndexPath The index path that is the destination of the move for the item.
*
* @discussion This method must be called from the main thread. The data source must be updated to reflect the changes
* before this method is called.
*/
- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath;
/**
* Reload everything from scratch, destroying the working range and all cached nodes.
*
* @param completion block to run on completion of asynchronous loading or nil. If supplied, the block is run on
* the main thread.
* @warning This method is substantially more expensive than UICollectionView's version.
*/
- (void)reloadDataWithCompletion:(nullable void (^)(void))completion;
/**
* Reload everything from scratch, destroying the working range and all cached nodes.
*
* @warning This method is substantially more expensive than UICollectionView's version.
*/
- (void)reloadData;
/**
* Triggers a relayout of all nodes.
*
* @discussion This method invalidates and lays out every cell node in the collection view.
*/
- (void)relayoutItems;
#pragma mark - Selection
/**
* The index paths of the selected items, or @c nil if no items are selected.
*/
@property (nullable, nonatomic, copy, readonly) NSArray<NSIndexPath *> *indexPathsForSelectedItems;
/**
* Selects the item at the specified index path and optionally scrolls it into view.
* If the `allowsSelection` property is NO, calling this method has no effect. If there is an existing selection with a different index path and the `allowsMultipleSelection` property is NO, calling this method replaces the previous selection.
* This method does not cause any selection-related delegate methods to be called.
*
* @param indexPath The index path of the item to select. Specifying nil for this parameter clears the current selection.
*
* @param animated Specify YES to animate the change in the selection or NO to make the change without animating it.
*
* @param scrollPosition An option that specifies where the item should be positioned when scrolling finishes. For a list of possible values, see `UICollectionViewScrollPosition`.
*
* @discussion This method must be called from the main thread.
*/
- (void)selectItemAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition;
/**
* Deselects the item at the specified index.
* If the allowsSelection property is NO, calling this method has no effect.
* This method does not cause any selection-related delegate methods to be called.
*
* @param indexPath The index path of the item to select. Specifying nil for this parameter clears the current selection.
*
* @param animated Specify YES to animate the change in the selection or NO to make the change without animating it.
*
* @discussion This method must be called from the main thread.
*/
- (void)deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated;
#pragma mark - Querying Data
/**
* Retrieves the number of items in the given section.
*
* @param section The section.
*
* @return The number of items.
*/
- (NSInteger)numberOfItemsInSection:(NSInteger)section AS_WARN_UNUSED_RESULT;
/**
* The number of sections.
*/
@property (nonatomic, readonly) NSInteger numberOfSections;
/**
* Similar to -visibleCells.
*
* @return an array containing the nodes being displayed on screen. This must be called on the main thread.
*/
@property (nonatomic, readonly) NSArray<__kindof ASCellNode *> *visibleNodes;
/**
* Retrieves the node for the item at the given index path.
*
* @param indexPath The index path of the requested item.
*
* @return The node for the given item, or @c nil if no item exists at the specified path.
*/
- (nullable __kindof ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT;
/**
* Retrieves the node-model for the item at the given index path, if any.
*
* @param indexPath The index path of the requested item.
*
* @return The node-model for the given item, or @c nil if no item exists at the specified path or no node-model was provided.
*
* @warning This API is beta and subject to change. We'll try to provide an easy migration path.
*/
- (nullable id)nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT;
/**
* Retrieve the index path for the item with the given node.
*
* @param cellNode A node for an item in the collection node.
*
* @return The indexPath for this item.
*/
- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT;
/**
* Retrieve the index paths of all visible items.
*
* @return an array containing the index paths of all visible items. This must be called on the main thread.
*/
@property (nonatomic, readonly) NSArray<NSIndexPath *> *indexPathsForVisibleItems;
/**
* Retrieve the index path of the item at the given point.
*
* @param point The point of the requested item.
*
* @return The indexPath for the item at the given point. This must be called on the main thread.
*/
- (nullable NSIndexPath *)indexPathForItemAtPoint:(CGPoint)point AS_WARN_UNUSED_RESULT;
/**
* Retrieve the cell at the given index path.
*
* @param indexPath The index path of the requested item.
*
* @return The cell for the given index path. This must be called on the main thread.
*/
- (nullable UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath;
/**
* Retrieves the context object for the given section, as provided by the data source in
* the @c collectionNode:contextForSection: method.
*
* @param section The section to get the context for.
*
* @return The context object, or @c nil if no context was provided.
*
* TODO: This method currently accepts @c section in the _view_ index space, but it should
* be in the node index space. To get the context in the view index space (e.g. for subclasses
* of @c UICollectionViewLayout, the user will call the same method on @c ASCollectionView.
*/
- (nullable id<ASSectionContext>)contextForSection:(NSInteger)section AS_WARN_UNUSED_RESULT;
@end
@interface ASCollectionNode (Deprecated)
- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("This method has been renamed to -waitUntilAllUpdatesAreProcessed.");
@end
/**
* This is a node-based UICollectionViewDataSource.
*/
@protocol ASCollectionDataSource <ASCommonCollectionDataSource>
@optional
/**
* Asks the data source for the number of items in the given section of the collection node.
*
* @see @c collectionView:numberOfItemsInSection:
*/
- (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section;
/**
* Asks the data source for the number of sections in the collection node.
*
* @see @c numberOfSectionsInCollectionView:
*/
- (NSInteger)numberOfSectionsInCollectionNode:(ASCollectionNode *)collectionNode;
/**
* --BETA--
* Asks the data source for a view-model for the item at the given index path.
*
* @param collectionNode The sender.
* @param indexPath The index path of the item.
*
* @return An object that contains all the data for this item.
*/
- (nullable id)collectionNode:(ASCollectionNode *)collectionNode nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath;
/**
* Similar to -collectionNode:nodeForItemAtIndexPath:
* This method takes precedence over collectionNode:nodeForItemAtIndexPath: if implemented.
*
* @param collectionNode The sender.
* @param indexPath The index path of the item.
*
* @return a block that creates the node for display for this item.
* Must be thread-safe (can be called on the main thread or a background
* queue) and should not implement reuse (it will be called once per row).
*/
- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath;
/**
* Similar to -collectionView:cellForItemAtIndexPath:.
*
* @param collectionNode The sender.
* @param indexPath The index path of the item.
*
* @return A node to display for the given item. This will be called on the main thread and should
* not implement reuse (it will be called once per item). Unlike UICollectionView's version,
* this method is not called when the item is about to display.
*/
- (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForItemAtIndexPath:(NSIndexPath *)indexPath;
/**
* Asks the data source to provide a node-block to display for the given supplementary element in the collection view.
*
* @param collectionNode The sender.
* @param kind The kind of supplementary element.
* @param indexPath The index path of the supplementary element.
*/
- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
/**
* Asks the data source to provide a node to display for the given supplementary element in the collection view.
*
* @param collectionNode The sender.
* @param kind The kind of supplementary element.
* @param indexPath The index path of the supplementary element.
*/
- (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
/**
* Asks the data source to provide a context object for the given section. This object
* can later be retrieved by calling @c contextForSection: and is useful when implementing
* custom @c UICollectionViewLayout subclasses. The context object is ret
*
* @param collectionNode The sender.
* @param section The index of the section to provide context for.
*
* @return A context object to assign to the given section, or @c nil.
*/
- (nullable id<ASSectionContext>)collectionNode:(ASCollectionNode *)collectionNode contextForSection:(NSInteger)section;
/**
* Asks the data source to provide an array of supplementary element kinds that exist in a given section.
*
* @param collectionNode The sender.
* @param section The index of the section to provide supplementary kinds for.
*
* @return The supplementary element kinds that exist in the given section, if any.
*/
- (NSArray<NSString *> *)collectionNode:(ASCollectionNode *)collectionNode supplementaryElementKindsInSection:(NSInteger)section;
/**
* Asks the data source if it's possible to move the specified item interactively.
*
* See @p -[UICollectionViewDataSource collectionView:canMoveItemAtIndexPath:] @c.
*
* @param collectionNode The sender.
* @param node The display node for the item that may be moved.
*
* @return Whether the item represented by @p node may be moved.
*/
- (BOOL)collectionNode:(ASCollectionNode *)collectionNode canMoveItemWithNode:(ASCellNode *)node;
/**
* Called when the user has interactively moved an item. The data source
* should update its internal data store to reflect the move. Note that you
* should not call [collectionNode moveItemAtIndexPath:toIndexPath:] the
* collection node's internal state will be updated automatically.
*
* * See @p -[UICollectionViewDataSource collectionView:moveItemAtIndexPath:toIndexPath:] @c.
*
* @param collectionNode The sender.
* @param sourceIndexPath The original item index path.
* @param destinationIndexPath The new item index path.
*/
- (void)collectionNode:(ASCollectionNode *)collectionNode moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;
/**
* Generate a unique identifier for an element in a collection. This helps state restoration persist the scroll position
* of a collection view even when the data in that table changes. See the documentation for UIDataSourceModelAssociation for more information.
*
* @param indexPath The index path of the requested node.
*
* @param collectionNode The sender.
*
* @return a unique identifier for the element at the given path. Return nil if the index path does not exist in the collection.
*/
- (nullable NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)indexPath inNode:(ASCollectionNode *)collectionNode;
/**
* Similar to -collectionView:cellForItemAtIndexPath:. See the documentation for UIDataSourceModelAssociation for more information.
*
* @param identifier The model identifier of the element, previously generated by a call to modelIdentifierForElementAtIndexPath
*
* @param collectionNode The sender.
*
* @return the index path to the current position of the matching element in the collection. Return nil if the element is not found.
*/
- (nullable NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inNode:(ASCollectionNode *)collectionNode;
/**
* Similar to -collectionView:cellForItemAtIndexPath:.
*
* @param collectionView The sender.
*
* @param indexPath The index path of the requested node.
*
* @return a node for display at this indexpath. This will be called on the main thread and should
* not implement reuse (it will be called once per row). Unlike UICollectionView's version,
* this method is not called when the row is about to display.
*/
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead.");
/**
* Similar to -collectionView:nodeForItemAtIndexPath:
* This method takes precedence over collectionView:nodeForItemAtIndexPath: if implemented.
*
* @param collectionView The sender.
*
* @param indexPath The index path of the requested node.
*
* @return a block that creates the node for display at this indexpath.
* Must be thread-safe (can be called on the main thread or a background
* queue) and should not implement reuse (it will be called once per row).
*/
- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead.");
/**
* Asks the collection view to provide a supplementary node to display in the collection view.
*
* @param collectionView An object representing the collection view requesting this information.
* @param kind The kind of supplementary node to provide.
* @param indexPath The index path that specifies the location of the new supplementary node.
*/
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead.");
/**
* Indicator to lock the data source for data fetching in async mode.
* We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistency or exception
* due to the data access in async mode.
*
* @param collectionView The sender.
* @deprecated The data source is always accessed on the main thread, and this method will not be called.
*/
- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Data source accesses are on the main thread. Method will not be called.");
/**
* Indicator to unlock the data source for data fetching in async mode.
* We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistency or exception
* due to the data access in async mode.
*
* @param collectionView The sender.
* @deprecated The data source is always accessed on the main thread, and this method will not be called.
*/
- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Data source accesses are on the main thread. Method will not be called.");
@end
/**
* This is a node-based UICollectionViewDelegate.
*/
@protocol ASCollectionDelegate <ASCommonCollectionDelegate, NSObject>
@optional
/**
* Provides the constrained size range for measuring the given item.
*
* @param collectionNode The sender.
*
* @param indexPath The index path of the item.
*
* @return A constrained size range for layout for the item at this index path.
*/
- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode constrainedSizeForItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionNode:(ASCollectionNode *)collectionNode willDisplayItemWithNode:(ASCellNode *)node;
- (void)collectionNode:(ASCollectionNode *)collectionNode didEndDisplayingItemWithNode:(ASCellNode *)node;
- (void)collectionNode:(ASCollectionNode *)collectionNode willDisplaySupplementaryElementWithNode:(ASCellNode *)node NS_AVAILABLE_IOS(8_0);
- (void)collectionNode:(ASCollectionNode *)collectionNode didEndDisplayingSupplementaryElementWithNode:(ASCellNode *)node;
- (BOOL)collectionNode:(ASCollectionNode *)collectionNode shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionNode:(ASCollectionNode *)collectionNode didHighlightItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionNode:(ASCollectionNode *)collectionNode didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath;
- (BOOL)collectionNode:(ASCollectionNode *)collectionNode shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath;
- (BOOL)collectionNode:(ASCollectionNode *)collectionNode shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionNode:(ASCollectionNode *)collectionNode didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionNode:(ASCollectionNode *)collectionNode didDeselectItemAtIndexPath:(NSIndexPath *)indexPath;
- (BOOL)collectionNode:(ASCollectionNode *)collectionNode shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath;
- (BOOL)collectionNode:(ASCollectionNode *)collectionNode canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath sender:(nullable id)sender;
- (void)collectionNode:(ASCollectionNode *)collectionNode performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath sender:(nullable id)sender;
/**
* Receive a message that the collection node is near the end of its data set and more data should be fetched if
* necessary.
*
* @param collectionNode The sender.
* @param context A context object that must be notified when the batch fetch is completed.
*
* @discussion You must eventually call -completeBatchFetching: with an argument of YES in order to receive future
* notifications to do batch fetches. This method is called on a background queue.
*
* ASCollectionNode currently only supports batch events for tail loads. If you require a head load, consider
* implementing a UIRefreshControl.
*/
- (void)collectionNode:(ASCollectionNode *)collectionNode willBeginBatchFetchWithContext:(ASBatchContext *)context;
/**
* Tell the collection node if batch fetching should begin.
*
* @param collectionNode The sender.
*
* @discussion Use this method to conditionally fetch batches. Example use cases are: limiting the total number of
* objects that can be fetched or no network connection.
*
* If not implemented, the collection node assumes that it should notify its asyncDelegate when batch fetching
* should occur.
*/
- (BOOL)shouldBatchFetchForCollectionNode:(ASCollectionNode *)collectionNode;
/**
* Provides the constrained size range for measuring the node at the index path.
*
* @param collectionView The sender.
*
* @param indexPath The index path of the node.
*
* @return A constrained size range for layout the node at this index path.
*/
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's constrainedSizeForItemAtIndexPath: instead. PLEASE NOTE the very subtle method name change.");
/**
* Informs the delegate that the collection view will add the given node
* at the given index path to the view hierarchy.
*
* @param collectionView The sender.
* @param node The node that will be displayed.
* @param indexPath The index path of the item that will be displayed.
*
* @warning AsyncDisplayKit processes collection view edits asynchronously. The index path
* passed into this method may not correspond to the same item in your data source
* if your data source has been updated since the last edit was processed.
*/
- (void)collectionView:(ASCollectionView *)collectionView willDisplayNode:(ASCellNode *)node forItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead.");
/**
* Informs the delegate that the collection view did remove the provided node from the view hierarchy.
* This may be caused by the node scrolling out of view, or by deleting the item
* or its containing section with @c deleteItemsAtIndexPaths: or @c deleteSections: .
*
* @param collectionView The sender.
* @param node The node which was removed from the view hierarchy.
* @param indexPath The index path at which the node was located before it was removed.
*
* @warning AsyncDisplayKit processes collection view edits asynchronously. The index path
* passed into this method may not correspond to the same item in your data source
* if your data source has been updated since the last edit was processed.
*/
- (void)collectionView:(ASCollectionView *)collectionView didEndDisplayingNode:(ASCellNode *)node forItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead.");
- (void)collectionView:(ASCollectionView *)collectionView willBeginBatchFetchWithContext:(ASBatchContext *)context ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead.");
/**
* Tell the collectionView if batch fetching should begin.
*
* @param collectionView The sender.
*
* @discussion Use this method to conditionally fetch batches. Example use cases are: limiting the total number of
* objects that can be fetched or no network connection.
*
* If not implemented, the collectionView assumes that it should notify its asyncDelegate when batch fetching
* should occur.
*/
- (BOOL)shouldBatchFetchForCollectionView:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead.");
/**
* Informs the delegate that the collection view will add the node
* at the given index path to the view hierarchy.
*
* @param collectionView The sender.
* @param indexPath The index path of the item that will be displayed.
*
* @warning AsyncDisplayKit processes collection view edits asynchronously. The index path
* passed into this method may not correspond to the same item in your data source
* if your data source has been updated since the last edit was processed.
*
* This method is deprecated. Use @c collectionView:willDisplayNode:forItemAtIndexPath: instead.
*/
- (void)collectionView:(ASCollectionView *)collectionView willDisplayNodeForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead.");
@end
@protocol ASCollectionDataSourceInterop <ASCollectionDataSource>
/**
* This method offers compatibility with synchronous, standard UICollectionViewCell objects.
* These cells will **not** have the performance benefits of ASCellNodes (like preloading, async layout, and
* async drawing) - even when mixed within the same ASCollectionNode.
*
* In order to use this method, you must:
* 1. Implement it on your ASCollectionDataSource object.
* 2. Call registerCellClass: on the collectionNode.view (in viewDidLoad, or register an onDidLoad: block).
* 3. Return nil from the nodeBlockForItem...: or nodeForItem...: method. NOTE: it is an error to return
* nil from within a nodeBlock, if you have returned a nodeBlock object.
* 4. Lastly, you must implement a method to provide the size for the cell. There are two ways this is done:
* 4a. UICollectionViewFlowLayout (incl. ASPagerNode). Implement
collectionNode:constrainedSizeForItemAtIndexPath:.
* 4b. Custom collection layouts. Set .layoutInspector and have it implement
collectionView:constrainedSizeForNodeAtIndexPath:.
*
* For an example of using this method with all steps above (including a custom layout, 4b.),
* see the app in examples/CustomCollectionView and enable kShowUICollectionViewCells = YES.
*/
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;
@optional
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
/**
* Implement this property and return YES if you want your interop data source to be
* used when dequeuing cells for node-backed items.
*
* If NO (the default), the interop data source will only be consulted in cases
* where no ASCellNode was provided to AsyncDisplayKit.
*
* If YES, the interop data source will always be consulted to dequeue cells, and
* will be expected to return _ASCollectionViewCells in cases where a node was provided.
*
* The default value is NO.
*/
@property (class, nonatomic, readonly) BOOL dequeuesCellsForNodeBackedItems;
@end
@protocol ASCollectionDelegateInterop <ASCollectionDelegate>
@optional
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
@end
NS_ASSUME_NONNULL_END
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,496 @@
//
// ASCollectionView.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#ifndef MINIMAL_ASDK
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASCollectionViewProtocols.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASBatchContext.h>
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASLayoutRangeType.h>
#import <AsyncDisplayKit/ASScrollDirection.h>
@class ASCellNode;
@class ASCollectionNode;
@protocol ASCollectionDataSource;
@protocol ASCollectionDelegate;
@protocol ASCollectionViewLayoutInspecting;
@protocol ASSectionContext;
NS_ASSUME_NONNULL_BEGIN
/**
* Asynchronous UICollectionView with Intelligent Preloading capabilities.
*
* @note ASCollectionNode is strongly recommended over ASCollectionView. This class exists for adoption convenience.
*/
@interface ASCollectionView : UICollectionView
/**
* Returns the corresponding ASCollectionNode
*
* @return collectionNode The corresponding ASCollectionNode, if one exists.
*/
@property (nonatomic, weak, readonly) ASCollectionNode *collectionNode;
/**
* Retrieves the node for the item at the given index path.
*
* @param indexPath The index path of the requested node.
* @return The node at the given index path, or @c nil if no item exists at the specified path.
*/
- (nullable ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT;
/**
* Similar to -indexPathForCell:.
*
* @param cellNode a cellNode in the collection view
*
* @return The index path for this cell node.
*
* @discussion This index path returned by this method is in the _view's_ index space
* and should only be used with @c ASCollectionView directly. To get an index path suitable
* for use with your data source and @c ASCollectionNode, call @c indexPathForNode: on the
* collection node instead.
*/
- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT;
/**
* Similar to -supplementaryViewForElementKind:atIndexPath:
*
* @param elementKind The kind of supplementary node to locate.
* @param indexPath The index path of the requested supplementary node.
*
* @return The specified supplementary node or @c nil.
*/
- (nullable ASCellNode *)supplementaryNodeForElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT;
/**
* Retrieves the context object for the given section, as provided by the data source in
* the @c collectionNode:contextForSection: method. This method must be called on the main thread.
*
* @param section The section to get the context for.
*
* @return The context object, or @c nil if no context was provided.
*/
- (nullable id<ASSectionContext>)contextForSection:(NSInteger)section AS_WARN_UNUSED_RESULT;
@end
@interface ASCollectionView (Deprecated)
/*
* A Boolean value that determines whether the nodes that the data source renders will be flipped.
*/
@property (nonatomic) BOOL inverted ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
/**
* The number of screens left to scroll before the delegate -collectionView:beginBatchFetchingWithContext: is called.
*
* Defaults to two screenfuls.
*/
@property (nonatomic) CGFloat leadingScreensForBatching ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
/**
* Optional introspection object for the collection view's layout.
*
* @discussion Since supplementary and decoration views are controlled by the collection view's layout, this object
* is used as a bridge to provide information to the internal data controller about the existence of these views and
* their associated index paths. For collection views using `UICollectionViewFlowLayout`, a default inspector
* implementation `ASCollectionViewFlowLayoutInspector` is created and set on this property by default. Custom
* collection view layout subclasses will need to provide their own implementation of an inspector object for their
* supplementary views to be compatible with `ASCollectionView`'s supplementary node support.
*/
@property (nonatomic, weak) id<ASCollectionViewLayoutInspecting> layoutInspector ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
/**
* Determines collection view's current scroll direction. Supports 2-axis collection views.
*
* @return a bitmask of ASScrollDirection values.
*/
@property (nonatomic, readonly) ASScrollDirection scrollDirection ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
/**
* Determines collection view's scrollable directions.
*
* @return a bitmask of ASScrollDirection values.
*/
@property (nonatomic, readonly) ASScrollDirection scrollableDirections ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
/**
* Forces the .contentInset to be UIEdgeInsetsZero.
*
* @discussion By default, UIKit sets the top inset to the navigation bar height, even for horizontally
* scrolling views. This can only be disabled by setting a property on the containing UIViewController,
* automaticallyAdjustsScrollViewInsets, which may not be accessible. ASPagerNode uses this to ensure
* its flow layout behaves predictably and does not log undefined layout warnings.
*/
@property (nonatomic) BOOL zeroContentInsets ASDISPLAYNODE_DEPRECATED_MSG("Set automaticallyAdjustsScrollViewInsets=NO on your view controller instead.");
/**
* The distance that the content view is inset from the collection view edges. Defaults to UIEdgeInsetsZero.
*/
@property (nonatomic) UIEdgeInsets contentInset ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead");
/**
* The point at which the origin of the content view is offset from the origin of the collection view.
*/
@property (nonatomic) CGPoint contentOffset ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
/**
* The object that acts as the asynchronous delegate of the collection view
*
* @discussion The delegate must adopt the ASCollectionDelegate protocol. The collection view maintains a weak reference to the delegate object.
*
* The delegate object is responsible for providing size constraints for nodes and indicating whether batch fetching should begin.
*/
@property (nonatomic, weak) id<ASCollectionDelegate> asyncDelegate ASDISPLAYNODE_DEPRECATED_MSG("Please use ASCollectionNode's .delegate property instead.");
/**
* The object that acts as the asynchronous data source of the collection view
*
* @discussion The datasource must adopt the ASCollectionDataSource protocol. The collection view maintains a weak reference to the datasource object.
*
* The datasource object is responsible for providing nodes or node creation blocks to the collection view.
*/
@property (nonatomic, weak) id<ASCollectionDataSource> asyncDataSource ASDISPLAYNODE_DEPRECATED_MSG("Please use ASCollectionNode's .dataSource property instead.");
/**
* Initializes an ASCollectionView
*
* @discussion Initializes and returns a newly allocated collection view object with the specified layout.
*
* @param layout The layout object to use for organizing items. The collection view stores a strong reference to the specified object. Must not be nil.
*/
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout ASDISPLAYNODE_DEPRECATED_MSG("Please use ASCollectionNode instead of ASCollectionView.");
/**
* Initializes an ASCollectionView
*
* @discussion Initializes and returns a newly allocated collection view object with the specified frame and layout.
*
* @param frame The frame rectangle for the collection view, measured in points. The origin of the frame is relative to the superview in which you plan to add it. This frame is passed to the superclass during initialization.
* @param layout The layout object to use for organizing items. The collection view stores a strong reference to the specified object. Must not be nil.
*/
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout ASDISPLAYNODE_DEPRECATED_MSG("Please use ASCollectionNode instead of ASCollectionView.");
/**
* Tuning parameters for a range type in full mode.
*
* @param rangeType The range type to get the tuning parameters for.
*
* @return A tuning parameter value for the given range type in full mode.
*
* @see ASLayoutRangeMode
* @see ASLayoutRangeType
*/
- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
/**
* Set the tuning parameters for a range type in full mode.
*
* @param tuningParameters The tuning parameters to store for a range type.
* @param rangeType The range type to set the tuning parameters for.
*
* @see ASLayoutRangeMode
* @see ASLayoutRangeType
*/
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
/**
* Tuning parameters for a range type in the specified mode.
*
* @param rangeMode The range mode to get the running parameters for.
* @param rangeType The range type to get the tuning parameters for.
*
* @return A tuning parameter value for the given range type in the given mode.
*
* @see ASLayoutRangeMode
* @see ASLayoutRangeType
*/
- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
/**
* Set the tuning parameters for a range type in the specified mode.
*
* @param tuningParameters The tuning parameters to store for a range type.
* @param rangeMode The range mode to set the running parameters for.
* @param rangeType The range type to set the tuning parameters for.
*
* @see ASLayoutRangeMode
* @see ASLayoutRangeType
*/
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
- (nullable __kindof UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
- (void)selectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
@property (nonatomic, copy, readonly) NSArray<NSIndexPath *> *indexPathsForVisibleItems ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
@property (nullable, nonatomic, copy, readonly) NSArray<NSIndexPath *> *indexPathsForSelectedItems ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
/**
* Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread.
* The asyncDataSource must be updated to reflect the changes before the update block completes.
*
* @param animated NO to disable animations for this batch
* @param updates The block that performs the relevant insert, delete, reload, or move operations.
* @param completion A completion handler block to execute when all of the operations are finished. This block takes a single
* Boolean parameter that contains the value YES if all of the related animations completed successfully or
* NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread.
*/
- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
/**
* Perform a batch of updates asynchronously. This method must be called from the main thread.
* The asyncDataSource must be updated to reflect the changes before update block completes.
*
* @param updates The block that performs the relevant insert, delete, reload, or move operations.
* @param completion A completion handler block to execute when all of the operations are finished. This block takes a single
* Boolean parameter that contains the value YES if all of the related animations completed successfully or
* NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread.
*/
- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
/**
* Reload everything from scratch, destroying the working range and all cached nodes.
*
* @param completion block to run on completion of asynchronous loading or nil. If supplied, the block is run on
* the main thread.
* @warning This method is substantially more expensive than UICollectionView's version.
*/
- (void)reloadDataWithCompletion:(nullable void (^)(void))completion AS_UNAVAILABLE("Use ASCollectionNode method instead.");
/**
* Reload everything from scratch, destroying the working range and all cached nodes.
*
* @warning This method is substantially more expensive than UICollectionView's version.
*/
- (void)reloadData AS_UNAVAILABLE("Use ASCollectionNode method instead.");
/**
* Triggers a relayout of all nodes.
*
* @discussion This method invalidates and lays out every cell node in the collection.
*/
- (void)relayoutItems ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
/**
* See ASCollectionNode.h for full documentation of these methods.
*/
@property (nonatomic, readonly) BOOL isProcessingUpdates;
- (void)onDidFinishProcessingUpdates:(void (^)(void))completion;
- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use -[ASCollectionNode waitUntilAllUpdatesAreProcessed] instead.");
/**
* See ASCollectionNode.h for full documentation of these methods.
*/
@property (nonatomic, readonly, getter=isSynchronized) BOOL synchronized;
- (void)onDidFinishSynchronizing:(void (^)(void))completion;
/**
* Registers the given kind of supplementary node for use in creating node-backed supplementary views.
*
* @param elementKind The kind of supplementary node that will be requested through the data source.
*
* @discussion Use this method to register support for the use of supplementary nodes in place of the default
* `registerClass:forSupplementaryViewOfKind:withReuseIdentifier:` and `registerNib:forSupplementaryViewOfKind:withReuseIdentifier:`
* methods. This method will register an internal backing view that will host the contents of the supplementary nodes
* returned from the data source.
*/
- (void)registerSupplementaryNodeOfKind:(NSString *)elementKind ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
/**
* Inserts one or more sections.
*
* @param sections An index set that specifies the sections to insert.
*
* @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes
* before this method is called.
*/
- (void)insertSections:(NSIndexSet *)sections ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
/**
* Deletes one or more sections.
*
* @param sections An index set that specifies the sections to delete.
*
* @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes
* before this method is called.
*/
- (void)deleteSections:(NSIndexSet *)sections ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
/**
* Reloads the specified sections.
*
* @param sections An index set that specifies the sections to reload.
*
* @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes
* before this method is called.
*/
- (void)reloadSections:(NSIndexSet *)sections ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
/**
* Moves a section to a new location.
*
* @param section The index of the section to move.
*
* @param newSection The index that is the destination of the move for the section.
*
* @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes
* before this method is called.
*/
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
/**
* Inserts items at the locations identified by an array of index paths.
*
* @param indexPaths An array of NSIndexPath objects, each representing an item index and section index that together identify an item.
*
* @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes
* before this method is called.
*/
- (void)insertItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
/**
* Deletes the items specified by an array of index paths.
*
* @param indexPaths An array of NSIndexPath objects identifying the items to delete.
*
* @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes
* before this method is called.
*/
- (void)deleteItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
/**
* Reloads the specified items.
*
* @param indexPaths An array of NSIndexPath objects identifying the items to reload.
*
* @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes
* before this method is called.
*/
- (void)reloadItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
/**
* Moves the item at a specified location to a destination location.
*
* @param indexPath The index path identifying the item to move.
*
* @param newIndexPath The index path that is the destination of the move for the item.
*
* @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes
* before this method is called.
*/
- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
/**
* Query the sized node at @c indexPath for its calculatedSize.
*
* @param indexPath The index path for the node of interest.
*
* This method is deprecated. Call @c calculatedSize on the node of interest instead. First deprecated in version 2.0.
*/
- (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Call -calculatedSize on the node of interest instead.");
/**
* Similar to -visibleCells.
*
* @return an array containing the nodes being displayed on screen.
*/
- (NSArray<__kindof ASCellNode *> *)visibleNodes AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
@end
ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDataSource.")
@protocol ASCollectionViewDataSource <ASCollectionDataSource>
@end
ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDelegate.")
@protocol ASCollectionViewDelegate <ASCollectionDelegate>
@end
/**
* Defines methods that let you coordinate a `UICollectionViewFlowLayout` in combination with an `ASCollectionNode`.
*/
@protocol ASCollectionDelegateFlowLayout <ASCollectionDelegate>
@optional
/**
* Asks the delegate for the inset that should be applied to the given section.
*
* @see the same method in UICollectionViewDelegate.
*/
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
/**
* Asks the delegate for the size range that should be used to measure the header in the given flow layout section.
*
* @param collectionNode The sender.
* @param section The section.
*
* @return The size range for the header, or @c ASSizeRangeZero if there is no header in this section.
*
* If you want the header to completely determine its own size, return @c ASSizeRangeUnconstrained.
*
* @note Only the scrollable dimension of the returned size range will be used. In a vertical flow,
* only the height will be used. In a horizontal flow, only the width will be used. The other dimension
* will be constrained to fill the collection node.
*
* @discussion If you do not implement this method, ASDK will fall back to calling @c collectionView:layout:referenceSizeForHeaderInSection:
* and using that as the exact constrained size. If you don't implement that method, ASDK will read the @c headerReferenceSize from the layout.
*/
- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForHeaderInSection:(NSInteger)section;
/**
* Asks the delegate for the size range that should be used to measure the footer in the given flow layout section.
*
* @param collectionNode The sender.
* @param section The section.
*
* @return The size range for the footer, or @c ASSizeRangeZero if there is no footer in this section.
*
* If you want the footer to completely determine its own size, return @c ASSizeRangeUnconstrained.
*
* @note Only the scrollable dimension of the returned size range will be used. In a vertical flow,
* only the height will be used. In a horizontal flow, only the width will be used. The other dimension
* will be constrained to fill the collection node.
*
* @discussion If you do not implement this method, ASDK will fall back to calling @c collectionView:layout:referenceSizeForFooterInSection:
* and using that as the exact constrained size. If you don't implement that method, ASDK will read the @c footerReferenceSize from the layout.
*/
- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForFooterInSection:(NSInteger)section;
/**
* Asks the delegate for the size of the header in the specified section.
*/
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:sizeRangeForHeaderInSection: instead.");
/**
* Asks the delegate for the size of the footer in the specified section.
*/
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:sizeRangeForFooterInSection: instead.");
@end
ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDelegateFlowLayout.")
@protocol ASCollectionViewDelegateFlowLayout <ASCollectionDelegateFlowLayout>
@end
NS_ASSUME_NONNULL_END
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
//
// ASCollectionViewLayoutFacilitatorProtocol.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#ifndef MINIMAL_ASDK
#pragma once
#import <Foundation/Foundation.h>
/**
* This facilitator protocol is intended to help Layout to better
* gel with the CollectionView
*/
@protocol ASCollectionViewLayoutFacilitatorProtocol <NSObject>
/**
* Inform that the collectionView is editing the cells at a list of indexPaths
*
* @param indexPaths an array of NSIndexPath objects of cells being/will be edited.
* @param isBatched indicates whether the editing operation will be batched by the collectionView
*
* NOTE: when isBatched, used in combination with -collectionViewWillPerformBatchUpdates
*/
- (void)collectionViewWillEditCellsAtIndexPaths:(NSArray *)indexPaths batched:(BOOL)isBatched;
/**
* Inform that the collectionView is editing the sections at a set of indexes
*
* @param indexes an NSIndexSet of section indexes being/will be edited.
* @param batched indicates whether the editing operation will be batched by the collectionView
*
* NOTE: when batched, used in combination with -collectionViewWillPerformBatchUpdates
*/
- (void)collectionViewWillEditSectionsAtIndexSet:(NSIndexSet *)indexes batched:(BOOL)batched;
/**
* Informs the delegate that the collectionView is about to call performBatchUpdates
*/
- (void)collectionViewWillPerformBatchUpdates;
@end
#endif

View File

@ -0,0 +1,103 @@
//
// ASCollectionViewProtocols.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#ifndef MINIMAL_ASDK
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
typedef NS_OPTIONS(NSUInteger, ASCellLayoutMode) {
/**
* No options set. If cell layout mode is set to ASCellLayoutModeNone, the default values for
* each flag listed below is used.
*/
ASCellLayoutModeNone = 0,
/**
* If ASCellLayoutModeAlwaysSync is enabled it will cause the ASDataController to wait on the
* background queue, and this ensures that any new / changed cells are in the hierarchy by the
* very next CATransaction / frame draw.
*
* Note: Sync & Async flags force the behavior to be always one or the other, regardless of the
* items. Default: If neither ASCellLayoutModeAlwaysSync or ASCellLayoutModeAlwaysAsync is set,
* default behavior is synchronous when there are 0 or 1 ASCellNodes in the data source, and
* asynchronous when there are 2 or more.
*/
ASCellLayoutModeAlwaysSync = 1 << 1, // Default OFF
ASCellLayoutModeAlwaysAsync = 1 << 2, // Default OFF
ASCellLayoutModeForceIfNeeded = 1 << 3, // Deprecated, default OFF.
ASCellLayoutModeAlwaysPassthroughDelegate = 1 << 4, // Deprecated, default ON.
/** Instead of using performBatchUpdates: prefer using reloadData for changes for collection view */
ASCellLayoutModeAlwaysReloadData = 1 << 5, // Default OFF
/** If flag is enabled nodes are *not* gonna be range managed. */
ASCellLayoutModeDisableRangeController = 1 << 6, // Default OFF
ASCellLayoutModeAlwaysLazy = 1 << 7, // Deprecated, default OFF.
/**
* Defines if the node creation should happen serialized and not in parallel within the
* data controller
*/
ASCellLayoutModeSerializeNodeCreation = 1 << 8, // Default OFF
/**
* When set, the performBatchUpdates: API (including animation) is used when handling Section
* Reload operations. This is useful only when ASCellLayoutModeAlwaysReloadData is enabled and
* cell height animations are desired.
*/
ASCellLayoutModeAlwaysBatchUpdateSectionReload = 1 << 9, // Default OFF
};
NS_ASSUME_NONNULL_BEGIN
/**
* This is a subset of UICollectionViewDataSource.
*
* @see ASCollectionDataSource
*/
@protocol ASCommonCollectionDataSource <NSObject>
@optional
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("Implement -collectionNode:numberOfItemsInSection: instead.");
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Implement -numberOfSectionsInCollectionNode: instead.");
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement - collectionNode:nodeForSupplementaryElementOfKind:atIndexPath: instead.");
@end
/**
* This is a subset of UICollectionViewDelegate.
*
* @see ASCollectionDelegate
*/
@protocol ASCommonCollectionDelegate <NSObject, UIScrollViewDelegate>
@optional
- (UICollectionViewTransitionLayout *)collectionView:(UICollectionView *)collectionView transitionLayoutForOldLayout:(UICollectionViewLayout *)fromLayout newLayout:(UICollectionViewLayout *)toLayout;
- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -collectionNode:willDisplaySupplementaryView:forElementKind:atIndexPath: instead.");
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -collectionNode:didEndDisplayingSupplementaryView:forElementKind:atIndexPath: instead.");
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:shouldHighlightItemAtIndexPath: instead.");
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:didHighlightItemAtIndexPath: instead.");
- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:didUnhighlightItemAtIndexPath: instead.");
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:shouldSelectItemAtIndexPath: instead.");
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:didSelectItemAtIndexPath: instead.");
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:shouldDeselectItemAtIndexPath: instead.");
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:didDeselectItemAtIndexPath: instead.");
- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:shouldShowMenuForItemAtIndexPath: instead.");
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:canPerformAction:forItemAtIndexPath:withSender: instead.");
- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:performAction:forItemAtIndexPath:withSender: instead.");
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,38 @@
//
// ASCollections.h
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSArray<__covariant ObjectType> (ASCollections)
/**
* Create an immutable NSArray from a C-array of strong pointers.
*
* Note: The memory for the array you pass in will be zero'd (to prevent ARC from releasing
* the references when the array goes out of scope.)
*
* Can be combined with vector like:
* vector<NSString *> vec;
* vec.push_back(@"foo");
* vec.push_back(@"bar");
* NSArray *arr = [NSArray arrayTransferring:vec.data() count:vec.size()]
* ** vec is now { nil, nil } **
*
* Unfortunately making a convenience method to do this is currently impossible because
* vector<NSString *> can't be converted to vector<id> by the compiler (silly).
*
* See the private __CFArrayCreateTransfer function.
*/
+ (NSArray<ObjectType> *)arrayByTransferring:(ObjectType _Nonnull __strong * _Nonnull)pointers
count:(NSUInteger)count NS_RETURNS_RETAINED;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,61 @@
//
// ASCollections.mm
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASCollections.h>
/**
* A private allocator that signals to our retain callback to skip the retain.
* It behaves the same as the default allocator, but acts as a signal that we
* are creating a transfer array so we should skip the retain.
*/
static CFAllocatorRef gTransferAllocator;
static const void *ASTransferRetain(CFAllocatorRef allocator, const void *val) {
if (allocator == gTransferAllocator) {
// Transfer allocator. Ignore retain and pass through.
return val;
} else {
// Other allocator. Retain like normal.
// This happens when they make a mutable copy.
return (&kCFTypeArrayCallBacks)->retain(allocator, val);
}
}
@implementation NSArray (ASCollections)
+ (NSArray *)arrayByTransferring:(__strong id *)pointers count:(NSUInteger)count NS_RETURNS_RETAINED
{
// Custom callbacks that point to our ASTransferRetain callback.
static CFArrayCallBacks callbacks;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
callbacks = kCFTypeArrayCallBacks;
callbacks.retain = ASTransferRetain;
CFAllocatorContext ctx;
CFAllocatorGetContext(NULL, &ctx);
gTransferAllocator = CFAllocatorCreate(NULL, &ctx);
});
// NSZeroArray fast path.
if (count == 0) {
return @[]; // Does not actually call +array when optimized.
}
// NSSingleObjectArray fast path. Retain/release here is worth it.
if (count == 1) {
NSArray *result = [[NSArray alloc] initWithObjects:pointers count:1];
pointers[0] = nil;
return result;
}
NSArray *result = (__bridge_transfer NSArray *)CFArrayCreate(gTransferAllocator, (const void **)(void *)pointers, count, &callbacks);
memset(pointers, 0, count * sizeof(id));
return result;
}
@end

View File

@ -0,0 +1,58 @@
//
// ASConfiguration.h
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASExperimentalFeatures.h>
@protocol ASConfigurationDelegate;
NS_ASSUME_NONNULL_BEGIN
static NSInteger const ASConfigurationSchemaCurrentVersion = 1;
AS_SUBCLASSING_RESTRICTED
@interface ASConfiguration : NSObject <NSCopying>
/**
* Initialize this configuration with the provided dictionary,
* or nil to create an empty configuration.
*
* The schema is located in `schemas/configuration.json`.
*/
- (instancetype)initWithDictionary:(nullable NSDictionary *)dictionary;
/**
* The delegate for configuration-related events.
* Delegate methods are called from a serial queue.
*/
@property (nonatomic, nullable) id<ASConfigurationDelegate> delegate;
/**
* The experimental features to enable in Texture.
* See ASExperimentalFeatures for functions to convert to/from a string array.
*/
@property (nonatomic) ASExperimentalFeatures experimentalFeatures;
@end
/**
* Implement this method in a category to make your
* configuration available to Texture. It will be read
* only once and copied.
*
* NOTE: To specify your configuration at compile-time, you can
* define AS_FIXED_CONFIG_JSON as a C-string of JSON. This method
* will then be implemented to parse that string and generate
* a configuration.
*/
@interface ASConfiguration (UserProvided)
+ (ASConfiguration *)textureConfiguration NS_RETURNS_RETAINED;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,64 @@
//
// ASConfiguration.mm
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASConfiguration.h>
#import <AsyncDisplayKit/ASConfigurationInternal.h>
/// Not too performance-sensitive here.
@implementation ASConfiguration
- (instancetype)initWithDictionary:(NSDictionary *)dictionary
{
if (self = [super init]) {
if (dictionary != nil) {
const auto featureStrings = ASDynamicCast(dictionary[@"experimental_features"], NSArray);
const auto version = ASDynamicCast(dictionary[@"version"], NSNumber).integerValue;
if (version != ASConfigurationSchemaCurrentVersion) {
NSLog(@"Texture warning: configuration schema is old version (%ld vs %ld)", (long)version, (long)ASConfigurationSchemaCurrentVersion);
}
self.experimentalFeatures = ASExperimentalFeaturesFromArray(featureStrings);
} else {
self.experimentalFeatures = kNilOptions;
}
}
return self;
}
- (id)copyWithZone:(NSZone *)zone
{
ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil];
config.experimentalFeatures = self.experimentalFeatures;
config.delegate = self.delegate;
return config;
}
@end
//#define AS_FIXED_CONFIG_JSON "{ \"version\" : 1, \"experimental_features\": [ \"exp_text_node\" ] }"
#ifdef AS_FIXED_CONFIG_JSON
@implementation ASConfiguration (UserProvided)
+ (ASConfiguration *)textureConfiguration NS_RETURNS_RETAINED
{
NSData *data = [@AS_FIXED_CONFIG_JSON dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *d = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
if (!d) {
NSAssert(NO, @"Error parsing fixed config string '%s': %@", AS_FIXED_CONFIG_JSON, error);
return nil;
} else {
return [[ASConfiguration alloc] initWithDictionary:d];
}
}
@end
#endif // AS_FIXED_CONFIG_JSON

View File

@ -0,0 +1,37 @@
//
// ASConfigurationDelegate.h
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASConfiguration.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Used to communicate configuration-related events to the client.
*/
@protocol ASConfigurationDelegate <NSObject>
/**
* Texture performed its first behavior related to the feature(s).
* This can be useful for tracking the impact of the behavior (A/B testing).
*/
- (void)textureDidActivateExperimentalFeatures:(ASExperimentalFeatures)features;
@optional
/**
* Texture framework initialized. This method is called synchronously
* on the main thread from ASInitializeFrameworkMainThread if you defined
* AS_INITIALIZE_FRAMEWORK_MANUALLY or from the default initialization point
* (currently +load) otherwise.
*/
- (void)textureDidInitialize;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,57 @@
//
// ASConfigurationInternal.h
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
/// Note this has to be public because it's imported by public header ASThread.h =/
/// It will be private again after exp_unfair_lock ends.
#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASConfiguration.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Quickly check if an experiment is enabled and notify the delegate
* that it's been activated.
*
* The delegate will be notified asynchronously.
*/
#if DEBUG
#define ASActivateExperimentalFeature(opt) _ASActivateExperimentalFeature(opt)
#else
#define ASActivateExperimentalFeature(opt) ({\
static BOOL result;\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{ result = _ASActivateExperimentalFeature(opt); });\
result;\
})
#endif
/**
* Internal function. Use the macro without the underbar.
*/
AS_EXTERN BOOL _ASActivateExperimentalFeature(ASExperimentalFeatures option);
/**
* Notify the configuration delegate that the framework initialized, if needed.
*/
AS_EXTERN void ASNotifyInitialized(void);
AS_SUBCLASSING_RESTRICTED
@interface ASConfigurationManager : NSObject
/**
* No API for now.
* Just use ASActivateExperimentalFeature to access this efficiently.
*/
/* Exposed for testing purposes only */
+ (void)test_resetWithConfiguration:(nullable ASConfiguration *)configuration;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,111 @@
//
// ASConfigurationInternal.mm
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import "ASConfigurationInternal.h"
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASConfiguration.h>
#import <AsyncDisplayKit/ASConfigurationDelegate.h>
#import <stdatomic.h>
static ASConfigurationManager *ASSharedConfigurationManager;
static dispatch_once_t ASSharedConfigurationManagerOnceToken;
NS_INLINE ASConfigurationManager *ASConfigurationManagerGet() {
dispatch_once(&ASSharedConfigurationManagerOnceToken, ^{
ASSharedConfigurationManager = [[ASConfigurationManager alloc] init];
});
return ASSharedConfigurationManager;
}
@implementation ASConfigurationManager {
ASConfiguration *_config;
dispatch_queue_t _delegateQueue;
BOOL _frameworkInitialized;
_Atomic(ASExperimentalFeatures) _activatedExperiments;
}
+ (ASConfiguration *)defaultConfiguration NS_RETURNS_RETAINED
{
ASConfiguration *config = [[ASConfiguration alloc] init];
// TODO(wsdwsd0829): Fix #788 before enabling it.
// config.experimentalFeatures = ASExperimentalInterfaceStateCoalescing;
return config;
}
- (instancetype)init
{
if (self = [super init]) {
_delegateQueue = dispatch_queue_create("org.TextureGroup.Texture.ConfigNotifyQueue", DISPATCH_QUEUE_SERIAL);
if ([ASConfiguration respondsToSelector:@selector(textureConfiguration)]) {
_config = [[ASConfiguration textureConfiguration] copy];
} else {
_config = [ASConfigurationManager defaultConfiguration];
}
}
return self;
}
- (void)frameworkDidInitialize
{
ASDisplayNodeAssertMainThread();
if (_frameworkInitialized) {
ASDisplayNodeFailAssert(@"Framework initialized twice.");
return;
}
_frameworkInitialized = YES;
const auto delegate = _config.delegate;
if ([delegate respondsToSelector:@selector(textureDidInitialize)]) {
[delegate textureDidInitialize];
}
}
- (BOOL)activateExperimentalFeature:(ASExperimentalFeatures)requested
{
if (_config == nil) {
return NO;
}
NSAssert(__builtin_popcountl(requested) == 1, @"Cannot activate multiple features at once with this method.");
// We need to call out, whether it's enabled or not.
// A/B testing requires even "control" users to be activated.
ASExperimentalFeatures enabled = requested & _config.experimentalFeatures;
ASExperimentalFeatures prevTriggered = atomic_fetch_or(&_activatedExperiments, requested);
ASExperimentalFeatures newlyTriggered = requested & ~prevTriggered;
// Notify delegate if needed.
if (newlyTriggered != 0) {
__unsafe_unretained id<ASConfigurationDelegate> del = _config.delegate;
dispatch_async(_delegateQueue, ^{
[del textureDidActivateExperimentalFeatures:newlyTriggered];
});
}
return (enabled != 0);
}
// Define this even when !DEBUG, since we may run our tests in release mode.
+ (void)test_resetWithConfiguration:(ASConfiguration *)configuration
{
ASConfigurationManager *inst = ASConfigurationManagerGet();
inst->_config = configuration ?: [self defaultConfiguration];
atomic_store(&inst->_activatedExperiments, 0);
}
@end
BOOL _ASActivateExperimentalFeature(ASExperimentalFeatures feature)
{
return [ASConfigurationManagerGet() activateExperimentalFeature:feature];
}
void ASNotifyInitialized()
{
[ASConfigurationManagerGet() frameworkDidInitialize];
}

View File

@ -0,0 +1,72 @@
//
// ASContextTransitioning.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASDimension.h>
@class ASDisplayNode;
@class ASLayout;
NS_ASSUME_NONNULL_BEGIN
AS_EXTERN NSString * const ASTransitionContextFromLayoutKey;
AS_EXTERN NSString * const ASTransitionContextToLayoutKey;
@protocol ASContextTransitioning <NSObject>
/**
@abstract Defines if the given transition is animated
*/
- (BOOL)isAnimated;
/**
* @abstract Retrieve either the "from" or "to" layout
*/
- (nullable ASLayout *)layoutForKey:(NSString *)key;
/**
* @abstract Retrieve either the "from" or "to" constrainedSize
*/
- (ASSizeRange)constrainedSizeForKey:(NSString *)key;
/**
* @abstract Retrieve the subnodes from either the "from" or "to" layout
*/
- (NSArray<ASDisplayNode *> *)subnodesForKey:(NSString *)key;
/**
* @abstract Subnodes that have been inserted in the layout transition
*/
- (NSArray<ASDisplayNode *> *)insertedSubnodes;
/**
* @abstract Subnodes that will be removed in the layout transition
*/
- (NSArray<ASDisplayNode *> *)removedSubnodes;
/**
@abstract The frame for the given node before the transition began.
@discussion Returns CGRectNull if the node was not in the hierarchy before the transition.
*/
- (CGRect)initialFrameForNode:(ASDisplayNode *)node;
/**
@abstract The frame for the given node when the transition completes.
@discussion Returns CGRectNull if the node is no longer in the hierarchy after the transition.
*/
- (CGRect)finalFrameForNode:(ASDisplayNode *)node;
/**
@abstract Invoke this method when the transition is completed in `animateLayoutTransition:`
@discussion Passing NO to `didComplete` will set the original layout as the new layout.
*/
- (void)completeTransition:(BOOL)didComplete;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,68 @@
//
// ASControlNode+Subclasses.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASControlNode.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
* The subclass header _ASControlNode+Subclasses_ defines methods to be
* overridden by custom nodes that subclass ASControlNode.
*
* These methods should never be called directly by other classes.
*/
@interface ASControlNode (Subclassing)
/**
@abstract Sends action messages for the given control events.
@param controlEvents A bitmask whose set flags specify the control events for which action messages are sent. See "Control Events" in ASControlNode.h for bitmask constants.
@param touchEvent An event object encapsulating the information specific to the user event.
@discussion ASControlNode implements this method to send all action messages associated with controlEvents. The list of targets is constructed from prior invocations of addTarget:action:forControlEvents:.
*/
- (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(nullable UIEvent *)touchEvent;
/**
@abstract Sent to the control when tracking begins.
@param touch The touch on the receiving control.
@param touchEvent An event object encapsulating the information specific to the user event.
@result YES if the receiver should respond continuously (respond when touch is dragged); NO otherwise.
*/
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)touchEvent;
/**
@abstract Sent continuously to the control as it tracks a touch within the control's bounds.
@param touch The touch on the receiving control.
@param touchEvent An event object encapsulating the information specific to the user event.
@result YES if touch tracking should continue; NO otherwise.
*/
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)touchEvent;
/**
@abstract Sent to the control when tracking should be cancelled.
@param touchEvent An event object encapsulating the information specific to the user event. This parameter may be nil, indicating that the cancelation was caused by something other than an event, such as the display node being removed from its supernode.
*/
- (void)cancelTrackingWithEvent:(nullable UIEvent *)touchEvent;
/**
@abstract Sent to the control when the last touch completely ends, telling it to stop tracking.
@param touch The touch that ended.
@param touchEvent An event object encapsulating the information specific to the user event.
*/
- (void)endTrackingWithTouch:(nullable UITouch *)touch withEvent:(nullable UIEvent *)touchEvent;
/**
@abstract Settable version of highlighted property.
*/
@property (getter=isHighlighted) BOOL highlighted;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,149 @@
//
// ASControlNode.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASDisplayNode.h>
#pragma once
NS_ASSUME_NONNULL_BEGIN
/**
@abstract Kinds of events possible for control nodes.
@discussion These events are identical to their UIControl counterparts.
*/
typedef NS_OPTIONS(NSUInteger, ASControlNodeEvent)
{
/** A touch-down event in the control node. */
ASControlNodeEventTouchDown = 1 << 0,
/** A repeated touch-down event in the control node; for this event the value of the UITouch tapCount method is greater than one. */
ASControlNodeEventTouchDownRepeat = 1 << 1,
/** An event where a finger is dragged inside the bounds of the control node. */
ASControlNodeEventTouchDragInside = 1 << 2,
/** An event where a finger is dragged just outside the bounds of the control. */
ASControlNodeEventTouchDragOutside = 1 << 3,
/** A touch-up event in the control node where the finger is inside the bounds of the node. */
ASControlNodeEventTouchUpInside = 1 << 4,
/** A touch-up event in the control node where the finger is outside the bounds of the node. */
ASControlNodeEventTouchUpOutside = 1 << 5,
/** A system event canceling the current touches for the control node. */
ASControlNodeEventTouchCancel = 1 << 6,
/** A system event triggered when controls like switches, slides, etc change state. */
ASControlNodeEventValueChanged = 1 << 12,
/** A system event when the Play/Pause button on the Apple TV remote is pressed. */
ASControlNodeEventPrimaryActionTriggered = 1 << 13,
/** All events, including system events. */
ASControlNodeEventAllEvents = 0xFFFFFFFF
};
/**
* Compatibility aliases for @c ASControlState enum.
* We previously provided our own enum, but when it was imported
* into Swift, the @c normal (0) option disappeared.
*
* Apple's UIControlState enum gets special treatment here, and
* UIControlStateNormal is available in Swift.
*/
typedef UIControlState ASControlState ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlState.");
static UIControlState const ASControlStateNormal ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlStateNormal.") = UIControlStateNormal;
static UIControlState const ASControlStateDisabled ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlStateDisabled.") = UIControlStateDisabled;
static UIControlState const ASControlStateHighlighted ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlStateHighlighted.") = UIControlStateHighlighted;
static UIControlState const ASControlStateSelected ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlStateSelected.") = UIControlStateSelected;
/**
@abstract ASControlNode is the base class for control nodes (such as buttons), or nodes that track touches to invoke targets with action messages.
@discussion ASControlNode cannot be used directly. It instead defines the common interface and behavior structure for all its subclasses. Subclasses should import "ASControlNode+Subclasses.h" for information on methods intended to be overriden.
*/
@interface ASControlNode : ASDisplayNode
#pragma mark - Control State
/**
@abstract Indicates whether or not the receiver is enabled.
@discussion Specify YES to make the control enabled; otherwise, specify NO to make it disabled. The default value is YES. If the enabled state is NO, the control ignores touch events and subclasses may draw differently.
*/
@property (getter=isEnabled) BOOL enabled;
/**
@abstract Indicates whether or not the receiver is highlighted.
@discussion This is set automatically when the there is a touch inside the control and removed on exit or touch up. This is different from touchInside in that it includes an area around the control, rather than just for touches inside the control.
*/
@property (getter=isHighlighted) BOOL highlighted;
/**
@abstract Indicates whether or not the receiver is highlighted.
@discussion This is set automatically when the receiver is tapped.
*/
@property (getter=isSelected) BOOL selected;
#pragma mark - Tracking Touches
/**
@abstract Indicates whether or not the receiver is currently tracking touches related to an event.
@discussion YES if the receiver is tracking touches; NO otherwise.
*/
@property (readonly, getter=isTracking) BOOL tracking;
/**
@abstract Indicates whether or not a touch is inside the bounds of the receiver.
@discussion YES if a touch is inside the receiver's bounds; NO otherwise.
*/
@property (readonly, getter=isTouchInside) BOOL touchInside;
#pragma mark - Action Messages
/**
@abstract Adds a target-action pair for a particular event (or events).
@param target The object to which the action message is sent. If this is nil, the responder chain is searched for an object willing to respond to the action message. target is not retained.
@param action A selector identifying an action message. May optionally include the sender and the event as parameters, in that order. May not be NULL.
@param controlEvents A bitmask specifying the control events for which the action message is sent. May not be 0. See "Control Events" for bitmask constants.
@discussion You may call this method multiple times, and you may specify multiple target-action pairs for a particular event. Targets are held weakly.
*/
- (void)addTarget:(nullable id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEvents;
/**
@abstract Returns the actions that are associated with a target and a particular control event.
@param target The target object. May not be nil.
@param controlEvent A single constant of type ASControlNodeEvent that specifies a particular user action on the control; for a list of these constants, see "Control Events". May not be 0 or ASControlNodeEventAllEvents.
@result An array of selector names as NSString objects, or nil if there are no action selectors associated with controlEvent.
*/
- (nullable NSArray<NSString *> *)actionsForTarget:(id)target forControlEvent:(ASControlNodeEvent)controlEvent AS_WARN_UNUSED_RESULT;
/**
@abstract Returns all target objects associated with the receiver.
@result A set of all targets for the receiver. The set may include NSNull to indicate at least one nil target (meaning, the responder chain is searched for a target.)
*/
- (NSSet *)allTargets AS_WARN_UNUSED_RESULT;
/**
@abstract Removes a target-action pair for a particular event.
@param target The target object. Pass nil to remove all targets paired with action and the specified control events.
@param action A selector identifying an action message. Pass NULL to remove all action messages paired with target.
@param controlEvents A bitmask specifying the control events associated with target and action. See "Control Events" for bitmask constants. May not be 0.
*/
- (void)removeTarget:(nullable id)target action:(nullable SEL)action forControlEvents:(ASControlNodeEvent)controlEvents;
/**
@abstract Sends the actions for the control events for a particular event.
@param controlEvents A bitmask specifying the control events for which to send actions. See "Control Events" for bitmask constants. May not be 0.
@param event The event which triggered these control actions. May be nil.
*/
- (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(nullable UIEvent *)event;
@end
#if TARGET_OS_TV
@interface ASControlNode (tvOS)
/**
@abstract How the node looks when it isn't focused. Exposed here so that subclasses can override.
*/
- (void)setDefaultFocusAppearance;
@end
#endif
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,516 @@
//
// ASControlNode.mm
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASControlNode.h>
#import <AsyncDisplayKit/ASControlNode+Private.h>
#import <AsyncDisplayKit/ASControlNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASImageNode.h>
#import <AsyncDisplayKit/AsyncDisplayKit+Debug.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <AsyncDisplayKit/ASControlTargetAction.h>
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
#import <AsyncDisplayKit/ASThread.h>
// UIControl allows dragging some distance outside of the control itself during
// tracking. This value depends on the device idiom (25 or 70 points), so
// so replicate that effect with the same values here for our own controls.
#define kASControlNodeExpandedInset (([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) ? -25.0f : -70.0f)
// Initial capacities for dispatch tables.
#define kASControlNodeEventDispatchTableInitialCapacity 4
#define kASControlNodeActionDispatchTableInitialCapacity 4
@interface ASControlNode ()
{
@private
// Control Attributes
BOOL _enabled;
BOOL _highlighted;
// Tracking
BOOL _tracking;
BOOL _touchInside;
// Target action pairs stored in an array for each event type
// ASControlEvent -> [ASTargetAction0, ASTargetAction1]
NSMutableDictionary<id<NSCopying>, NSMutableArray<ASControlTargetAction *> *> *_controlEventDispatchTable;
}
// Read-write overrides.
@property (getter=isTracking) BOOL tracking;
@property (getter=isTouchInside) BOOL touchInside;
/**
@abstract Returns a key to be used in _controlEventDispatchTable that identifies the control event.
@param controlEvent A control event.
@result A key for use in _controlEventDispatchTable.
*/
id<NSCopying> _ASControlNodeEventKeyForControlEvent(ASControlNodeEvent controlEvent);
/**
@abstract Enumerates the ASControlNode events included mask, invoking the block for each event.
@param mask An ASControlNodeEvent mask.
@param block The block to be invoked for each ASControlNodeEvent included in mask.
*/
void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent));
/**
@abstract Returns the expanded bounds used to determine if a touch is considered 'inside' during tracking.
@param controlNode A control node.
@result The expanded bounds of the node.
*/
CGRect _ASControlNodeGetExpandedBounds(ASControlNode *controlNode);
@end
@implementation ASControlNode
{
ASImageNode *_debugHighlightOverlay;
}
#pragma mark - Lifecycle
- (instancetype)init
{
if (!(self = [super init]))
return nil;
_enabled = YES;
// As we have no targets yet, we start off with user interaction off. When a target is added, it'll get turned back on.
self.userInteractionEnabled = NO;
return self;
}
#if TARGET_OS_TV
- (void)didLoad
{
[super didLoad];
// On tvOS all controls, such as buttons, interact with the focus system even if they don't have a target set on them.
// Here we add our own internal tap gesture to handle this behaviour.
self.userInteractionEnabled = YES;
UITapGestureRecognizer *tapGestureRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_pressDown)];
tapGestureRec.allowedPressTypes = @[@(UIPressTypeSelect)];
[self.view addGestureRecognizer:tapGestureRec];
}
#endif
- (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled
{
[super setUserInteractionEnabled:userInteractionEnabled];
self.isAccessibilityElement = userInteractionEnabled;
}
- (void)__exitHierarchy
{
[super __exitHierarchy];
// If a control node is exit the hierarchy and is tracking we have to cancel it
if (self.tracking) {
[self _cancelTrackingWithEvent:nil];
}
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-missing-super-calls"
#pragma mark - ASDisplayNode Overrides
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// If we're not interested in touches, we have nothing to do.
if (!self.enabled) {
return;
}
// Check if the tracking should start
UITouch *theTouch = [touches anyObject];
if (![self beginTrackingWithTouch:theTouch withEvent:event]) {
return;
}
// If we get more than one touch down on us, cancel.
// Additionally, if we're already tracking a touch, a second touch beginning is cause for cancellation.
if (touches.count > 1 || self.tracking) {
[self _cancelTrackingWithEvent:event];
} else {
// Otherwise, begin tracking.
self.tracking = YES;
// No need to check bounds on touchesBegan as we wouldn't get the call if it wasn't in our bounds.
self.touchInside = YES;
self.highlighted = YES;
// Send the appropriate touch-down control event depending on how many times we've been tapped.
ASControlNodeEvent controlEventMask = (theTouch.tapCount == 1) ? ASControlNodeEventTouchDown : ASControlNodeEventTouchDownRepeat;
[self sendActionsForControlEvents:controlEventMask withEvent:event];
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// If we're not interested in touches, we have nothing to do.
if (!self.enabled) {
return;
}
NSParameterAssert(touches.count == 1);
UITouch *theTouch = [touches anyObject];
// Check if tracking should continue
if (!self.tracking || ![self continueTrackingWithTouch:theTouch withEvent:event]) {
self.tracking = NO;
return;
}
CGPoint touchLocation = [theTouch locationInView:self.view];
// Update our touchInside state.
BOOL dragIsInsideBounds = [self pointInside:touchLocation withEvent:nil];
// Update our highlighted state.
CGRect expandedBounds = _ASControlNodeGetExpandedBounds(self);
BOOL dragIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation);
self.touchInside = dragIsInsideExpandedBounds;
self.highlighted = dragIsInsideExpandedBounds;
[self sendActionsForControlEvents:(dragIsInsideBounds ? ASControlNodeEventTouchDragInside : ASControlNodeEventTouchDragOutside)
withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
// If we're not interested in touches, we have nothing to do.
if (!self.enabled) {
return;
}
// Note that we've cancelled tracking.
[self _cancelTrackingWithEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// If we're not interested in touches, we have nothing to do.
if (!self.enabled) {
return;
}
// On iPhone 6s, iOS 9.2 (and maybe other versions) sometimes calls -touchesEnded:withEvent:
// twice on the view for one call to -touchesBegan:withEvent:. On ASControlNode, it used to
// trigger an action twice unintentionally. Now, we ignore that event if we're not in a tracking
// state in order to have a correct behavior.
// It might be related to that issue: http://www.openradar.me/22910171
if (!self.tracking) {
return;
}
NSParameterAssert([touches count] == 1);
UITouch *theTouch = [touches anyObject];
CGPoint touchLocation = [theTouch locationInView:self.view];
// Update state.
self.tracking = NO;
self.touchInside = NO;
self.highlighted = NO;
// Note that we've ended tracking.
[self endTrackingWithTouch:theTouch withEvent:event];
// Send the appropriate touch-up control event.
CGRect expandedBounds = _ASControlNodeGetExpandedBounds(self);
BOOL touchUpIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation);
[self sendActionsForControlEvents:(touchUpIsInsideExpandedBounds ? ASControlNodeEventTouchUpInside : ASControlNodeEventTouchUpOutside)
withEvent:event];
}
- (void)_cancelTrackingWithEvent:(UIEvent *)event
{
// We're no longer tracking and there is no touch to be inside.
self.tracking = NO;
self.touchInside = NO;
self.highlighted = NO;
// Send the cancel event.
[self sendActionsForControlEvents:ASControlNodeEventTouchCancel withEvent:event];
}
#pragma clang diagnostic pop
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
ASDisplayNodeAssertMainThread();
// If not enabled we should not care about receving touches
if (! self.enabled) {
return nil;
}
return [super hitTest:point withEvent:event];
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
// If we're interested in touches, this is a tap (the only gesture we care about) and passed -hitTest for us, then no, you may not begin. Sir.
if (self.enabled && [gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] && gestureRecognizer.view != self.view) {
UITapGestureRecognizer *tapRecognizer = (UITapGestureRecognizer *)gestureRecognizer;
// Allow double-tap gestures
return tapRecognizer.numberOfTapsRequired != 1;
}
// Otherwise, go ahead. :]
return YES;
}
- (BOOL)supportsLayerBacking
{
return super.supportsLayerBacking && !self.userInteractionEnabled;
}
#pragma mark - Action Messages
- (void)addTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEventMask
{
NSParameterAssert(action);
NSParameterAssert(controlEventMask != 0);
// ASControlNode cannot be layer backed if adding a target
ASDisplayNodeAssert(!self.isLayerBacked, @"ASControlNode is layer backed, will never be able to call target in target:action: pair.");
ASLockScopeSelf();
if (!_controlEventDispatchTable) {
_controlEventDispatchTable = [[NSMutableDictionary alloc] initWithCapacity:kASControlNodeEventDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries.
// only show tap-able areas for views with 1 or more addTarget:action: pairs
if ([ASControlNode enableHitTestDebug] && _debugHighlightOverlay == nil) {
// do not use ASPerformBlockOnMainThread here, if it performs the block synchronously it will continue
// holding the lock while calling addSubnode.
dispatch_async(dispatch_get_main_queue(), ^{
// add a highlight overlay node with area of ASControlNode + UIEdgeInsets
self.clipsToBounds = NO;
_debugHighlightOverlay = [[ASImageNode alloc] init];
_debugHighlightOverlay.zPosition = 1000; // ensure we're over the top of any siblings
_debugHighlightOverlay.layerBacked = YES;
[self addSubnode:_debugHighlightOverlay];
});
}
}
// Create new target action pair
ASControlTargetAction *targetAction = [[ASControlTargetAction alloc] init];
targetAction.action = action;
targetAction.target = target;
// Enumerate the events in the mask, adding the target-action pair for each control event included in controlEventMask
_ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^
(ASControlNodeEvent controlEvent)
{
// Do we already have an event table for this control event?
id<NSCopying> eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent);
NSMutableArray *eventTargetActionArray = _controlEventDispatchTable[eventKey];
if (!eventTargetActionArray) {
eventTargetActionArray = [[NSMutableArray alloc] init];
}
// Remove any prior target-action pair for this event, as UIKit does.
[eventTargetActionArray removeObject:targetAction];
// Register the new target-action as the last one to be sent.
[eventTargetActionArray addObject:targetAction];
if (eventKey) {
[_controlEventDispatchTable setObject:eventTargetActionArray forKey:eventKey];
}
});
self.userInteractionEnabled = YES;
}
- (NSArray *)actionsForTarget:(id)target forControlEvent:(ASControlNodeEvent)controlEvent
{
NSParameterAssert(target);
NSParameterAssert(controlEvent != 0 && controlEvent != ASControlNodeEventAllEvents);
ASLockScopeSelf();
// Grab the event target action array for this event.
NSMutableArray *eventTargetActionArray = _controlEventDispatchTable[_ASControlNodeEventKeyForControlEvent(controlEvent)];
if (!eventTargetActionArray) {
return nil;
}
NSMutableArray *actions = [[NSMutableArray alloc] init];
// Collect all actions for this target.
for (ASControlTargetAction *targetAction in eventTargetActionArray) {
if ((target == nil && targetAction.createdWithNoTarget) || (target != nil && target == targetAction.target)) {
[actions addObject:NSStringFromSelector(targetAction.action)];
}
}
return actions;
}
- (NSSet *)allTargets
{
ASLockScopeSelf();
NSMutableSet *targets = [[NSMutableSet alloc] init];
// Look at each event...
for (NSMutableArray *eventTargetActionArray in [_controlEventDispatchTable objectEnumerator]) {
// and each event's targets...
for (ASControlTargetAction *targetAction in eventTargetActionArray) {
[targets addObject:targetAction.target];
}
}
return targets;
}
- (void)removeTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEventMask
{
NSParameterAssert(controlEventMask != 0);
ASLockScopeSelf();
// Enumerate the events in the mask, removing the target-action pair for each control event included in controlEventMask.
_ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^
(ASControlNodeEvent controlEvent)
{
// Grab the dispatch table for this event (if we have it).
id<NSCopying> eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent);
NSMutableArray *eventTargetActionArray = _controlEventDispatchTable[eventKey];
if (!eventTargetActionArray) {
return;
}
NSPredicate *filterPredicate = [NSPredicate predicateWithBlock:^BOOL(ASControlTargetAction *_Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
if (!target || evaluatedObject.target == target) {
if (!action) {
return NO;
} else if (evaluatedObject.action == action) {
return NO;
}
}
return YES;
}];
[eventTargetActionArray filterUsingPredicate:filterPredicate];
if (eventTargetActionArray.count == 0) {
// If there are no targets for this event anymore, remove it.
[_controlEventDispatchTable removeObjectForKey:eventKey];
}
});
}
#pragma mark -
- (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(UIEvent *)event
{
ASDisplayNodeAssertMainThread(); //We access self.view below, it's not safe to call this off of main.
NSParameterAssert(controlEvents != 0);
NSMutableArray *resolvedEventTargetActionArray = [[NSMutableArray<ASControlTargetAction *> alloc] init];
{
ASLockScopeSelf();
// Enumerate the events in the mask, invoking the target-action pairs for each.
_ASEnumerateControlEventsIncludedInMaskWithBlock(controlEvents, ^
(ASControlNodeEvent controlEvent)
{
// Iterate on each target action pair
for (ASControlTargetAction *targetAction in _controlEventDispatchTable[_ASControlNodeEventKeyForControlEvent(controlEvent)]) {
ASControlTargetAction *resolvedTargetAction = [[ASControlTargetAction alloc] init];
resolvedTargetAction.action = targetAction.action;
resolvedTargetAction.target = targetAction.target;
// NSNull means that a nil target was set, so start at self and travel the responder chain
if (!resolvedTargetAction.target && targetAction.createdWithNoTarget) {
// if the target cannot perform the action, travel the responder chain to try to find something that does
resolvedTargetAction.target = [self.view targetForAction:resolvedTargetAction.action withSender:self];
}
if (resolvedTargetAction.target) {
[resolvedEventTargetActionArray addObject:resolvedTargetAction];
}
}
});
}
//We don't want to hold the lock while calling out, we could potentially walk up the ownership tree causing a deadlock.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
for (ASControlTargetAction *targetAction in resolvedEventTargetActionArray) {
[targetAction.target performSelector:targetAction.action withObject:self withObject:event];
}
#pragma clang diagnostic pop
}
#pragma mark - Convenience
id<NSCopying> _ASControlNodeEventKeyForControlEvent(ASControlNodeEvent controlEvent)
{
return @(controlEvent);
}
void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent))
{
if (block == nil) {
return;
}
// Start with our first event (touch down) and work our way up to the last event (PrimaryActionTriggered)
for (ASControlNodeEvent thisEvent = ASControlNodeEventTouchDown; thisEvent <= ASControlNodeEventPrimaryActionTriggered; thisEvent <<= 1) {
// If it's included in the mask, invoke the block.
if ((mask & thisEvent) == thisEvent)
block(thisEvent);
}
}
CGRect _ASControlNodeGetExpandedBounds(ASControlNode *controlNode) {
return CGRectInset(UIEdgeInsetsInsetRect(controlNode.view.bounds, controlNode.hitTestSlop), kASControlNodeExpandedInset, kASControlNodeExpandedInset);
}
#pragma mark - For Subclasses
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent
{
return YES;
}
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent
{
return YES;
}
- (void)cancelTrackingWithEvent:(UIEvent *)touchEvent
{
// Subclass hook
}
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent
{
// Subclass hook
}
#pragma mark - Debug
- (ASImageNode *)debugHighlightOverlay
{
return _debugHighlightOverlay;
}
@end

View File

@ -0,0 +1,191 @@
//
// ASDisplayNode+Beta.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASAvailability.h>
#import <AsyncDisplayKit/ASDisplayNode.h>
#import <AsyncDisplayKit/ASLayoutRangeType.h>
#import <AsyncDisplayKit/ASEventLog.h>
#if YOGA
#import YOGA_HEADER_PATH
#import <AsyncDisplayKit/ASYogaUtilities.h>
#import <AsyncDisplayKit/ASDisplayNode+Yoga.h>
#endif
NS_ASSUME_NONNULL_BEGIN
AS_EXTERN void ASPerformBlockOnMainThread(void (^block)(void));
AS_EXTERN void ASPerformBlockOnBackgroundThread(void (^block)(void)); // DISPATCH_QUEUE_PRIORITY_DEFAULT
#if ASEVENTLOG_ENABLE
#define ASDisplayNodeLogEvent(node, ...) [node.eventLog logEventWithBacktrace:(AS_SAVE_EVENT_BACKTRACES ? [NSThread callStackSymbols] : nil) format:__VA_ARGS__]
#else
#define ASDisplayNodeLogEvent(node, ...)
#endif
#if ASEVENTLOG_ENABLE
#define ASDisplayNodeGetEventLog(node) node.eventLog
#else
#define ASDisplayNodeGetEventLog(node) nil
#endif
/**
* Bitmask to indicate what performance measurements the cell should record.
*/
typedef NS_OPTIONS(NSUInteger, ASDisplayNodePerformanceMeasurementOptions) {
ASDisplayNodePerformanceMeasurementOptionLayoutSpec = 1 << 0,
ASDisplayNodePerformanceMeasurementOptionLayoutComputation = 1 << 1
};
typedef struct {
CFTimeInterval layoutSpecTotalTime;
NSInteger layoutSpecNumberOfPasses;
CFTimeInterval layoutComputationTotalTime;
NSInteger layoutComputationNumberOfPasses;
} ASDisplayNodePerformanceMeasurements;
@interface ASDisplayNode (Beta)
/**
* ASTableView and ASCollectionView now throw exceptions on invalid updates
* like their UIKit counterparts. If YES, these classes will log messages
* on invalid updates rather than throwing exceptions.
*
* Note that even if AsyncDisplayKit's exception is suppressed, the app may still crash
* as it proceeds with an invalid update.
*
* This property defaults to NO. It will be removed in a future release.
*/
+ (BOOL)suppressesInvalidCollectionUpdateExceptions AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Collection update exceptions are thrown if assertions are enabled.");
+ (void)setSuppressesInvalidCollectionUpdateExceptions:(BOOL)suppresses;
/**
* @abstract Recursively ensures node and all subnodes are displayed.
* @see Full documentation in ASDisplayNode+FrameworkPrivate.h
*/
- (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously;
/**
* @abstract allow modification of a context before the node's content is drawn
*
* @discussion Set the block to be called after the context has been created and before the node's content is drawn.
* You can override this to modify the context before the content is drawn. You are responsible for saving and
* restoring context if necessary. Restoring can be done in contextDidDisplayNodeContent
* This block can be called from *any* thread and it is unsafe to access any UIKit main thread properties from it.
*/
@property (nullable) ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext;
/**
* @abstract allow modification of a context after the node's content is drawn
*/
@property (nullable) ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext;
/**
* @abstract A bitmask representing which actions (layout spec, layout generation) should be measured.
*/
@property ASDisplayNodePerformanceMeasurementOptions measurementOptions;
/**
* @abstract A simple struct representing performance measurements collected.
*/
@property (readonly) ASDisplayNodePerformanceMeasurements performanceMeasurements;
#if ASEVENTLOG_ENABLE
/*
* @abstract The primitive event tracing object. You shouldn't directly use it to log event. Use the ASDisplayNodeLogEvent macro instead.
*/
@property (nonatomic, readonly) ASEventLog *eventLog;
#endif
/**
* @abstract Whether this node acts as an accessibility container. If set to YES, then this node's accessibility label will represent
* an aggregation of all child nodes' accessibility labels. Nodes in this node's subtree that are also accessibility containers will
* not be included in this aggregation, and will be exposed as separate accessibility elements to UIKit.
*/
@property BOOL isAccessibilityContainer;
/**
* @abstract Returns the default accessibility property values set by Texture on this node. For
* example, the default accessibility label for a text node may be its text content, while most
* other nodes would have nil default labels.
*/
@property (nullable, readonly, copy) NSString *defaultAccessibilityLabel;
@property (nullable, readonly, copy) NSString *defaultAccessibilityHint;
@property (nullable, readonly, copy) NSString *defaultAccessibilityValue;
@property (nullable, readonly, copy) NSString *defaultAccessibilityIdentifier;
@property (readonly) UIAccessibilityTraits defaultAccessibilityTraits;
/**
* @abstract Invoked when a user performs a custom action on an accessible node. Nodes that are children of accessibility containers, have
* an accessibity label and have an interactive UIAccessibilityTrait will automatically receive custom-action handling.
*
* @return Return a boolean value that determine whether to propagate through the responder chain.
* To halt propagation, return YES; otherwise, return NO.
*/
- (BOOL)performAccessibilityCustomAction:(UIAccessibilityCustomAction *)action;
/**
* @abstract Currently used by ASNetworkImageNode and ASMultiplexImageNode to allow their placeholders to stay if they are loading an image from the network.
* Otherwise, a display pass is scheduled and completes, but does not actually draw anything - and ASDisplayNode considers the element finished.
*/
- (BOOL)placeholderShouldPersist AS_WARN_UNUSED_RESULT;
/**
* @abstract Indicates that the receiver and all subnodes have finished displaying. May be called more than once, for example if the receiver has
* a network image node. This is called after the first display pass even if network image nodes have not downloaded anything (text would be done,
* and other nodes that are ready to do their final display). Each render of every progressive jpeg network node would cause this to be called, so
* this hook could be called up to 1 + (pJPEGcount * pJPEGrenderCount) times. The render count depends on how many times the downloader calls the
* progressImage block.
*/
AS_CATEGORY_IMPLEMENTABLE
- (void)hierarchyDisplayDidFinish NS_REQUIRES_SUPER;
/**
* Only called on the root during yoga layout.
*/
AS_CATEGORY_IMPLEMENTABLE
- (void)willCalculateLayout:(ASSizeRange)constrainedSize NS_REQUIRES_SUPER;
/**
* Only ASLayoutRangeModeVisibleOnly or ASLayoutRangeModeLowMemory are recommended. Default is ASLayoutRangeModeVisibleOnly,
* because this is the only way to ensure an application will not have blank / flashing views as the user navigates back after
* a memory warning. Apps that wish to use the more effective / aggressive ASLayoutRangeModeLowMemory may need to take steps
* to mitigate this behavior, including: restoring a larger range mode to the next controller before the user navigates there,
* enabling .neverShowPlaceholders on ASCellNodes so that the navigation operation is blocked on redisplay completing, etc.
*/
+ (void)setRangeModeForMemoryWarnings:(ASLayoutRangeMode)rangeMode;
/**
* @abstract Whether to draw all descendent nodes' contents into this node's layer's backing store.
*
* @discussion
* When called, causes all descendent nodes' contents to be drawn directly into this node's layer's backing
* store.
*
* If a node's descendants are static (never animated or never change attributes after creation) then that node is a
* good candidate for rasterization. Rasterizing descendants has two main benefits:
* 1) Backing stores for descendant layers are not created. Instead the layers are drawn directly into the rasterized
* container. This can save a great deal of memory.
* 2) Since the entire subtree is drawn into one backing store, compositing and blending are eliminated in that subtree
* which can help improve animation/scrolling/etc performance.
*
* Rasterization does not currently support descendants with transform, sublayerTransform, or alpha. Those properties
* will be ignored when rasterizing descendants.
*
* Note: this has nothing to do with -[CALayer shouldRasterize], which doesn't work with ASDisplayNode's asynchronous
* rendering model.
*
* Note: You cannot add subnodes whose layers/views are already loaded to a rasterized node.
* Note: You cannot call this method after the receiver's layer/view is loaded.
*/
- (void)enableSubtreeRasterization;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,28 @@
//
// ASDisplayNode+Convenience.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASDisplayNode.h>
NS_ASSUME_NONNULL_BEGIN
@class UIViewController;
@interface ASDisplayNode (Convenience)
/**
* @abstract Returns the view controller nearest to this node in the view hierarchy.
*
* @warning This property may only be accessed on the main thread. This property may
* be @c nil until the node's view is actually hosted in the view hierarchy.
*/
@property (nonatomic, nullable, readonly) __kindof UIViewController *closestViewController;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,40 @@
//
// ASDisplayNode+Convenience.mm
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import "ASDisplayNode+Convenience.h"
#import <UIKit/UIViewController.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASResponderChainEnumerator.h>
@implementation ASDisplayNode (Convenience)
- (__kindof UIViewController *)closestViewController
{
ASDisplayNodeAssertMainThread();
// Careful not to trigger node loading here.
if (!self.nodeLoaded) {
return nil;
}
// Get the closest view.
UIView *view = ASFindClosestViewOfLayer(self.layer);
// Travel up the responder chain to find a view controller.
for (UIResponder *responder in [view asdk_responderChainEnumerator]) {
UIViewController *vc = ASDynamicCast(responder, UIViewController);
if (vc != nil) {
return vc;
}
}
return nil;
}
@end

View File

@ -0,0 +1,142 @@
//
// ASDisplayNode+Deprecated.h
// Texture
//
// 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 /ASDK-Licenses directory of this source tree. An additional
// grant of patent rights can be found in the PATENTS file in the same directory.
//
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#pragma once
#import <AsyncDisplayKit/ASDisplayNode.h>
@interface ASDisplayNode (Deprecated)
/**
* @abstract The name of this node, which will be displayed in `description`. The default value is nil.
*
* @deprecated Deprecated in version 2.0: Use .debugName instead. This value will display in
* results of the -asciiArtString method (@see ASLayoutElementAsciiArtProtocol).
*/
@property (nullable, nonatomic, copy) NSString *name ASDISPLAYNODE_DEPRECATED_MSG("Use .debugName instead.");
/**
* @abstract Provides a default intrinsic content size for calculateSizeThatFits:. This is useful when laying out
* a node that either has no intrinsic content size or should be laid out at a different size than its intrinsic content
* size. For example, this property could be set on an ASImageNode to display at a size different from the underlying
* image size.
*
* @return Try to create a CGSize for preferredFrameSize of this node from the width and height property of this node. It will return CGSizeZero if width and height dimensions are not of type ASDimensionUnitPoints.
*
* @deprecated Deprecated in version 2.0: Just calls through to set the height and width property of the node. Convert to use sizing properties instead: height, minHeight, maxHeight, width, minWidth, maxWidth.
*/
@property (nonatomic, assign, readwrite) CGSize preferredFrameSize ASDISPLAYNODE_DEPRECATED_MSG("Use .style.preferredSize instead OR set individual values with .style.height and .style.width.");
/**
* @abstract Asks the node to measure and return the size that best fits its subnodes.
*
* @param constrainedSize The maximum size the receiver should fit in.
*
* @return A new size that fits the receiver's subviews.
*
* @discussion Though this method does not set the bounds of the view, it does have side effects--caching both the
* constraint and the result.
*
* @warning Subclasses must not override this; it calls -measureWithSizeRange: with zero min size.
* -measureWithSizeRange: caches results from -calculateLayoutThatFits:. Calling this method may
* be expensive if result is not cached.
*
* @see measureWithSizeRange:
* @see [ASDisplayNode(Subclassing) calculateLayoutThatFits:]
*
* @deprecated Deprecated in version 2.0: Use layoutThatFits: with a constrained size of (CGSizeZero, constrainedSize) and call size on the returned ASLayout
*/
- (CGSize)measure:(CGSize)constrainedSize/* ASDISPLAYNODE_DEPRECATED_MSG("Use layoutThatFits: with a constrained size of (CGSizeZero, constrainedSize) and call size on the returned ASLayout.")*/;
ASLayoutElementStyleForwardingDeclaration
/**
* @abstract Called whenever the visiblity of the node changed.
*
* @discussion Subclasses may use this to monitor when they become visible.
*
* @deprecated @see didEnterVisibleState @see didExitVisibleState
*/
- (void)visibilityDidChange:(BOOL)isVisible ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterVisibleState / -didExitVisibleState instead.");
/**
* @abstract Called whenever the visiblity of the node changed.
*
* @discussion Subclasses may use this to monitor when they become visible.
*
* @deprecated @see didEnterVisibleState @see didExitVisibleState
*/
- (void)visibleStateDidChange:(BOOL)isVisible ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterVisibleState / -didExitVisibleState instead.");
/**
* @abstract Called whenever the the node has entered or exited the display state.
*
* @discussion Subclasses may use this to monitor when a node should be rendering its content.
*
* @note This method can be called from any thread and should therefore be thread safe.
*
* @deprecated @see didEnterDisplayState @see didExitDisplayState
*/
- (void)displayStateDidChange:(BOOL)inDisplayState ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterDisplayState / -didExitDisplayState instead.");
/**
* @abstract Called whenever the the node has entered or left the load state.
*
* @discussion Subclasses may use this to monitor data for a node should be loaded, either from a local or remote source.
*
* @note This method can be called from any thread and should therefore be thread safe.
*
* @deprecated @see didEnterPreloadState @see didExitPreloadState
*/
- (void)loadStateDidChange:(BOOL)inLoadState ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterPreloadState / -didExitPreloadState instead.");
/**
* @abstract Cancels all performing layout transitions. Can be called on any thread.
*
* @deprecated Deprecated in version 2.0: Use cancelLayoutTransition
*/
- (void)cancelLayoutTransitionsInProgress ASDISPLAYNODE_DEPRECATED_MSG("Use -cancelLayoutTransition instead.");
/**
* @abstract A boolean that shows whether the node automatically inserts and removes nodes based on the presence or
* absence of the node and its subnodes is completely determined in its layoutSpecThatFits: method.
*
* @discussion If flag is YES the node no longer require addSubnode: or removeFromSupernode method calls. The presence
* or absence of subnodes is completely determined in its layoutSpecThatFits: method.
*
* @deprecated Deprecated in version 2.0: Use automaticallyManagesSubnodes
*/
@property (nonatomic, assign) BOOL usesImplicitHierarchyManagement ASDISPLAYNODE_DEPRECATED_MSG("Set .automaticallyManagesSubnodes instead.");
/**
* @abstract Indicates that the node should fetch any external data, such as images.
*
* @discussion Subclasses may override this method to be notified when they should begin to preload. Fetching
* should be done asynchronously. The node is also responsible for managing the memory of any data.
* The data may be remote and accessed via the network, but could also be a local database query.
*/
- (void)fetchData ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterPreloadState instead.");
/**
* Provides an opportunity to clear any fetched data (e.g. remote / network or database-queried) on the current node.
*
* @discussion This will not clear data recursively for all subnodes. Either call -recursivelyClearPreloadedData or
* selectively clear fetched data.
*/
- (void)clearFetchedData ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didExitPreloadState instead.");
@end

View File

@ -0,0 +1,130 @@
//
// ASDisplayNode+InterfaceState.h
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <Foundation/Foundation.h>
/**
* Interface state is available on ASDisplayNode and ASViewController, and
* allows checking whether a node is in an interface situation where it is prudent to trigger certain
* actions: measurement, data loading, display, and visibility (the latter for animations or other onscreen-only effects).
*
* The defualt state, ASInterfaceStateNone, means that the element is not predicted to be onscreen soon and
* preloading should not be performed. Swift: use [] for the default behavior.
*/
typedef NS_OPTIONS(NSUInteger, ASInterfaceState)
{
/** The element is not predicted to be onscreen soon and preloading should not be performed */
ASInterfaceStateNone = 0,
/** The element may be added to a view soon that could become visible. Measure the layout, including size calculation. */
ASInterfaceStateMeasureLayout = 1 << 0,
/** The element is likely enough to come onscreen that disk and/or network data required for display should be fetched. */
ASInterfaceStatePreload = 1 << 1,
/** The element is very likely to become visible, and concurrent rendering should be executed for any -setNeedsDisplay. */
ASInterfaceStateDisplay = 1 << 2,
/** The element is physically onscreen by at least 1 pixel.
In practice, all other bit fields should also be set when this flag is set. */
ASInterfaceStateVisible = 1 << 3,
/**
* The node is not contained in a cell but it is in a window.
*
* Currently we only set `interfaceState` to other values for
* nodes contained in table views or collection views.
*/
ASInterfaceStateInHierarchy = ASInterfaceStateMeasureLayout | ASInterfaceStatePreload | ASInterfaceStateDisplay | ASInterfaceStateVisible,
};
@protocol ASInterfaceStateDelegate <NSObject>
/**
* @abstract Called whenever any bit in the ASInterfaceState bitfield is changed.
* @discussion Subclasses may use this to monitor when they become visible, should free cached data, and much more.
* @see ASInterfaceState
*/
- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState;
/**
* @abstract Called whenever the node becomes visible.
* @discussion Subclasses may use this to monitor when they become visible.
* @note This method is guaranteed to be called on main.
*/
- (void)didEnterVisibleState;
/**
* @abstract Called whenever the node is no longer visible.
* @discussion Subclasses may use this to monitor when they are no longer visible.
* @note This method is guaranteed to be called on main.
*/
- (void)didExitVisibleState;
/**
* @abstract Called whenever the the node has entered the display state.
* @discussion Subclasses may use this to monitor when a node should be rendering its content.
* @note This method is guaranteed to be called on main.
*/
- (void)didEnterDisplayState;
/**
* @abstract Called whenever the the node has exited the display state.
* @discussion Subclasses may use this to monitor when a node should no longer be rendering its content.
* @note This method is guaranteed to be called on main.
*/
- (void)didExitDisplayState;
/**
* @abstract Called whenever the the node has entered the preload state.
* @discussion Subclasses may use this to monitor data for a node should be preloaded, either from a local or remote source.
* @note This method is guaranteed to be called on main.
*/
- (void)didEnterPreloadState;
/**
* @abstract Called whenever the the node has exited the preload state.
* @discussion Subclasses may use this to monitor whether preloading data for a node should be canceled.
* @note This method is guaranteed to be called on main.
*/
- (void)didExitPreloadState;
/**
* @abstract Called when the node has completed applying the layout.
* @discussion Can be used for operations that are performed after layout has completed.
* @note This method is guaranteed to be called on main.
*/
- (void)nodeDidLayout;
/**
* @abstract Called when the node loads.
* @discussion Can be used for operations that are performed after the node's view is available.
* @note This method is guaranteed to be called on main.
*/
- (void)nodeDidLoad;
/**
* @abstract Indicates that the receiver and all subnodes have finished displaying.
* @discussion May be called more than once, for example if the receiver has a network image node.
* This is called after the first display pass even if network image nodes have not downloaded anything
* (text would be done, and other nodes that are ready to do their final display). Each render of
* every progressive jpeg network node would cause this to be called, so this hook could be called up to
* 1 + (pJPEGcount * pJPEGrenderCount) times. The render count depends on how many times the downloader calls
* the progressImage block.
* @note This method is guaranteed to be called on main.
*/
- (void)hierarchyDisplayDidFinish;
@optional
/**
* @abstract Called when the node is about to calculate layout. This is only called before
* Yoga-driven layouts.
* @discussion Can be used for operations that are performed after the node's view is available.
* @note This method is guaranteed to be called on main, but implementations should be careful not
* to attempt to ascend the node tree when handling this, as the root node is locked when this is
* called.
*/
- (void)nodeWillCalculateLayout:(ASSizeRange)constrainedSize;
@end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,66 @@
//
// ASDisplayNode+LayoutSpec.h
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASDisplayNode.h>
#import <AsyncDisplayKit/ASDimension.h>
@class ASLayout;
NS_ASSUME_NONNULL_BEGIN
@interface ASDisplayNode (ASLayoutSpec)
/**
* @abstract Provides a way to declare a block to provide an ASLayoutSpec without having to subclass ASDisplayNode and
* implement layoutSpecThatFits:
*
* @return A block that takes a constrainedSize ASSizeRange argument, and must return an ASLayoutSpec that includes all
* of the subnodes to position in the layout. This input-output relationship is identical to the subclass override
* method -layoutSpecThatFits:
*
* @warning Subclasses that implement -layoutSpecThatFits: must not also use .layoutSpecBlock. Doing so will trigger
* an exception. A future version of the framework may support using both, calling them serially, with the
* .layoutSpecBlock superseding any values set by the method override.
*
* @code ^ASLayoutSpec *(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {};
*/
@property (nullable) ASLayoutSpecBlock layoutSpecBlock;
@end
// These methods are intended to be used internally to Texture, and should not be called directly.
@interface ASDisplayNode (ASLayoutSpecPrivate)
/// For internal usage only
- (ASLayout *)calculateLayoutLayoutSpec:(ASSizeRange)constrainedSize;
@end
@interface ASDisplayNode (ASLayoutSpecSubclasses)
/**
* @abstract Return a layout spec that describes the layout of the receiver and its children.
*
* @param constrainedSize The minimum and maximum sizes the receiver should fit in.
*
* @discussion Subclasses that override should expect this method to be called on a non-main thread. The returned layout spec
* is used to calculate an ASLayout and cached by ASDisplayNode for quick access during -layout. Other expensive work that needs to
* be done before display can be performed here, and using ivars to cache any valuable intermediate results is
* encouraged.
*
* @note This method should not be called directly outside of ASDisplayNode; use -layoutThatFits: instead.
*
* @warning Subclasses that implement -layoutSpecThatFits: must not use .layoutSpecBlock. Doing so will trigger an
* exception. A future version of the framework may support using both, calling them serially, with the .layoutSpecBlock
* superseding any values set by the method override.
*/
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,146 @@
//
// ASDisplayNode+LayoutSpec.mm
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASAvailability.h>
#import <AsyncDisplayKit/_ASScopeTimer.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASLayoutSpec+Subclasses.h>
#import <AsyncDisplayKit/ASLayoutSpecPrivate.h>
#import <AsyncDisplayKit/ASLog.h>
#import <AsyncDisplayKit/ASThread.h>
@implementation ASDisplayNode (ASLayoutSpec)
- (void)setLayoutSpecBlock:(ASLayoutSpecBlock)layoutSpecBlock
{
// For now there should never be an override of layoutSpecThatFits: and a layoutSpecBlock together.
ASDisplayNodeAssert(!(_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits),
@"Nodes with a .layoutSpecBlock must not also implement -layoutSpecThatFits:");
AS::MutexLocker l(__instanceLock__);
_layoutSpecBlock = layoutSpecBlock;
}
- (ASLayoutSpecBlock)layoutSpecBlock
{
AS::MutexLocker l(__instanceLock__);
return _layoutSpecBlock;
}
- (ASLayout *)calculateLayoutLayoutSpec:(ASSizeRange)constrainedSize
{
AS::UniqueLock l(__instanceLock__);
// Manual size calculation via calculateSizeThatFits:
if (_layoutSpecBlock == NULL && (_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) == 0) {
CGSize size = [self calculateSizeThatFits:constrainedSize.max];
ASDisplayNodeLogEvent(self, @"calculatedSize: %@", NSStringFromCGSize(size));
return [ASLayout layoutWithLayoutElement:self size:ASSizeRangeClamp(constrainedSize, size) sublayouts:nil];
}
// Size calcualtion with layout elements
BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec;
if (measureLayoutSpec) {
_layoutSpecNumberOfPasses++;
}
// Get layout element from the node
id<ASLayoutElement> layoutElement = [self _locked_layoutElementThatFits:constrainedSize];
#if ASEnableVerboseLogging
for (NSString *asciiLine in [[layoutElement asciiArtString] componentsSeparatedByString:@"\n"]) {
as_log_verbose(ASLayoutLog(), "%@", asciiLine);
}
#endif
// Certain properties are necessary to set on an element of type ASLayoutSpec
if (layoutElement.layoutElementType == ASLayoutElementTypeLayoutSpec) {
ASLayoutSpec *layoutSpec = (ASLayoutSpec *)layoutElement;
#if AS_DEDUPE_LAYOUT_SPEC_TREE
NSHashTable *duplicateElements = [layoutSpec findDuplicatedElementsInSubtree];
if (duplicateElements.count > 0) {
ASDisplayNodeFailAssert(@"Node %@ returned a layout spec that contains the same elements in multiple positions. Elements: %@", self, duplicateElements);
// Use an empty layout spec to avoid crashes
layoutSpec = [[ASLayoutSpec alloc] init];
}
#endif
ASDisplayNodeAssert(layoutSpec.isMutable, @"Node %@ returned layout spec %@ that has already been used. Layout specs should always be regenerated.", self, layoutSpec);
layoutSpec.isMutable = NO;
}
// Manually propagate the trait collection here so that any layoutSpec children of layoutSpec will get a traitCollection
{
AS::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec);
ASTraitCollectionPropagateDown(layoutElement, self.primitiveTraitCollection);
}
BOOL measureLayoutComputation = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutComputation;
if (measureLayoutComputation) {
_layoutComputationNumberOfPasses++;
}
// Layout element layout creation
ASLayout *layout = ({
AS::SumScopeTimer t(_layoutComputationTotalTime, measureLayoutComputation);
[layoutElement layoutThatFits:constrainedSize];
});
ASDisplayNodeAssertNotNil(layout, @"[ASLayoutElement layoutThatFits:] should never return nil! %@, %@", self, layout);
// Make sure layoutElementObject of the root layout is `self`, so that the flattened layout will be structurally correct.
BOOL isFinalLayoutElement = (layout.layoutElement != self);
if (isFinalLayoutElement) {
layout.position = CGPointZero;
layout = [ASLayout layoutWithLayoutElement:self size:layout.size sublayouts:@[layout]];
}
ASDisplayNodeLogEvent(self, @"computedLayout: %@", layout);
// PR #1157: Reduces accuracy of _unflattenedLayout for debugging/Weaver
if ([ASDisplayNode shouldStoreUnflattenedLayouts]) {
_unflattenedLayout = layout;
}
layout = [layout filteredNodeLayoutTree];
return layout;
}
- (id<ASLayoutElement>)_locked_layoutElementThatFits:(ASSizeRange)constrainedSize
{
ASAssertLocked(__instanceLock__);
BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec;
if (_layoutSpecBlock != NULL) {
return ({
AS::MutexLocker l(__instanceLock__);
AS::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec);
_layoutSpecBlock(self, constrainedSize);
});
} else {
return ({
AS::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec);
[self layoutSpecThatFits:constrainedSize];
});
}
}
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
__ASDisplayNodeCheckForLayoutMethodOverrides;
ASDisplayNodeAssert(NO, @"-[ASDisplayNode layoutSpecThatFits:] should never return an empty value. One way this is caused is by calling -[super layoutSpecThatFits:] which is not currently supported.");
return [[ASLayoutSpec alloc] init];
}
@end

View File

@ -0,0 +1,493 @@
//
// ASDisplayNode+Subclasses.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASBlockTypes.h>
#import <AsyncDisplayKit/ASDisplayNode.h>
#import <AsyncDisplayKit/ASDisplayNode+LayoutSpec.h>
@class ASLayoutSpec, _ASDisplayLayer;
NS_ASSUME_NONNULL_BEGIN
/**
* The subclass header _ASDisplayNode+Subclasses_ defines the following methods that either must or can be overriden by
* subclasses of ASDisplayNode.
*
* These methods should never be called directly by other classes.
*
* ## Drawing
*
* Implement one of +displayWithParameters:isCancelled: or +drawRect:withParameters:isCancelled: to provide
* drawing for your node.
*
* Use -drawParametersForAsyncLayer: to copy any properties that are involved in drawing into an immutable object for
* use on the display queue. The display and drawRect implementations *MUST* be thread-safe, as they can be called on
* the displayQueue (asynchronously) or the main thread (synchronously/displayImmediately).
*
* Class methods that require passing in copies of the values are used to minimize the need for locking around instance
* variable access, and the possibility of the asynchronous display pass grabbing an inconsistent state across multiple
* variables.
*/
@interface ASDisplayNode (Subclassing) <ASInterfaceStateDelegate>
#pragma mark - Properties
/** @name Properties */
/**
* @abstract Return the calculated layout.
*
* @discussion For node subclasses that implement manual layout (e.g., they have a custom -layout method),
* calculatedLayout may be accessed on subnodes to retrieved cached information about their size.
* This allows -layout to be very fast, saving time on the main thread.
* Note: .calculatedLayout will only be set for nodes that have had -layoutThatFits: called on them.
* For manual layout, make sure you call -layoutThatFits: in your implementation of -calculateSizeThatFits:.
*
* For node subclasses that use automatic layout (e.g., they implement -layoutSpecThatFits:),
* it is typically not necessary to use .calculatedLayout at any point. For these nodes,
* the ASLayoutSpec implementation will automatically call -layoutThatFits: on all of the subnodes,
* and the ASDisplayNode base class implementation of -layout will automatically make use of .calculatedLayout on the subnodes.
*
* @return Layout that wraps calculated size returned by -calculateSizeThatFits: (in manual layout mode),
* or layout already calculated from layout spec returned by -layoutSpecThatFits: (in automatic layout mode).
*
* @warning Subclasses must not override this; it returns the last cached layout and is never expensive.
*/
@property (nullable, readonly) ASLayout *calculatedLayout;
#pragma mark - View Lifecycle
/** @name View Lifecycle */
/**
* @abstract Called on the main thread immediately after self.view is created.
*
* @discussion This is the best time to add gesture recognizers to the view.
*/
AS_CATEGORY_IMPLEMENTABLE
- (void)didLoad ASDISPLAYNODE_REQUIRES_SUPER;
/**
* An empty method that you can implement in a category to add global
* node initialization behavior. This method will be called by [ASDisplayNode init].
*/
AS_CATEGORY_IMPLEMENTABLE
- (void)baseDidInit;
/**
* An empty method that you can implement in a category to add global
* node deallocation behavior. This method will be called by [ASDisplayNode dealloc].
*/
AS_CATEGORY_IMPLEMENTABLE
- (void)baseWillDealloc;
#pragma mark - Layout
/** @name Layout */
/**
* @abstract Called on the main thread by the view's -layoutSubviews.
*
* @discussion Subclasses override this method to layout all subnodes or subviews.
*/
- (void)layout ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Called on the main thread by the view's -layoutSubviews, after -layout.
*
* @discussion Gives a chance for subclasses to perform actions after the subclass and superclass have finished laying
* out.
*/
AS_CATEGORY_IMPLEMENTABLE
- (void)layoutDidFinish ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Called on a background thread if !isNodeLoaded - called on the main thread if isNodeLoaded.
*
* @discussion When the .calculatedLayout property is set to a new ASLayout (directly from -calculateLayoutThatFits: or
* calculated via use of -layoutSpecThatFits:), subclasses may inspect it here.
*/
AS_CATEGORY_IMPLEMENTABLE
- (void)calculatedLayoutDidChange ASDISPLAYNODE_REQUIRES_SUPER;
#pragma mark - Layout calculation
/** @name Layout calculation */
/**
* @abstract Calculate a layout based on given size range.
*
* @param constrainedSize The minimum and maximum sizes the receiver should fit in.
*
* @return An ASLayout instance defining the layout of the receiver (and its children, if the box layout model is used).
*
* @discussion This method is called on a non-main thread. The default implementation calls either -layoutSpecThatFits:
* or -calculateSizeThatFits:, whichever method is overriden. Subclasses rarely need to override this method,
* override -layoutSpecThatFits: or -calculateSizeThatFits: instead.
*
* @note This method should not be called directly outside of ASDisplayNode; use -layoutThatFits: or -calculatedLayout instead.
*/
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize;
- (CGSize)calculateSizeThatFits:(CGSize)contrainedSize;
/**
* ASDisplayNode's implementation of -layoutThatFits:parentSize: calls this method to resolve the node's size
* against parentSize, intersect it with constrainedSize, and call -calculateLayoutThatFits: with the result.
*
* In certain advanced cases, you may want to customize this logic. Overriding this method allows you to receive all
* three parameters and do the computation yourself.
*
* @warning Overriding this method should be done VERY rarely.
*/
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
restrictedToSize:(ASLayoutElementSize)size
relativeToParentSize:(CGSize)parentSize;
/**
* @abstract Return the calculated size.
*
* @param constrainedSize The maximum size the receiver should fit in.
*
* @discussion Subclasses that override should expect this method to be called on a non-main thread. The returned size
* is wrapped in an ASLayout and cached for quick access during -layout. Other expensive work that needs to
* be done before display can be performed here, and using ivars to cache any valuable intermediate results is
* encouraged.
*
* @note Subclasses that override are committed to manual layout. Therefore, -layout: must be overriden to layout all subnodes or subviews.
*
* @note This method should not be called directly outside of ASDisplayNode; use -layoutThatFits: or layoutThatFits:parentSize: instead.
*/
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize;
/**
* @abstract Invalidate previously measured and cached layout.
*
* @discussion Subclasses should call this method to invalidate the previously measured and cached layout for the display
* node, when the contents of the node change in such a way as to require measuring it again.
*/
- (void)invalidateCalculatedLayout;
#pragma mark - Observing Node State Changes
/** @name Observing node state changes */
/**
* Declare <ASInterfaceStateDelegate> methods as requiring super calls (this can't be required in the protocol).
* For descriptions, see <ASInterfaceStateDelegate> definition.
*/
AS_CATEGORY_IMPLEMENTABLE
- (void)didEnterVisibleState ASDISPLAYNODE_REQUIRES_SUPER;
AS_CATEGORY_IMPLEMENTABLE
- (void)didExitVisibleState ASDISPLAYNODE_REQUIRES_SUPER;
AS_CATEGORY_IMPLEMENTABLE
- (void)didEnterDisplayState ASDISPLAYNODE_REQUIRES_SUPER;
AS_CATEGORY_IMPLEMENTABLE
- (void)didExitDisplayState ASDISPLAYNODE_REQUIRES_SUPER;
AS_CATEGORY_IMPLEMENTABLE
- (void)didEnterPreloadState ASDISPLAYNODE_REQUIRES_SUPER;
AS_CATEGORY_IMPLEMENTABLE
- (void)didExitPreloadState ASDISPLAYNODE_REQUIRES_SUPER;
AS_CATEGORY_IMPLEMENTABLE
- (void)interfaceStateDidChange:(ASInterfaceState)newState
fromState:(ASInterfaceState)oldState ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Called when the node's ASTraitCollection changes
*
* @discussion Subclasses can override this method to react to a trait collection change.
*/
AS_CATEGORY_IMPLEMENTABLE
- (void)asyncTraitCollectionDidChange;
#pragma mark - Drawing
/** @name Drawing */
/**
* @summary Delegate method to draw layer contents into a CGBitmapContext. The current UIGraphics context will be set
* to an appropriate context.
*
* @param bounds Region to draw in.
* @param parameters An object describing all of the properties you need to draw. Return this from
* -drawParametersForAsyncLayer:
* @param isCancelledBlock Execute this block to check whether the current drawing operation has been cancelled to avoid
* unnecessary work. A return value of YES means cancel drawing and return.
* @param isRasterizing YES if the layer is being rasterized into another layer, in which case drawRect: probably wants
* to avoid doing things like filling its bounds with a zero-alpha color to clear the backing store.
*
* @note Called on the display queue and/or main queue (MUST BE THREAD SAFE)
*/
/*+ (void)drawRect:(CGRect)bounds withParameters:(nullable id)parameters
isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock
isRasterizing:(BOOL)isRasterizing;*/
/**
* @summary Delegate override to provide new layer contents as a UIImage.
*
* @param parameters An object describing all of the properties you need to draw. Return this from
* -drawParametersForAsyncLayer:
* @param isCancelledBlock Execute this block to check whether the current drawing operation has been cancelled to avoid
* unnecessary work. A return value of YES means cancel drawing and return.
*
* @return A UIImage with contents that are ready to display on the main thread. Make sure that the image is already
* decoded before returning it here.
*
* @note Called on the display queue and/or main queue (MUST BE THREAD SAFE)
*/
+ (nullable UIImage *)displayWithParameters:(nullable id)parameters
isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock;
/**
* @abstract Delegate override for drawParameters
*
* @param layer The layer that will be drawn into.
*
* @note Called on the main thread only
*/
- (nullable id<NSObject>)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer;
/**
* @abstract Indicates that the receiver is about to display.
*
* @discussion Deprecated in 2.5.
*
* @discussion Subclasses may override this method to be notified when display (asynchronous or synchronous) is
* about to begin.
*
* @note Called on the main thread only
*/
- (void)displayWillStart ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use displayWillStartAsynchronously: instead.");
/**
* @abstract Indicates that the receiver is about to display.
*
* @discussion Subclasses may override this method to be notified when display (asynchronous or synchronous) is
* about to begin.
*
* @note Called on the main thread only
*/
- (void)displayWillStartAsynchronously:(BOOL)asynchronously ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Indicates that the receiver has finished displaying.
*
* @discussion Subclasses may override this method to be notified when display (asynchronous or synchronous) has
* completed.
*
* @note Called on the main thread only
*/
- (void)displayDidFinish ASDISPLAYNODE_REQUIRES_SUPER;
/**
* Called just before the view is added to a window.
*/
- (void)willEnterHierarchy ASDISPLAYNODE_REQUIRES_SUPER;
/**
* Called after the view is removed from the window.
*/
- (void)didExitHierarchy ASDISPLAYNODE_REQUIRES_SUPER;
/**
* Called just after the view is added to a window.
* Note: this may be called multiple times during view controller transitions. To overcome this: use didEnterVisibleState or its equavalents.
*/
- (void)didEnterHierarchy ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Whether the view or layer of this display node is currently in a window
*/
@property (readonly, getter=isInHierarchy) BOOL inHierarchy;
/**
* Provides an opportunity to clear backing store and other memory-intensive intermediates, such as text layout managers
* on the current node.
*
* @discussion Called by -recursivelyClearContents. Always called on main thread. Base class implements self.contents = nil, clearing any backing
* store, for asynchronous regeneration when needed.
*/
- (void)clearContents ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Indicates that the receiver is about to display its subnodes. This method is not called if there are no
* subnodes present.
*
* @param subnode The subnode of which display is about to begin.
*
* @discussion Subclasses may override this method to be notified when subnode display (asynchronous or synchronous) is
* about to begin.
*/
- (void)subnodeDisplayWillStart:(ASDisplayNode *)subnode ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Indicates that the receiver is finished displaying its subnodes. This method is not called if there are
* no subnodes present.
*
* @param subnode The subnode of which display is about to completed.
*
* @discussion Subclasses may override this method to be notified when subnode display (asynchronous or synchronous) has
* completed.
*/
- (void)subnodeDisplayDidFinish:(ASDisplayNode *)subnode ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Marks the receiver's bounds as needing to be redrawn, with a scale value.
*
* @param contentsScale The scale at which the receiver should be drawn.
*
* @discussion Subclasses should override this if they don't want their contentsScale changed.
*
* @note This changes an internal property.
* -setNeedsDisplay is also available to trigger display without changing contentsScaleForDisplay.
* @see -setNeedsDisplay, contentsScaleForDisplay
*/
- (void)setNeedsDisplayAtScale:(CGFloat)contentsScale;
/**
* @abstract Recursively calls setNeedsDisplayAtScale: on subnodes.
*
* @param contentsScale The scale at which the receiver's subnode hierarchy should be drawn.
*
* @discussion Subclasses may override this if they require modifying the scale set on their child nodes.
*
* @note Only the node tree is walked, not the view or layer trees.
*
* @see setNeedsDisplayAtScale:
* @see contentsScaleForDisplay
*/
- (void)recursivelySetNeedsDisplayAtScale:(CGFloat)contentsScale;
/**
* @abstract The scale factor to apply to the rendering.
*
* @discussion Use setNeedsDisplayAtScale: to set a value and then after display, the display node will set the layer's
* contentsScale. This is to prevent jumps when re-rasterizing at a different contentsScale.
* Read this property if you need to know the future contentsScale of your layer, eg in drawParameters.
*
* @see setNeedsDisplayAtScale:
*/
@property (readonly) CGFloat contentsScaleForDisplay;
#pragma mark - Touch handling
/** @name Touch handling */
/**
* @abstract Tells the node when touches began in its view.
*
* @param touches A set of UITouch instances.
* @param event A UIEvent associated with the touch.
*/
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Tells the node when touches moved in its view.
*
* @param touches A set of UITouch instances.
* @param event A UIEvent associated with the touch.
*/
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Tells the node when touches ended in its view.
*
* @param touches A set of UITouch instances.
* @param event A UIEvent associated with the touch.
*/
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Tells the node when touches was cancelled in its view.
*
* @param touches A set of UITouch instances.
* @param event A UIEvent associated with the touch.
*/
- (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
#pragma mark - Managing Gesture Recognizers
/** @name Managing Gesture Recognizers */
/**
* @abstract Asks the node if a gesture recognizer should continue tracking touches.
*
* @param gestureRecognizer A gesture recognizer trying to recognize a gesture.
*/
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
#pragma mark - Hit Testing
/** @name Hit Testing */
/**
* @abstract Returns the view that contains the point.
*
* @discussion Override to make this node respond differently to touches: (e.g. hide touches from subviews, send all
* touches to certain subviews (hit area maximizing), etc.)
*
* @param point A point specified in the node's local coordinate system (bounds).
* @param event The event that warranted a call to this method.
*
* @return Returns a UIView, not ASDisplayNode, for two reasons:
* 1) allows sending events to plain UIViews that don't have attached nodes,
* 2) hitTest: is never called before the views are created.
*/
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
#pragma mark - Placeholders
/** @name Placeholders */
/**
* @abstract Optionally provide an image to serve as the placeholder for the backing store while the contents are being
* displayed.
*
* @discussion
* Subclasses may override this method and return an image to use as the placeholder. Take caution as there may be a
* time and place where this method is called on a background thread. Note that -[UIImage imageNamed:] is not thread
* safe when using image assets.
*
* To retrieve the CGSize to do any image drawing, use the node's calculatedSize property.
*
* Defaults to nil.
*
* @note Called on the display queue and/or main queue (MUST BE THREAD SAFE)
*/
- (nullable UIImage *)placeholderImage;
#pragma mark - Description
/** @name Description */
/**
* @abstract Return a description of the node
*
* @discussion The function that gets called for each display node in -recursiveDescription
*/
- (NSString *)descriptionForRecursiveDescription;
@end
// Check that at most a layoutSpecBlock or one of the three layout methods is overridden
#define __ASDisplayNodeCheckForLayoutMethodOverrides \
ASDisplayNodeAssert(_layoutSpecBlock != NULL || \
((ASDisplayNodeSubclassOverridesSelector(self.class, @selector(calculateSizeThatFits:)) ? 1 : 0) \
+ (ASDisplayNodeSubclassOverridesSelector(self.class, @selector(layoutSpecThatFits:)) ? 1 : 0) \
+ (ASDisplayNodeSubclassOverridesSelector(self.class, @selector(calculateLayoutThatFits:)) ? 1 : 0)) <= 1, \
@"Subclass %@ must at least provide a layoutSpecBlock or override at most one of the three layout methods: calculateLayoutThatFits:, layoutSpecThatFits:, or calculateSizeThatFits:", NSStringFromClass(self.class))
#define ASDisplayNodeAssertThreadAffinity(viewNode) ASDisplayNodeAssert(!viewNode || ASMainThreadAssertionsAreDisabled() || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created")
#define ASDisplayNodeCAssertThreadAffinity(viewNode) ASDisplayNodeCAssert(!viewNode || ASMainThreadAssertionsAreDisabled() || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created")
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,102 @@
//
// ASDisplayNode+Yoga.h
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASAvailability.h>
#if YOGA
NS_ASSUME_NONNULL_BEGIN
@class ASLayout;
AS_EXTERN void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullable node, void(^block)(ASDisplayNode *node));
@interface ASDisplayNode (Yoga)
@property (copy) NSArray *yogaChildren;
- (void)addYogaChild:(ASDisplayNode *)child;
- (void)removeYogaChild:(ASDisplayNode *)child;
- (void)insertYogaChild:(ASDisplayNode *)child atIndex:(NSUInteger)index;
- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute;
@property BOOL yogaLayoutInProgress;
// TODO: Make this atomic (lock).
@property (nullable, nonatomic) ASLayout *yogaCalculatedLayout;
// Will walk up the Yoga tree and returns the root node
- (ASDisplayNode *)yogaRoot;
@end
@interface ASDisplayNode (YogaLocking)
/**
* @discussion Attempts(spinning) to lock all node up to root node when yoga is enabled.
* This will lock self when yoga is not enabled;
*/
- (ASLockSet)lockToRootIfNeededForLayout;
@end
// These methods are intended to be used internally to Texture, and should not be called directly.
@interface ASDisplayNode (YogaInternal)
/// For internal usage only
- (BOOL)shouldHaveYogaMeasureFunc;
/// For internal usage only
- (ASLayout *)calculateLayoutYoga:(ASSizeRange)constrainedSize;
/// For internal usage only
- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize;
/// For internal usage only
- (void)invalidateCalculatedYogaLayout;
/**
* @discussion return true only when yoga enabled and the node is in yoga tree and the node is
* not leaf that implemented measure function.
*/
- (BOOL)locked_shouldLayoutFromYogaRoot;
@end
@interface ASDisplayNode (YogaDebugging)
- (NSString *)yogaTreeDescription;
@end
@interface ASLayoutElementStyle (Yoga)
- (YGNodeRef)yogaNodeCreateIfNeeded;
- (void)destroyYogaNode;
@property (readonly) YGNodeRef yogaNode;
@property ASStackLayoutDirection flexDirection;
@property YGDirection direction;
@property ASStackLayoutJustifyContent justifyContent;
@property ASStackLayoutAlignItems alignItems;
@property YGPositionType positionType;
@property ASEdgeInsets position;
@property ASEdgeInsets margin;
@property ASEdgeInsets padding;
@property ASEdgeInsets border;
@property CGFloat aspectRatio;
@property YGWrap flexWrap;
@end
NS_ASSUME_NONNULL_END
// When Yoga is enabled, there are several points where we want to lock the tree to the root but otherwise (without Yoga)
// will want to simply lock self.
#define ASScopedLockSelfOrToRoot() ASScopedLockSet lockSet = [self lockToRootIfNeededForLayout]
#else
#define ASScopedLockSelfOrToRoot() ASLockScopeSelf()
#endif

View File

@ -0,0 +1,472 @@
//
// ASDisplayNode+Yoga.mm
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASAvailability.h>
#if YOGA /* YOGA */
#import <AsyncDisplayKit/_ASDisplayViewAccessiblity.h>
#import <AsyncDisplayKit/ASYogaUtilities.h>
#import <AsyncDisplayKit/ASCollections.h>
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASLayoutElementStylePrivate.h>
#import <AsyncDisplayKit/ASNodeController+Beta.h>
#import <AsyncDisplayKit/ASDisplayNode+LayoutSpec.h>
#define YOGA_LAYOUT_LOGGING 0
#pragma mark - ASDisplayNode+Yoga
@interface ASDisplayNode (YogaPrivate)
@property (nonatomic, weak) ASDisplayNode *yogaParent;
- (ASSizeRange)_locked_constrainedSizeForLayoutPass;
@end
@implementation ASDisplayNode (Yoga)
- (ASDisplayNode *)yogaRoot
{
ASDisplayNode *yogaRoot = self;
ASDisplayNode *yogaParent = nil;
while ((yogaParent = yogaRoot.yogaParent)) {
yogaRoot = yogaParent;
}
return yogaRoot;
}
- (void)setYogaChildren:(NSArray *)yogaChildren
{
ASScopedLockSelfOrToRoot();
for (ASDisplayNode *child in [_yogaChildren copy]) {
// Make sure to un-associate the YGNodeRef tree before replacing _yogaChildren
// If this becomes a performance bottleneck, it can be optimized by not doing the NSArray removals here.
[self _locked_removeYogaChild:child];
}
_yogaChildren = nil;
for (ASDisplayNode *child in yogaChildren) {
[self _locked_addYogaChild:child];
}
}
- (NSArray *)yogaChildren
{
ASLockScope(self.yogaRoot);
return [_yogaChildren copy] ?: @[];
}
- (void)addYogaChild:(ASDisplayNode *)child
{
ASScopedLockSelfOrToRoot();
[self _locked_addYogaChild:child];
}
- (void)_locked_addYogaChild:(ASDisplayNode *)child
{
[self insertYogaChild:child atIndex:_yogaChildren.count];
}
- (void)removeYogaChild:(ASDisplayNode *)child
{
ASScopedLockSelfOrToRoot();
[self _locked_removeYogaChild:child];
}
- (void)_locked_removeYogaChild:(ASDisplayNode *)child
{
if (child == nil) {
return;
}
[_yogaChildren removeObjectIdenticalTo:child];
// YGNodeRef removal is done in setParent:
child.yogaParent = nil;
}
- (void)insertYogaChild:(ASDisplayNode *)child atIndex:(NSUInteger)index
{
ASScopedLockSelfOrToRoot();
[self _locked_insertYogaChild:child atIndex:index];
}
- (void)_locked_insertYogaChild:(ASDisplayNode *)child atIndex:(NSUInteger)index
{
if (child == nil) {
return;
}
if (_yogaChildren == nil) {
_yogaChildren = [[NSMutableArray alloc] init];
}
// Clean up state in case this child had another parent.
[self _locked_removeYogaChild:child];
[_yogaChildren insertObject:child atIndex:index];
// YGNodeRef insertion is done in setParent:
child.yogaParent = self;
}
#pragma mark - Subclass Hooks
- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute
{
UIUserInterfaceLayoutDirection layoutDirection =
[UIView userInterfaceLayoutDirectionForSemanticContentAttribute:attribute];
self.style.direction = (layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight
? YGDirectionLTR : YGDirectionRTL);
}
- (void)setYogaParent:(ASDisplayNode *)yogaParent
{
ASLockScopeSelf();
if (_yogaParent == yogaParent) {
return;
}
YGNodeRef yogaNode = [self.style yogaNodeCreateIfNeeded];
YGNodeRef oldParentRef = YGNodeGetParent(yogaNode);
if (oldParentRef != NULL) {
YGNodeRemoveChild(oldParentRef, yogaNode);
}
_yogaParent = yogaParent;
if (yogaParent) {
YGNodeRef newParentRef = [yogaParent.style yogaNodeCreateIfNeeded];
YGNodeInsertChild(newParentRef, yogaNode, YGNodeGetChildCount(newParentRef));
}
}
- (ASDisplayNode *)yogaParent
{
return _yogaParent;
}
- (void)setYogaCalculatedLayout:(ASLayout *)yogaCalculatedLayout
{
_yogaCalculatedLayout = yogaCalculatedLayout;
}
- (ASLayout *)yogaCalculatedLayout
{
return _yogaCalculatedLayout;
}
- (void)setYogaLayoutInProgress:(BOOL)yogaLayoutInProgress
{
setFlag(YogaLayoutInProgress, yogaLayoutInProgress);
[self updateYogaMeasureFuncIfNeeded];
}
- (BOOL)yogaLayoutInProgress
{
return checkFlag(YogaLayoutInProgress);
}
- (ASLayout *)layoutForYogaNode
{
YGNodeRef yogaNode = self.style.yogaNode;
CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode));
CGPoint position = CGPointMake(YGNodeLayoutGetLeft(yogaNode), YGNodeLayoutGetTop(yogaNode));
if (!ASIsCGSizeValidForSize(size)) {
size = CGSizeZero;
}
if (!ASIsCGPositionValidForLayout(position)) {
position = CGPointZero;
}
return [ASLayout layoutWithLayoutElement:self size:size position:position sublayouts:nil];
}
- (void)setupYogaCalculatedLayout
{
ASScopedLockSelfOrToRoot();
YGNodeRef yogaNode = self.style.yogaNode;
uint32_t childCount = YGNodeGetChildCount(yogaNode);
ASDisplayNodeAssert(childCount == _yogaChildren.count,
@"Yoga tree should always be in sync with .yogaNodes array! %@",
_yogaChildren);
ASLayout *rawSublayouts[childCount];
int i = 0;
for (ASDisplayNode *subnode in _yogaChildren) {
rawSublayouts[i++] = [subnode layoutForYogaNode];
}
const auto sublayouts = [NSArray<ASLayout *> arrayByTransferring:rawSublayouts count:childCount];
// The layout for self should have position CGPointNull, but include the calculated size.
CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode));
if (!ASIsCGSizeValidForSize(size)) {
size = CGSizeZero;
}
ASLayout *layout = [ASLayout layoutWithLayoutElement:self size:size sublayouts:sublayouts];
#if ASDISPLAYNODE_ASSERTIONS_ENABLED
// Assert that the sublayout is already flattened.
for (ASLayout *sublayout in layout.sublayouts) {
if (sublayout.sublayouts.count > 0 || ASDynamicCast(sublayout.layoutElement, ASDisplayNode) == nil) {
ASDisplayNodeAssert(NO, @"Yoga sublayout is not flattened! %@, %@", self, sublayout);
}
}
#endif
// Because this layout won't go through the rest of the logic in calculateLayoutThatFits:, flatten it now.
layout = [layout filteredNodeLayoutTree];
if ([self.yogaCalculatedLayout isEqual:layout] == NO) {
self.yogaCalculatedLayout = layout;
} else {
layout = self.yogaCalculatedLayout;
ASYogaLog("-setupYogaCalculatedLayout: applying identical ASLayout: %@", layout);
}
// Setup _pendingDisplayNodeLayout to reference the Yoga-calculated ASLayout, *unless* we are a leaf node.
// Leaf yoga nodes may have their own .sublayouts, if they use a layout spec (such as ASButtonNode).
// Their _pending variable is set after passing the Yoga checks at the start of -calculateLayoutThatFits:
// For other Yoga nodes, there is no code that will set _pending unless we do it here. Why does it need to be set?
// When CALayer triggers the -[ASDisplayNode __layout] call, we will check if our current _pending layout
// has a size which matches our current bounds size. If it does, that layout will be used without recomputing it.
// NOTE: Yoga does not make the constrainedSize available to intermediate nodes in the tree (e.g. not root or leaves).
// Although the size range provided here is not accurate, this will only affect caching of calls to layoutThatFits:
// These calls will behave as if they are not cached, starting a new Yoga layout pass, but this will tap into Yoga's
// own internal cache.
if ([self shouldHaveYogaMeasureFunc] == NO) {
YGNodeRef parentNode = YGNodeGetParent(yogaNode);
CGSize parentSize = CGSizeZero;
if (parentNode) {
parentSize.width = YGNodeLayoutGetWidth(parentNode);
parentSize.height = YGNodeLayoutGetHeight(parentNode);
}
// For the root node in a Yoga tree, make sure to preserve the constrainedSize originally provided.
// This will be used for all relayouts triggered by children, since they escalate to root.
ASSizeRange range = parentNode ? ASSizeRangeUnconstrained : self.constrainedSizeForCalculatedLayout;
_pendingDisplayNodeLayout = ASDisplayNodeLayout(layout, range, parentSize, _layoutVersion);
}
}
- (BOOL)shouldHaveYogaMeasureFunc
{
ASLockScopeSelf();
// Size calculation via calculateSizeThatFits: or layoutSpecThatFits:
// For these nodes, we assume they may need custom Baseline calculation too.
// This will be used for ASTextNode, as well as any other node that has no Yoga children
BOOL isLeafNode = (_yogaChildren.count == 0);
BOOL definesCustomLayout = [self implementsLayoutMethod];
return (isLeafNode && definesCustomLayout);
}
- (void)updateYogaMeasureFuncIfNeeded
{
// We set the measure func only during layout. Otherwise, a cycle is created:
// The YGNodeRef Context will retain the ASDisplayNode, which retains the style, which owns the YGNodeRef.
BOOL shouldHaveMeasureFunc = ([self shouldHaveYogaMeasureFunc] && checkFlag(YogaLayoutInProgress));
ASLayoutElementYogaUpdateMeasureFunc(self.style.yogaNode, shouldHaveMeasureFunc ? self : nil);
}
- (void)invalidateCalculatedYogaLayout
{
ASLockScopeSelf();
YGNodeRef yogaNode = self.style.yogaNode;
if (yogaNode && [self shouldHaveYogaMeasureFunc]) {
// Yoga internally asserts that MarkDirty() may only be called on nodes with a measurement function.
BOOL needsTemporaryMeasureFunc = (YGNodeGetMeasureFunc(yogaNode) == NULL);
if (needsTemporaryMeasureFunc) {
ASDisplayNodeAssert(self.yogaLayoutInProgress == NO,
@"shouldHaveYogaMeasureFunc == YES, and inside a layout pass, but no measure func pointer! %@", self);
YGNodeSetMeasureFunc(yogaNode, &ASLayoutElementYogaMeasureFunc);
}
YGNodeMarkDirty(yogaNode);
if (needsTemporaryMeasureFunc) {
YGNodeSetMeasureFunc(yogaNode, NULL);
}
}
self.yogaCalculatedLayout = nil;
}
- (ASLayout *)calculateLayoutYoga:(ASSizeRange)constrainedSize
{
AS::UniqueLock l(__instanceLock__);
// There are several cases where Yoga could arrive here:
// - This node is not in a Yoga tree: it has neither a yogaParent nor yogaChildren.
// - This node is a Yoga tree root: it has no yogaParent, but has yogaChildren.
// - This node is a Yoga tree node: it has both a yogaParent and yogaChildren.
// - This node is a Yoga tree leaf: it has a yogaParent, but no yogaChidlren.
if ([self locked_shouldLayoutFromYogaRoot]) {
// If we're a yoga root, tree node, or leaf with no measure func (e.g. spacer), then
// initiate a new Yoga calculation pass from root.
as_activity_create_for_scope("Yoga layout calculation");
if (self.yogaLayoutInProgress == NO) {
ASYogaLog("Calculating yoga layout from root %@, %@", self,
NSStringFromASSizeRange(constrainedSize));
[self calculateLayoutFromYogaRoot:constrainedSize];
} else {
ASYogaLog("Reusing existing yoga layout %@", _yogaCalculatedLayout);
}
ASDisplayNodeAssert(_yogaCalculatedLayout,
@"Yoga node should have a non-nil layout at this stage: %@", self);
return _yogaCalculatedLayout;
} else {
// If we're a yoga leaf node with custom measurement function, proceed with normal layout so
// layoutSpecs can run (e.g. ASButtonNode).
ASYogaLog("PROCEEDING past Yoga check to calculate ASLayout for: %@", self);
}
// Delegate to layout spec layout for nodes that do not support Yoga
return [self calculateLayoutLayoutSpec:constrainedSize];
}
- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize
{
ASScopedLockSet lockSet = [self lockToRootIfNeededForLayout];
ASDisplayNode *yogaRoot = self.yogaRoot;
if (self != yogaRoot) {
ASYogaLog("ESCALATING to Yoga root: %@", self);
// TODO(appleguy): Consider how to get the constrainedSize for the yogaRoot when escalating manually.
[yogaRoot calculateLayoutFromYogaRoot:ASSizeRangeUnconstrained];
return;
}
if (ASSizeRangeEqualToSizeRange(rootConstrainedSize, ASSizeRangeUnconstrained)) {
rootConstrainedSize = [self _locked_constrainedSizeForLayoutPass];
}
[self willCalculateLayout:rootConstrainedSize];
[self enumerateInterfaceStateDelegates:^(id<ASInterfaceStateDelegate> _Nonnull delegate) {
if ([delegate respondsToSelector:@selector(nodeWillCalculateLayout:)]) {
[delegate nodeWillCalculateLayout:rootConstrainedSize];
}
}];
// Prepare all children for the layout pass with the current Yoga tree configuration.
ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode *_Nonnull node) {
node.yogaLayoutInProgress = YES;
ASDisplayNode *yogaParent = node.yogaParent;
if (yogaParent) {
node.style.parentAlignStyle = yogaParent.style.alignItems;
} else {
node.style.parentAlignStyle = ASStackLayoutAlignItemsNotSet;
};
});
ASYogaLog("CALCULATING at Yoga root with constraint = {%@, %@}: %@",
NSStringFromCGSize(rootConstrainedSize.min), NSStringFromCGSize(rootConstrainedSize.max), self);
YGNodeRef rootYogaNode = self.style.yogaNode;
// Apply the constrainedSize as a base, known frame of reference.
// If the root node also has style.*Size set, these will be overridden below.
// YGNodeCalculateLayout currently doesn't offer the ability to pass a minimum size (max is passed there).
// TODO(appleguy): Reconcile the self.style.*Size properties with rootConstrainedSize
YGNodeStyleSetMinWidth (rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.min.width));
YGNodeStyleSetMinHeight(rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.min.height));
// It is crucial to use yogaFloat... to convert CGFLOAT_MAX into YGUndefined here.
YGNodeCalculateLayout(rootYogaNode,
yogaFloatForCGFloat(rootConstrainedSize.max.width),
yogaFloatForCGFloat(rootConstrainedSize.max.height),
YGDirectionInherit);
// Reset accessible elements, since layout may have changed.
ASPerformBlockOnMainThread(^{
if (self.nodeLoaded && !self.isSynchronous) {
[(_ASDisplayView *)self.view setAccessibilityElements:nil];
}
});
ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) {
[node setupYogaCalculatedLayout];
node.yogaLayoutInProgress = NO;
});
#if YOGA_LAYOUT_LOGGING /* YOGA_LAYOUT_LOGGING */
// Concurrent layouts will interleave the NSLog messages unless we serialize.
// Use @synchornize rather than trampolining to the main thread so the tree state isn't changed.
@synchronized ([ASDisplayNode class]) {
NSLog(@"****************************************************************************");
NSLog(@"******************** STARTING YOGA -> ASLAYOUT CREATION ********************");
NSLog(@"****************************************************************************");
ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) {
NSLog(@"node = %@", node);
YGNodePrint(node.style.yogaNode, (YGPrintOptions)(YGPrintOptionsStyle | YGPrintOptionsLayout));
NSCAssert(ASIsCGSizeValidForSize(node.yogaCalculatedLayout.size), @"Yoga layout returned an invalid size");
NSLog(@" "); // Newline
});
}
#endif /* YOGA_LAYOUT_LOGGING */
}
@end
#pragma mark - ASDisplayNode (YogaLocking)
@implementation ASDisplayNode (YogaLocking)
- (ASLockSet)lockToRootIfNeededForLayout {
ASLockSet lockSet = ASLockSequence(^BOOL(ASAddLockBlock addLock) {
if (!addLock(self)) {
return NO;
}
#if YOGA
if (![self locked_shouldLayoutFromYogaRoot]) {
return YES;
}
if (self.nodeController && !addLock(self.nodeController)) {
return NO;
}
ASDisplayNode *parent = _supernode;
while (parent) {
if (!addLock(parent)) {
return NO;
}
if (parent.nodeController && !addLock(parent.nodeController)) {
return NO;
}
parent = parent->_supernode;
}
#endif
return true;
});
return lockSet;
}
@end
@implementation ASDisplayNode (YogaDebugging)
- (NSString *)yogaTreeDescription {
return [self _yogaTreeDescription:@""];
}
- (NSString *)_yogaTreeDescription:(NSString *)indent {
auto subtree = [NSMutableString stringWithFormat:@"%@%@\n", indent, self.description];
for (ASDisplayNode *n in self.yogaChildren) {
[subtree appendString:[n _yogaTreeDescription:[indent stringByAppendingString:@"| "]]];
}
return subtree;
}
@end
#endif /* YOGA */

View File

@ -0,0 +1,986 @@
//
// ASDisplayNode.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#pragma once
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/_ASAsyncTransactionContainer.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASDisplayNode+InterfaceState.h>
#import <AsyncDisplayKit/ASAsciiArtBoxCreator.h>
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
#import <AsyncDisplayKit/ASLayoutElement.h>
#import <AsyncDisplayKit/ASLocking.h>
#import <AsyncDisplayKit/ASBlockTypes.h>
NS_ASSUME_NONNULL_BEGIN
#define ASDisplayNodeLoggingEnabled 0
#ifndef AS_MAX_INTERFACE_STATE_DELEGATES
#define AS_MAX_INTERFACE_STATE_DELEGATES 4
#endif
@class ASDisplayNode;
@protocol ASContextTransitioning;
/**
* UIView creation block. Used to create the backing view of a new display node.
*/
typedef UIView * _Nonnull(^ASDisplayNodeViewBlock)(void);
/**
* UIView creation block. Used to create the backing view of a new display node.
*/
typedef UIViewController * _Nonnull(^ASDisplayNodeViewControllerBlock)(void);
/**
* CALayer creation block. Used to create the backing layer of a new display node.
*/
typedef CALayer * _Nonnull(^ASDisplayNodeLayerBlock)(void);
/**
* ASDisplayNode loaded callback block. This block is called BEFORE the -didLoad method and is always called on the main thread.
*/
typedef void (^ASDisplayNodeDidLoadBlock)(__kindof ASDisplayNode * node);
/**
* ASDisplayNode will / did render node content in context.
*/
typedef void (^ASDisplayNodeContextModifier)(CGContextRef context, id _Nullable drawParameters);
/**
* ASDisplayNode layout spec block. This block can be used instead of implementing layoutSpecThatFits: in subclass
*/
typedef ASLayoutSpec * _Nonnull(^ASLayoutSpecBlock)(__kindof ASDisplayNode *node, ASSizeRange constrainedSize);
/**
* AsyncDisplayKit non-fatal error block. This block can be used for handling non-fatal errors. Useful for reporting
* errors that happens in production.
*/
typedef void (^ASDisplayNodeNonFatalErrorBlock)(NSError *error);
typedef NS_ENUM(NSInteger, ASCornerRoundingType) {
ASCornerRoundingTypeDefaultSlowCALayer,
ASCornerRoundingTypePrecomposited,
ASCornerRoundingTypeClipping
};
/**
* Default drawing priority for display node
*/
AS_EXTERN NSInteger const ASDefaultDrawingPriority;
/**
* An `ASDisplayNode` is an abstraction over `UIView` and `CALayer` that allows you to perform calculations about a view
* hierarchy off the main thread, and could do rendering off the main thread as well.
*
* The node API is designed to be as similar as possible to `UIView`. See the README for examples.
*
* ## Subclassing
*
* `ASDisplayNode` can be subclassed to create a new UI element. The subclass header `ASDisplayNode+Subclasses` provides
* necessary declarations and conveniences.
*
* Commons reasons to subclass includes making a `UIView` property available and receiving a callback after async
* display.
*
*/
@interface ASDisplayNode : NSObject <ASLocking> {
@public
/**
* The _displayNodeContext ivar is unused by Texture, but provided to enable advanced clients to make powerful extensions to base class functionality.
* For example, _displayNodeContext can be used to implement category methods on ASDisplayNode that add functionality to all node subclass types.
* Code demonstrating this technique can be found in the CatDealsCollectionView example.
*/
void *_displayNodeContext;
}
+ (void)drawRect:(CGRect)bounds withParameters:(nullable id)parameters
isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock
isRasterizing:(BOOL)isRasterizing;
+ (nullable UIImage *)displayWithParameters:(nullable id)parameters
isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock;
/** @name Initializing a node object */
/**
* @abstract Designated initializer.
*
* @return An ASDisplayNode instance whose view will be a subclass that enables asynchronous rendering, and passes
* through -layout and touch handling methods.
*/
- (instancetype)init NS_DESIGNATED_INITIALIZER;
/**
* @abstract Alternative initializer with a block to create the backing view.
*
* @param viewBlock The block that will be used to create the backing view.
*
* @return An ASDisplayNode instance that loads its view with the given block that is guaranteed to run on the main
* queue. The view will render synchronously and -layout and touch handling methods on the node will not be called.
*/
- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock;
/**
* @abstract Alternative initializer with a block to create the backing view.
*
* @param viewBlock The block that will be used to create the backing view.
* @param didLoadBlock The block that will be called after the view created by the viewBlock is loaded
*
* @return An ASDisplayNode instance that loads its view with the given block that is guaranteed to run on the main
* queue. The view will render synchronously and -layout and touch handling methods on the node will not be called.
*/
- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_DESIGNATED_INITIALIZER;
/**
* @abstract Alternative initializer with a block to create the backing layer.
*
* @param layerBlock The block that will be used to create the backing layer.
*
* @return An ASDisplayNode instance that loads its layer with the given block that is guaranteed to run on the main
* queue. The layer will render synchronously and -layout and touch handling methods on the node will not be called.
*/
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock;
/**
* @abstract Alternative initializer with a block to create the backing layer.
*
* @param layerBlock The block that will be used to create the backing layer.
* @param didLoadBlock The block that will be called after the layer created by the layerBlock is loaded
*
* @return An ASDisplayNode instance that loads its layer with the given block that is guaranteed to run on the main
* queue. The layer will render synchronously and -layout and touch handling methods on the node will not be called.
*/
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_DESIGNATED_INITIALIZER;
/**
* @abstract Add a block of work to be performed on the main thread when the node's view or layer is loaded. Thread safe.
* @warning Be careful not to retain self in `body`. Change the block parameter list to `^(MYCustomNode *self) {}` if you
* want to shadow self (e.g. if calling this during `init`).
*
* @param body The work to be performed when the node is loaded.
*
* @precondition The node is not already loaded.
*/
- (void)onDidLoad:(ASDisplayNodeDidLoadBlock)body;
/**
* Set the block that should be used to load this node's view.
*
* @param viewBlock The block that creates a view for this node.
*
* @precondition The node is not yet loaded.
*
* @note You will usually NOT call this. See the limitations documented in @c initWithViewBlock:
*/
- (void)setViewBlock:(ASDisplayNodeViewBlock)viewBlock;
/**
* Set the block that should be used to load this node's layer.
*
* @param layerBlock The block that creates a layer for this node.
*
* @precondition The node is not yet loaded.
*
* @note You will usually NOT call this. See the limitations documented in @c initWithLayerBlock:
*/
- (void)setLayerBlock:(ASDisplayNodeLayerBlock)layerBlock;
/**
* @abstract Returns whether the node is synchronous.
*
* @return NO if the node wraps a _ASDisplayView, YES otherwise.
*/
@property (readonly, getter=isSynchronous) BOOL synchronous;
/** @name Getting view and layer */
/**
* @abstract Returns a view.
*
* @discussion The view property is lazily initialized, similar to UIViewController.
* To go the other direction, use ASViewToDisplayNode() in ASDisplayNodeExtras.h.
*
* @warning The first access to it must be on the main thread, and should only be used on the main thread thereafter as
* well.
*/
@property (readonly) UIView *view;
/**
* @abstract Returns whether a node's backing view or layer is loaded.
*
* @return YES if a view is loaded, or if layerBacked is YES and layer is not nil; NO otherwise.
*/
@property (readonly, getter=isNodeLoaded) BOOL nodeLoaded;
/**
* @abstract Returns whether the node rely on a layer instead of a view.
*
* @return YES if the node rely on a layer, NO otherwise.
*/
@property (getter=isLayerBacked) BOOL layerBacked;
/**
* @abstract Returns a layer.
*
* @discussion The layer property is lazily initialized, similar to the view property.
* To go the other direction, use ASLayerToDisplayNode() in ASDisplayNodeExtras.h.
*
* @warning The first access to it must be on the main thread, and should only be used on the main thread thereafter as
* well.
*/
@property (readonly) CALayer * layer;
/**
* Returns YES if the node is at least partially visible in a window.
*
* @see didEnterVisibleState and didExitVisibleState
*/
@property (readonly, getter=isVisible) BOOL visible;
/**
* Returns YES if the node is in the preloading interface state.
*
* @see didEnterPreloadState and didExitPreloadState
*/
@property (readonly, getter=isInPreloadState) BOOL inPreloadState;
/**
* Returns YES if the node is in the displaying interface state.
*
* @see didEnterDisplayState and didExitDisplayState
*/
@property (readonly, getter=isInDisplayState) BOOL inDisplayState;
/**
* @abstract Returns the Interface State of the node.
*
* @return The current ASInterfaceState of the node, indicating whether it is visible and other situational properties.
*
* @see ASInterfaceState
*/
@property (readonly) ASInterfaceState interfaceState;
/**
* @abstract Adds a delegate to receive notifications on interfaceState changes.
*
* @warning This must be called from the main thread.
* There is a hard limit on the number of delegates a node can have; see
* AS_MAX_INTERFACE_STATE_DELEGATES above.
*
* @see ASInterfaceState
*/
- (void)addInterfaceStateDelegate:(id <ASInterfaceStateDelegate>)interfaceStateDelegate;
/**
* @abstract Removes a delegate from receiving notifications on interfaceState changes.
*
* @warning This must be called from the main thread.
*
* @see ASInterfaceState
*/
- (void)removeInterfaceStateDelegate:(id <ASInterfaceStateDelegate>)interfaceStateDelegate;
/**
* @abstract Class property that allows to set a block that can be called on non-fatal errors. This
* property can be useful for cases when Async Display Kit can recover from an abnormal behavior, but
* still gives the opportunity to use a reporting mechanism to catch occurrences in production. In
* development, Async Display Kit will assert instead of calling this block.
*
* @warning This method is not thread-safe.
*/
@property (class, nonatomic) ASDisplayNodeNonFatalErrorBlock nonFatalErrorBlock;
/** @name Managing the nodes hierarchy */
/**
* @abstract Add a node as a subnode to this node.
*
* @param subnode The node to be added.
*
* @discussion The subnode's view will automatically be added to this node's view, lazily if the views are not created
* yet.
*/
- (void)addSubnode:(ASDisplayNode *)subnode;
/**
* @abstract Insert a subnode before a given subnode in the list.
*
* @param subnode The node to insert below another node.
* @param below The sibling node that will be above the inserted node.
*
* @discussion If the views are loaded, the subnode's view will be inserted below the given node's view in the hierarchy
* even if there are other non-displaynode views.
*/
- (void)insertSubnode:(ASDisplayNode *)subnode belowSubnode:(ASDisplayNode *)below;
/**
* @abstract Insert a subnode after a given subnode in the list.
*
* @param subnode The node to insert below another node.
* @param above The sibling node that will be behind the inserted node.
*
* @discussion If the views are loaded, the subnode's view will be inserted above the given node's view in the hierarchy
* even if there are other non-displaynode views.
*/
- (void)insertSubnode:(ASDisplayNode *)subnode aboveSubnode:(ASDisplayNode *)above;
/**
* @abstract Insert a subnode at a given index in subnodes.
*
* @param subnode The node to insert.
* @param idx The index in the array of the subnodes property at which to insert the node. Subnodes indices start at 0
* and cannot be greater than the number of subnodes.
*
* @discussion If this node's view is loaded, ASDisplayNode insert the subnode's view after the subnode at index - 1's
* view even if there are other non-displaynode views.
*/
- (void)insertSubnode:(ASDisplayNode *)subnode atIndex:(NSInteger)idx;
/**
* @abstract Replace subnode with replacementSubnode.
*
* @param subnode A subnode of self.
* @param replacementSubnode A node with which to replace subnode.
*
* @discussion Should both subnode and replacementSubnode already be subnodes of self, subnode is removed and
* replacementSubnode inserted in its place.
* If subnode is not a subnode of self, this method will throw an exception.
* If replacementSubnode is nil, this method will throw an exception
*/
- (void)replaceSubnode:(ASDisplayNode *)subnode withSubnode:(ASDisplayNode *)replacementSubnode;
/**
* @abstract Remove this node from its supernode.
*
* @discussion The node's view will be automatically removed from the supernode's view.
*/
- (void)removeFromSupernode;
/**
* @abstract The receiver's immediate subnodes.
*/
@property (nullable, readonly, copy) NSArray<ASDisplayNode *> *subnodes;
/**
* @abstract The receiver's supernode.
*/
@property (nullable, readonly, weak) ASDisplayNode *supernode;
/** @name Drawing and Updating the View */
/**
* @abstract Whether this node's view performs asynchronous rendering.
*
* @return Defaults to YES, except for synchronous views (ie, those created with -initWithViewBlock: /
* -initWithLayerBlock:), which are always NO.
*
* @discussion If this flag is set, then the node will participate in the current asyncdisplaykit_async_transaction and
* do its rendering on the displayQueue instead of the main thread.
*
* Asynchronous rendering proceeds as follows:
*
* When the view is initially added to the hierarchy, it has -needsDisplay true.
* After layout, Core Animation will call -display on the _ASDisplayLayer
* -display enqueues a rendering operation on the displayQueue
* When the render block executes, it calls the delegate display method (-drawRect:... or -display)
* The delegate provides contents via this method and an operation is added to the asyncdisplaykit_async_transaction
* Once all rendering is complete for the current asyncdisplaykit_async_transaction,
* the completion for the block sets the contents on all of the layers in the same frame
*
* If asynchronous rendering is disabled:
*
* When the view is initially added to the hierarchy, it has -needsDisplay true.
* After layout, Core Animation will call -display on the _ASDisplayLayer
* -display calls delegate display method (-drawRect:... or -display) immediately
* -display sets the layer contents immediately with the result
*
* Note: this has nothing to do with -[CALayer drawsAsynchronously].
*/
@property BOOL displaysAsynchronously;
/**
* @abstract Prevent the node's layer from displaying.
*
* @discussion A subclass may check this flag during -display or -drawInContext: to cancel a display that is already in
* progress.
*
* Defaults to NO. Does not control display for any child or descendant nodes; for that, use
* -recursivelySetDisplaySuspended:.
*
* If a setNeedsDisplay occurs while displaySuspended is YES, and displaySuspended is set to NO, then the
* layer will be automatically displayed.
*/
@property BOOL displaySuspended;
/**
* @abstract Whether size changes should be animated. Default to YES.
*/
@property BOOL shouldAnimateSizeChanges;
/**
* @abstract Prevent the node and its descendants' layer from displaying.
*
* @param flag YES if display should be prevented or cancelled; NO otherwise.
*
* @see displaySuspended
*/
- (void)recursivelySetDisplaySuspended:(BOOL)flag;
/**
* @abstract Calls -clearContents on the receiver and its subnode hierarchy.
*
* @discussion Clears backing stores and other memory-intensive intermediates.
* If the node is removed from a visible hierarchy and then re-added, it will automatically trigger a new asynchronous display,
* as long as displaySuspended is not set.
* If the node remains in the hierarchy throughout, -setNeedsDisplay is required to trigger a new asynchronous display.
*
* @see displaySuspended and setNeedsDisplay
*/
- (void)recursivelyClearContents;
/**
* @abstract Toggle displaying a placeholder over the node that covers content until the node and all subnodes are
* displayed.
*
* @discussion Defaults to NO.
*/
@property BOOL placeholderEnabled;
/**
* @abstract Set the time it takes to fade out the placeholder when a node's contents are finished displaying.
*
* @discussion Defaults to 0 seconds.
*/
@property NSTimeInterval placeholderFadeDuration;
/**
* @abstract Determines drawing priority of the node. Nodes with higher priority will be drawn earlier.
*
* @discussion Defaults to ASDefaultDrawingPriority. There may be multiple drawing threads, and some of them may
* decide to perform operations in queued order (regardless of drawingPriority)
*/
@property NSInteger drawingPriority;
/** @name Hit Testing */
/**
* @abstract Bounds insets for hit testing.
*
* @discussion When set to a non-zero inset, increases the bounds for hit testing to make it easier to tap or perform
* gestures on this node. Default is UIEdgeInsetsZero.
*
* This affects the default implementation of -hitTest and -pointInside, so subclasses should call super if you override
* it and want hitTestSlop applied.
*/
@property UIEdgeInsets hitTestSlop;
/**
* @abstract Returns a Boolean value indicating whether the receiver contains the specified point.
*
* @discussion Includes the "slop" factor specified with hitTestSlop.
*
* @param point A point that is in the receiver's local coordinate system (bounds).
* @param event The event that warranted a call to this method.
*
* @return YES if point is inside the receiver's bounds; otherwise, NO.
*/
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event AS_WARN_UNUSED_RESULT;
/** @name Converting Between View Coordinate Systems */
/**
* @abstract Converts a point from the receiver's coordinate system to that of the specified node.
*
* @param point A point specified in the local coordinate system (bounds) of the receiver.
* @param node The node into whose coordinate system point is to be converted.
*
* @return The point converted to the coordinate system of node.
*/
- (CGPoint)convertPoint:(CGPoint)point toNode:(nullable ASDisplayNode *)node AS_WARN_UNUSED_RESULT;
/**
* @abstract Converts a point from the coordinate system of a given node to that of the receiver.
*
* @param point A point specified in the local coordinate system (bounds) of node.
* @param node The node with point in its coordinate system.
*
* @return The point converted to the local coordinate system (bounds) of the receiver.
*/
- (CGPoint)convertPoint:(CGPoint)point fromNode:(nullable ASDisplayNode *)node AS_WARN_UNUSED_RESULT;
/**
* @abstract Converts a rectangle from the receiver's coordinate system to that of another view.
*
* @param rect A rectangle specified in the local coordinate system (bounds) of the receiver.
* @param node The node that is the target of the conversion operation.
*
* @return The converted rectangle.
*/
- (CGRect)convertRect:(CGRect)rect toNode:(nullable ASDisplayNode *)node AS_WARN_UNUSED_RESULT;
/**
* @abstract Converts a rectangle from the coordinate system of another node to that of the receiver.
*
* @param rect A rectangle specified in the local coordinate system (bounds) of node.
* @param node The node with rect in its coordinate system.
*
* @return The converted rectangle.
*/
- (CGRect)convertRect:(CGRect)rect fromNode:(nullable ASDisplayNode *)node AS_WARN_UNUSED_RESULT;
/**
* Whether or not the node would support having .layerBacked = YES.
*/
@property (readonly) BOOL supportsLayerBacking;
/**
* Whether or not the node layout should be automatically updated when it receives safeAreaInsetsDidChange.
*
* Defaults to NO.
*/
@property BOOL automaticallyRelayoutOnSafeAreaChanges;
/**
* Whether or not the node layout should be automatically updated when it receives layoutMarginsDidChange.
*
* Defaults to NO.
*/
@property BOOL automaticallyRelayoutOnLayoutMarginsChanges;
@end
/**
* Convenience methods for debugging.
*/
@interface ASDisplayNode (Debugging) <ASDebugNameProvider>
/**
* Whether or not ASDisplayNode instances should store their unflattened layouts.
*
* The layout can be accessed via `-unflattenedCalculatedLayout`.
*
* Flattened layouts use less memory and are faster to lookup. On the other hand, unflattened layouts are useful for debugging
* because they preserve original information.
*
* Defaults to NO.
*/
@property (class) BOOL shouldStoreUnflattenedLayouts;
@property (nullable, readonly) ASLayout *unflattenedCalculatedLayout;
/**
* @abstract Return a description of the node hierarchy.
*
* @discussion For debugging: (lldb) po [node displayNodeRecursiveDescription]
*/
- (NSString *)displayNodeRecursiveDescription AS_WARN_UNUSED_RESULT;
/**
* A detailed description of this node's layout state. This is useful when debugging.
*/
@property (copy, readonly) NSString *detailedLayoutDescription;
@end
/**
* ## UIView bridge
*
* ASDisplayNode provides thread-safe access to most of UIView and CALayer properties and methods, traditionally unsafe.
*
* Using them will not cause the actual view/layer to be created, and will be applied when it is created (when the view
* or layer property is accessed).
*
* - NOTE: After the view or layer is created, the properties pass through to the view or layer directly and must be called on the main thread.
*
* See UIView and CALayer for documentation on these common properties.
*/
@interface ASDisplayNode (UIViewBridge)
/**
* Marks the view as needing display. Convenience for use whether the view / layer is loaded or not. Safe to call from a background thread.
*/
- (void)setNeedsDisplay;
/**
* Marks the node as needing layout. Convenience for use whether the view / layer is loaded or not. Safe to call from a background thread.
*
* If the node determines its own desired layout size will change in the next layout pass, it will propagate this
* information up the tree so its parents can have a chance to consider and apply if necessary the new size onto the node.
*
* Note: ASCellNode has special behavior in that calling this method will automatically notify
* the containing ASTableView / ASCollectionView that the cell should be resized, if necessary.
*/
- (void)setNeedsLayout;
/**
* Performs a layout pass on the node. Convenience for use whether the view / layer is loaded or not. Safe to call from a background thread.
*/
- (void)layoutIfNeeded;
@property CGRect frame; // default=CGRectZero
@property CGRect bounds; // default=CGRectZero
@property CGPoint position; // default=CGPointZero
@property CGFloat alpha; // default=1.0f
/* @abstract Sets the corner rounding method to use on the ASDisplayNode.
* There are three types of corner rounding provided by Texture: CALayer, Precomposited, and Clipping.
*
* - ASCornerRoundingTypeDefaultSlowCALayer: uses CALayer's inefficient .cornerRadius property. Use
* this type of corner in situations in which there is both movement through and movement underneath
* the corner (very rare). This uses only .cornerRadius.
*
* - ASCornerRoundingTypePrecomposited: corners are drawn using bezier paths to clip the content in a
* CGContext / UIGraphicsContext. This requires .backgroundColor and .cornerRadius to be set. Use opaque
* background colors when possible for optimal efficiency, but transparent colors are supported and much
* more efficient than CALayer. The only limitation of this approach is that it cannot clip children, and
* thus works best for ASImageNodes or containers showing a background around their children.
*
* - ASCornerRoundingTypeClipping: overlays 4 separate opaque corners on top of the content that needs
* corner rounding. Requires .backgroundColor and .cornerRadius to be set. Use clip corners in situations
* in which is movement through the corner, with an opaque background (no movement underneath the corner).
* Clipped corners are ideal for animating / resizing views, and still outperform CALayer.
*
* For more information and examples, see http://texturegroup.org/docs/corner-rounding.html
*
* @default ASCornerRoundingTypeDefaultSlowCALayer
*/
@property ASCornerRoundingType cornerRoundingType; // default=ASCornerRoundingTypeDefaultSlowCALayer .cornerRadius (offscreen rendering)
/** @abstract The radius to use when rounding corners of the ASDisplayNode.
*
* @discussion This property is thread-safe and should always be preferred over CALayer's cornerRadius property,
* even if corner rounding type is ASCornerRoundingTypeDefaultSlowCALayer.
*/
@property CGFloat cornerRadius; // default=0.0
@property BOOL clipsToBounds; // default==NO
@property (getter=isHidden) BOOL hidden; // default==NO
@property (getter=isOpaque) BOOL opaque; // default==YES
@property (nullable) id contents; // default=nil
@property CGRect contentsRect; // default={0,0,1,1}. @see CALayer.h for details.
@property CGRect contentsCenter; // default={0,0,1,1}. @see CALayer.h for details.
@property CGFloat contentsScale; // default=1.0f. See @contentsScaleForDisplay for details.
@property CGFloat rasterizationScale; // default=1.0f.
@property CGPoint anchorPoint; // default={0.5, 0.5}
@property CGFloat zPosition; // default=0.0
@property CATransform3D transform; // default=CATransform3DIdentity
@property CATransform3D subnodeTransform; // default=CATransform3DIdentity
@property (getter=isUserInteractionEnabled) BOOL userInteractionEnabled; // default=YES (NO for layer-backed nodes)
#if TARGET_OS_IOS
@property (getter=isExclusiveTouch) BOOL exclusiveTouch; // default=NO
#endif
/**
* @abstract The node view's background color.
*
* @discussion In contrast to UIView, setting a transparent color will not set opaque = NO.
* This only affects nodes that implement +drawRect like ASTextNode.
*/
@property (nullable, copy) UIColor *backgroundColor; // default=nil
@property (null_resettable, copy) UIColor *tintColor; // default=Blue
- (void)tintColorDidChange; // Notifies the node when the tintColor has changed.
/**
* @abstract A flag used to determine how a node lays out its content when its bounds change.
*
* @discussion This is like UIView's contentMode property, but better. We do our own mapping to layer.contentsGravity in
* _ASDisplayView. You can set needsDisplayOnBoundsChange independently.
* Thus, UIViewContentModeRedraw is not allowed; use needsDisplayOnBoundsChange = YES instead, and pick an appropriate
* contentMode for your content while it's being re-rendered.
*/
@property UIViewContentMode contentMode; // default=UIViewContentModeScaleToFill
@property (copy) NSString *contentsGravity; // Use .contentMode in preference when possible.
@property UISemanticContentAttribute semanticContentAttribute;
@property (nullable) CGColorRef shadowColor; // default=opaque rgb black
@property CGFloat shadowOpacity; // default=0.0
@property CGSize shadowOffset; // default=(0, -3)
@property CGFloat shadowRadius; // default=3
@property CGFloat borderWidth; // default=0
@property (nullable) CGColorRef borderColor; // default=opaque rgb black
@property BOOL allowsGroupOpacity;
@property BOOL allowsEdgeAntialiasing;
@property unsigned int edgeAntialiasingMask; // default==all values from CAEdgeAntialiasingMask
@property BOOL needsDisplayOnBoundsChange; // default==NO
@property BOOL autoresizesSubviews; // default==YES (undefined for layer-backed nodes)
@property UIViewAutoresizing autoresizingMask; // default==UIViewAutoresizingNone (undefined for layer-backed nodes)
/**
* @abstract Content margins
*
* @discussion This property is bridged to its UIView counterpart.
*
* If your layout depends on this property, you should probably enable automaticallyRelayoutOnLayoutMarginsChanges to ensure
* that the layout gets automatically updated when the value of this property changes. Or you can override layoutMarginsDidChange
* and make all the necessary updates manually.
*/
@property UIEdgeInsets layoutMargins;
@property BOOL preservesSuperviewLayoutMargins; // default is NO - set to enable pass-through or cascading behavior of margins from this views parent to its children
- (void)layoutMarginsDidChange;
/**
* @abstract Safe area insets
*
* @discussion This property is bridged to its UIVIew counterpart.
*
* If your layout depends on this property, you should probably enable automaticallyRelayoutOnSafeAreaChanges to ensure
* that the layout gets automatically updated when the value of this property changes. Or you can override safeAreaInsetsDidChange
* and make all the necessary updates manually.
*/
@property (readonly) UIEdgeInsets safeAreaInsets;
@property BOOL insetsLayoutMarginsFromSafeArea; // Default: YES
- (void)safeAreaInsetsDidChange;
// UIResponder methods
// By default these fall through to the underlying view, but can be overridden.
- (BOOL)canBecomeFirstResponder; // default==NO
- (BOOL)becomeFirstResponder; // default==NO (no-op)
- (BOOL)canResignFirstResponder; // default==YES
- (BOOL)resignFirstResponder; // default==NO (no-op)
- (BOOL)isFirstResponder;
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender;
#if TARGET_OS_TV
//Focus Engine
- (void)setNeedsFocusUpdate;
- (BOOL)canBecomeFocused;
- (void)updateFocusIfNeeded;
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator;
- (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context;
- (nullable UIView *)preferredFocusedView;
#endif
@end
@interface ASDisplayNode (UIViewBridgeAccessibility)
// Accessibility support
@property BOOL isAccessibilityElement;
@property (nullable, copy) NSString *accessibilityLabel;
@property (nullable, copy) NSAttributedString *accessibilityAttributedLabel API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nullable, copy) NSString *accessibilityHint;
@property (nullable, copy) NSAttributedString *accessibilityAttributedHint API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nullable, copy) NSString *accessibilityValue;
@property (nullable, copy) NSAttributedString *accessibilityAttributedValue API_AVAILABLE(ios(11.0),tvos(11.0));
@property UIAccessibilityTraits accessibilityTraits;
@property CGRect accessibilityFrame;
@property (nullable, copy) UIBezierPath *accessibilityPath;
@property CGPoint accessibilityActivationPoint;
@property (nullable, copy) NSString *accessibilityLanguage;
@property BOOL accessibilityElementsHidden;
@property BOOL accessibilityViewIsModal;
@property BOOL shouldGroupAccessibilityChildren;
@property UIAccessibilityNavigationStyle accessibilityNavigationStyle;
#if TARGET_OS_TV
@property (nullable, copy) NSArray *accessibilityHeaderElements;
#endif
// Accessibility identification support
@property (nullable, copy) NSString *accessibilityIdentifier;
@end
@interface ASDisplayNode (ASLayoutElement) <ASLayoutElement>
/**
* @abstract Asks the node to return a layout based on given size range.
*
* @param constrainedSize The minimum and maximum sizes the receiver should fit in.
*
* @return An ASLayout instance defining the layout of the receiver (and its children, if the box layout model is used).
*
* @discussion Though this method does not set the bounds of the view, it does have side effects--caching both the
* constraint and the result.
*
* @warning Subclasses must not override this; it caches results from -calculateLayoutThatFits:. Calling this method may
* be expensive if result is not cached.
*
* @see [ASDisplayNode(Subclassing) calculateLayoutThatFits:]
*/
- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize;
- (CGSize)measure:(CGSize)constrainedSize;
@end
@interface ASDisplayNode (ASLayoutElementStylability) <ASLayoutElementStylability>
@end
typedef NS_ENUM(NSInteger, ASLayoutEngineType) {
ASLayoutEngineTypeLayoutSpec,
ASLayoutEngineTypeYoga
};
@interface ASDisplayNode (ASLayout)
/**
* @abstract Returns the current layout type the node uses for layout the subtree.
*/
@property (readonly) ASLayoutEngineType layoutEngineType;
/**
* @abstract Return the calculated size.
*
* @discussion Ideal for use by subclasses in -layout, having already prompted their subnodes to calculate their size by
* calling -layoutThatFits: on them in -calculateLayoutThatFits.
*
* @return Size already calculated by -calculateLayoutThatFits:.
*
* @warning Subclasses must not override this; it returns the last cached measurement and is never expensive.
*/
@property (readonly) CGSize calculatedSize;
/**
* @abstract Return the constrained size range used for calculating layout.
*
* @return The minimum and maximum constrained sizes used by calculateLayoutThatFits:.
*/
@property (readonly) ASSizeRange constrainedSizeForCalculatedLayout;
@end
@interface ASDisplayNode (ASLayoutTransitioning)
/**
* @abstract The amount of time it takes to complete the default transition animation. Default is 0.2.
*/
@property NSTimeInterval defaultLayoutTransitionDuration;
/**
* @abstract The amount of time (measured in seconds) to wait before beginning the default transition animation.
* Default is 0.0.
*/
@property NSTimeInterval defaultLayoutTransitionDelay;
/**
* @abstract A mask of options indicating how you want to perform the default transition animations.
* For a list of valid constants, see UIViewAnimationOptions.
*/
@property UIViewAnimationOptions defaultLayoutTransitionOptions;
/**
* @discussion A place to perform your animation. New nodes have been inserted here. You can also use this time to re-order the hierarchy.
*/
- (void)animateLayoutTransition:(nonnull id<ASContextTransitioning>)context;
/**
* @discussion A place to clean up your nodes after the transition
*/
- (void)didCompleteLayoutTransition:(nonnull id<ASContextTransitioning>)context;
/**
* @abstract Transitions the current layout with a new constrained size. Must be called on main thread.
*
* @param animated Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`.
* @param shouldMeasureAsync Measure the layout asynchronously.
* @param completion Optional completion block called only if a new layout is calculated.
* It is called on main, right after the measurement and before -animateLayoutTransition:.
*
* @discussion If the passed constrainedSize is the the same as the node's current constrained size, this method is noop. If passed YES to shouldMeasureAsync it's guaranteed that measurement is happening on a background thread, otherwise measaurement will happen on the thread that the method was called on. The measurementCompletion callback is always called on the main thread right after the measurement and before -animateLayoutTransition:.
*
* @see animateLayoutTransition:
*
*/
- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize
animated:(BOOL)animated
shouldMeasureAsync:(BOOL)shouldMeasureAsync
measurementCompletion:(nullable void(^)(void))completion;
/**
* @abstract Invalidates the layout and begins a relayout of the node with the current `constrainedSize`. Must be called on main thread.
*
* @discussion It is called right after the measurement and before -animateLayoutTransition:.
*
* @param animated Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`.
* @param shouldMeasureAsync Measure the layout asynchronously.
* @param completion Optional completion block called only if a new layout is calculated.
*
* @see animateLayoutTransition:
*
*/
- (void)transitionLayoutWithAnimation:(BOOL)animated
shouldMeasureAsync:(BOOL)shouldMeasureAsync
measurementCompletion:(nullable void(^)(void))completion;
/**
* @abstract Cancels all performing layout transitions. Can be called on any thread.
*/
- (void)cancelLayoutTransition;
@end
/*
* ASDisplayNode support for automatic subnode management.
*/
@interface ASDisplayNode (ASAutomaticSubnodeManagement)
/**
* @abstract A boolean that shows whether the node automatically inserts and removes nodes based on the presence or
* absence of the node and its subnodes is completely determined in its layoutSpecThatFits: method.
*
* @discussion If flag is YES the node no longer require addSubnode: or removeFromSupernode method calls. The presence
* or absence of subnodes is completely determined in its layoutSpecThatFits: method.
*/
@property BOOL automaticallyManagesSubnodes;
@end
/*
* ASDisplayNode participates in ASAsyncTransactions, so you can determine when your subnodes are done rendering.
* See: -(void)asyncdisplaykit_asyncTransactionContainerStateDidChange in ASDisplayNodeSubclass.h
*/
@interface ASDisplayNode (ASAsyncTransactionContainer) <ASAsyncTransactionContainer>
@end
/** UIVIew(AsyncDisplayKit) defines convenience method for adding sub-ASDisplayNode to an UIView. */
@interface UIView (AsyncDisplayKit)
/**
* Convenience method, equivalent to [view addSubview:node.view] or [view.layer addSublayer:node.layer] if layer-backed.
*
* @param node The node to be added.
*/
- (void)addSubnode:(ASDisplayNode *)node;
@end
/*
* CALayer(AsyncDisplayKit) defines convenience method for adding sub-ASDisplayNode to a CALayer.
*/
@interface CALayer (AsyncDisplayKit)
/**
* Convenience method, equivalent to [layer addSublayer:node.layer].
*
* @param node The node to be added.
*/
- (void)addSubnode:(ASDisplayNode *)node;
@end
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,213 @@
//
// ASDisplayNodeExtras.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASDisplayNode.h>
/**
* Sets the debugName field for these nodes to the given symbol names, within the domain of "self.class"
* For instance, in `MYButtonNode` if you call `ASSetDebugNames(self.titleNode, _countNode)` the debug names
* for the nodes will be set to `MYButtonNode.titleNode` and `MYButtonNode.countNode`.
*/
#if DEBUG
#define ASSetDebugName(node, format, ...) node.debugName = [NSString stringWithFormat:format, __VA_ARGS__]
#define ASSetDebugNames(...) _ASSetDebugNames(self.class, @"" # __VA_ARGS__, __VA_ARGS__, nil)
#else
#define ASSetDebugName(node, format, ...)
#define ASSetDebugNames(...)
#endif
NS_ASSUME_NONNULL_BEGIN
/// For deallocation of objects on the main thread across multiple run loops.
AS_EXTERN void ASPerformMainThreadDeallocation(id _Nullable __strong * _Nonnull objectPtr);
// Because inline methods can't be extern'd and need to be part of the translation unit of code
// that compiles with them to actually inline, we both declare and define these in the header.
ASDISPLAYNODE_INLINE BOOL ASInterfaceStateIncludesVisible(ASInterfaceState interfaceState)
{
return ((interfaceState & ASInterfaceStateVisible) == ASInterfaceStateVisible);
}
ASDISPLAYNODE_INLINE BOOL ASInterfaceStateIncludesDisplay(ASInterfaceState interfaceState)
{
return ((interfaceState & ASInterfaceStateDisplay) == ASInterfaceStateDisplay);
}
ASDISPLAYNODE_INLINE BOOL ASInterfaceStateIncludesPreload(ASInterfaceState interfaceState)
{
return ((interfaceState & ASInterfaceStatePreload) == ASInterfaceStatePreload);
}
ASDISPLAYNODE_INLINE BOOL ASInterfaceStateIncludesMeasureLayout(ASInterfaceState interfaceState)
{
return ((interfaceState & ASInterfaceStateMeasureLayout) == ASInterfaceStateMeasureLayout);
}
__unused static NSString * NSStringFromASInterfaceState(ASInterfaceState interfaceState)
{
NSMutableArray *states = [NSMutableArray array];
if (interfaceState == ASInterfaceStateNone) {
[states addObject:@"No state"];
}
if (ASInterfaceStateIncludesMeasureLayout(interfaceState)) {
[states addObject:@"MeasureLayout"];
}
if (ASInterfaceStateIncludesPreload(interfaceState)) {
[states addObject:@"Preload"];
}
if (ASInterfaceStateIncludesDisplay(interfaceState)) {
[states addObject:@"Display"];
}
if (ASInterfaceStateIncludesVisible(interfaceState)) {
[states addObject:@"Visible"];
}
return [NSString stringWithFormat:@"{ %@ }", [states componentsJoinedByString:@" | "]];
}
#define INTERFACE_STATE_DELTA(Name) ({ \
if ((oldState & ASInterfaceState##Name) != (newState & ASInterfaceState##Name)) { \
[changes appendFormat:@"%c%s ", (newState & ASInterfaceState##Name ? '+' : '-'), #Name]; \
} \
})
/// e.g. { +Visible, -Preload } (although that should never actually happen.)
/// NOTE: Changes to MeasureLayout state don't really mean anything so we omit them for now.
__unused static NSString *NSStringFromASInterfaceStateChange(ASInterfaceState oldState, ASInterfaceState newState)
{
if (oldState == newState) {
return @"{ }";
}
NSMutableString *changes = [NSMutableString stringWithString:@"{ "];
INTERFACE_STATE_DELTA(Preload);
INTERFACE_STATE_DELTA(Display);
INTERFACE_STATE_DELTA(Visible);
[changes appendString:@"}"];
return changes;
}
#undef INTERFACE_STATE_DELTA
/**
Returns the appropriate interface state for a given ASDisplayNode and window
*/
AS_EXTERN ASInterfaceState ASInterfaceStateForDisplayNode(ASDisplayNode *displayNode, UIWindow *window) AS_WARN_UNUSED_RESULT;
/**
Given a layer, returns the associated display node, if any.
*/
AS_EXTERN ASDisplayNode * _Nullable ASLayerToDisplayNode(CALayer * _Nullable layer) AS_WARN_UNUSED_RESULT;
/**
Given a view, returns the associated display node, if any.
*/
AS_EXTERN ASDisplayNode * _Nullable ASViewToDisplayNode(UIView * _Nullable view) AS_WARN_UNUSED_RESULT;
/**
Given a node, returns the root of the node hierarchy (where supernode == nil)
*/
AS_EXTERN ASDisplayNode *ASDisplayNodeUltimateParentOfNode(ASDisplayNode *node) AS_WARN_UNUSED_RESULT;
/**
If traverseSublayers == YES, this function will walk the layer hierarchy, spanning discontinuous sections of the node hierarchy\
(e.g. the layers of UIKit intermediate views in UIViewControllers, UITableView, UICollectionView).
In the event that a node's backing layer is not created yet, the function will only walk the direct subnodes instead
of forcing the layer hierarchy to be created.
*/
AS_EXTERN void ASDisplayNodePerformBlockOnEveryNode(CALayer * _Nullable layer, ASDisplayNode * _Nullable node, BOOL traverseSublayers, void(^block)(ASDisplayNode *node));
/**
This function will walk the node hierarchy in a breadth first fashion. It does run the block on the node provided
directly to the function call. It does NOT traverse sublayers.
*/
AS_EXTERN void ASDisplayNodePerformBlockOnEveryNodeBFS(ASDisplayNode *node, void(^block)(ASDisplayNode *node));
/**
Identical to ASDisplayNodePerformBlockOnEveryNode, except it does not run the block on the
node provided directly to the function call - only on all descendants.
*/
AS_EXTERN void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, BOOL traverseSublayers, void(^block)(ASDisplayNode *node));
/**
Given a display node, traverses up the layer tree hierarchy, returning the first display node that passes block.
*/
AS_EXTERN ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernode(ASDisplayNode * _Nullable node, BOOL (^block)(ASDisplayNode *node)) AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use the `supernodes` property instead.");
/**
Given a display node, traverses up the layer tree hierarchy, returning the first display node of kind class.
*/
AS_EXTERN __kindof ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernodeOfClass(ASDisplayNode *start, Class c) AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use the `supernodeOfClass:includingSelf:` method instead.");
/**
* Given a layer, find the window it lives in, if any.
*/
AS_EXTERN UIWindow * _Nullable ASFindWindowOfLayer(CALayer *layer) AS_WARN_UNUSED_RESULT;
/**
* Given a layer, find the closest view it lives in, if any.
*/
AS_EXTERN UIView * _Nullable ASFindClosestViewOfLayer(CALayer *layer) AS_WARN_UNUSED_RESULT;
/**
* Given two nodes, finds their most immediate common parent. Used for geometry conversion methods.
* NOTE: It is an error to try to convert between nodes which do not share a common ancestor. This behavior is
* disallowed in UIKit documentation and the behavior is left undefined. The output does not have a rigorously defined
* failure mode (i.e. returning CGPointZero or returning the point exactly as passed in). Rather than track the internal
* undefined and undocumented behavior of UIKit in ASDisplayNode, this operation is defined to be incorrect in all
* circumstances and must be fixed wherever encountered.
*/
AS_EXTERN ASDisplayNode * _Nullable ASDisplayNodeFindClosestCommonAncestor(ASDisplayNode *node1, ASDisplayNode *node2) AS_WARN_UNUSED_RESULT;
/**
Given a display node, collects all descendants. This is a specialization of ASCollectContainer() that walks the Core Animation layer tree as opposed to the display node tree, thus supporting non-continues display node hierarchies.
*/
AS_EXTERN NSArray<ASDisplayNode *> *ASCollectDisplayNodes(ASDisplayNode *node) AS_WARN_UNUSED_RESULT;
/**
Given a display node, traverses down the node hierarchy, returning all the display nodes that pass the block.
*/
AS_EXTERN NSArray<ASDisplayNode *> *ASDisplayNodeFindAllSubnodes(ASDisplayNode *start, BOOL (^block)(ASDisplayNode *node)) AS_WARN_UNUSED_RESULT;
/**
Given a display node, traverses down the node hierarchy, returning all the display nodes of kind class.
*/
AS_EXTERN NSArray<__kindof ASDisplayNode *> *ASDisplayNodeFindAllSubnodesOfClass(ASDisplayNode *start, Class c) AS_WARN_UNUSED_RESULT;
/**
Given a display node, traverses down the node hierarchy, returning the depth-first display node, including the start node that pass the block.
*/
AS_EXTERN __kindof ASDisplayNode * _Nullable ASDisplayNodeFindFirstNode(ASDisplayNode *start, BOOL (^block)(ASDisplayNode *node)) AS_WARN_UNUSED_RESULT;
/**
Given a display node, traverses down the node hierarchy, returning the depth-first display node, excluding the start node, that pass the block
*/
AS_EXTERN __kindof ASDisplayNode * _Nullable ASDisplayNodeFindFirstSubnode(ASDisplayNode *start, BOOL (^block)(ASDisplayNode *node)) AS_WARN_UNUSED_RESULT;
/**
Given a display node, traverses down the node hierarchy, returning the depth-first display node of kind class.
*/
AS_EXTERN __kindof ASDisplayNode * _Nullable ASDisplayNodeFindFirstSubnodeOfClass(ASDisplayNode *start, Class c) AS_WARN_UNUSED_RESULT;
AS_EXTERN UIColor *ASDisplayNodeDefaultPlaceholderColor(void) AS_WARN_UNUSED_RESULT;
AS_EXTERN UIColor *ASDisplayNodeDefaultTintColor(void) AS_WARN_UNUSED_RESULT;
/**
Disable willAppear / didAppear / didDisappear notifications for a sub-hierarchy, then re-enable when done. Nested calls are supported.
*/
AS_EXTERN void ASDisplayNodeDisableHierarchyNotifications(ASDisplayNode *node);
AS_EXTERN void ASDisplayNodeEnableHierarchyNotifications(ASDisplayNode *node);
// Not to be called directly.
AS_EXTERN void _ASSetDebugNames(Class owningClass, NSString *names, ASDisplayNode * _Nullable object, ...);
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,338 @@
//
// ASDisplayNodeExtras.mm
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
#import <AsyncDisplayKit/ASDisplayNode+Ancestry.h>
#import <queue>
#import <AsyncDisplayKit/ASRunLoopQueue.h>
void ASPerformMainThreadDeallocation(id _Nullable __strong * _Nonnull objectPtr) {
/**
* UIKit components must be deallocated on the main thread. We use this shared
* run loop queue to gradually deallocate them across many turns of the main run loop.
*/
static ASRunLoopQueue *queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:nil];
queue.batchSize = 10;
});
if (objectPtr != NULL && *objectPtr != nil) {
// TODO: If ASRunLoopQueue supported an "unsafe_unretained" mode, we could
// transfer the caller's +1 into it and save the retain/release pair.
// Lock queue while enqueuing and releasing, so that there's no risk
// that the queue will release before we get a chance to release.
[queue lock];
[queue enqueue:*objectPtr]; // Retain, +1
*objectPtr = nil; // Release, +0
[queue unlock]; // (After queue drains), release, -1
}
}
void _ASSetDebugNames(Class _Nonnull owningClass, NSString * _Nonnull names, ASDisplayNode * _Nullable object, ...)
{
NSString *owningClassName = NSStringFromClass(owningClass);
NSArray *nameArray = [names componentsSeparatedByString:@", "];
va_list args;
va_start(args, object);
NSInteger i = 0;
for (ASDisplayNode *node = object; node != nil; node = va_arg(args, id), i++) {
NSMutableString *symbolName = [nameArray[i] mutableCopy];
// Remove any `self.` or `_` prefix
[symbolName replaceOccurrencesOfString:@"self." withString:@"" options:NSAnchoredSearch range:NSMakeRange(0, symbolName.length)];
[symbolName replaceOccurrencesOfString:@"_" withString:@"" options:NSAnchoredSearch range:NSMakeRange(0, symbolName.length)];
node.debugName = [NSString stringWithFormat:@"%@.%@", owningClassName, symbolName];
}
ASDisplayNodeCAssert(nameArray.count == i, @"Malformed call to ASSetDebugNames: %@", names);
va_end(args);
}
ASInterfaceState ASInterfaceStateForDisplayNode(ASDisplayNode *displayNode, UIWindow *window)
{
ASDisplayNodeCAssert(![displayNode isLayerBacked], @"displayNode must not be layer backed as it may have a nil window");
if (displayNode && [displayNode supportsRangeManagedInterfaceState]) {
// Directly clear the visible bit if we are not in a window. This means that the interface state is,
// if not already, about to be set to invisible as it is not possible for an element to be visible
// while outside of a window.
ASInterfaceState interfaceState = displayNode.pendingInterfaceState;
return (window == nil ? (interfaceState &= (~ASInterfaceStateVisible)) : interfaceState);
} else {
// For not range managed nodes we might be on our own to try to guess if we're visible.
return (window == nil ? ASInterfaceStateNone : (ASInterfaceStateVisible | ASInterfaceStateDisplay));
}
}
ASDisplayNode *ASLayerToDisplayNode(CALayer *layer)
{
return layer.asyncdisplaykit_node;
}
ASDisplayNode *ASViewToDisplayNode(UIView *view)
{
return view.asyncdisplaykit_node;
}
void ASDisplayNodePerformBlockOnEveryNode(CALayer * _Nullable layer, ASDisplayNode * _Nullable node, BOOL traverseSublayers, void(^block)(ASDisplayNode *node))
{
if (!node) {
ASDisplayNodeCAssertNotNil(layer, @"Cannot recursively perform with nil node and nil layer");
ASDisplayNodeCAssertMainThread();
node = ASLayerToDisplayNode(layer);
}
if (node) {
block(node);
}
if (traverseSublayers && !layer && [node isNodeLoaded] && ASDisplayNodeThreadIsMain()) {
layer = node.layer;
}
if (traverseSublayers && layer && node.rasterizesSubtree == NO) {
/// NOTE: The docs say `sublayers` returns a copy, but it does not.
/// See: http://stackoverflow.com/questions/14854480/collection-calayerarray-0x1ed8faa0-was-mutated-while-being-enumerated
for (CALayer *sublayer in [[layer sublayers] copy]) {
ASDisplayNodePerformBlockOnEveryNode(sublayer, nil, traverseSublayers, block);
}
} else if (node) {
for (ASDisplayNode *subnode in [node subnodes]) {
ASDisplayNodePerformBlockOnEveryNode(nil, subnode, traverseSublayers, block);
}
}
}
void ASDisplayNodePerformBlockOnEveryNodeBFS(ASDisplayNode *node, void(^block)(ASDisplayNode *node))
{
// Queue used to keep track of subnodes while traversing this layout in a BFS fashion.
std::queue<ASDisplayNode *> queue;
queue.push(node);
while (!queue.empty()) {
node = queue.front();
queue.pop();
block(node);
// Add all subnodes to process in next step
for (ASDisplayNode *subnode in node.subnodes) {
queue.push(subnode);
}
}
}
void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, BOOL traverseSublayers, void(^block)(ASDisplayNode *node))
{
for (ASDisplayNode *subnode in node.subnodes) {
ASDisplayNodePerformBlockOnEveryNode(nil, subnode, YES, block);
}
}
ASDisplayNode *ASDisplayNodeFindFirstSupernode(ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node))
{
// This function has historically started with `self` but the name suggests
// that it wouldn't. Perhaps we should change the behavior.
for (ASDisplayNode *ancestor in node.supernodesIncludingSelf) {
if (block(ancestor)) {
return ancestor;
}
}
return nil;
}
__kindof ASDisplayNode *ASDisplayNodeFindFirstSupernodeOfClass(ASDisplayNode *start, Class c)
{
// This function has historically started with `self` but the name suggests
// that it wouldn't. Perhaps we should change the behavior.
return [start supernodeOfClass:c includingSelf:YES];
}
static void _ASCollectDisplayNodes(NSMutableArray *array, CALayer *layer)
{
ASDisplayNode *node = ASLayerToDisplayNode(layer);
if (nil != node) {
[array addObject:node];
}
for (CALayer *sublayer in layer.sublayers)
_ASCollectDisplayNodes(array, sublayer);
}
NSArray<ASDisplayNode *> *ASCollectDisplayNodes(ASDisplayNode *node)
{
NSMutableArray *list = [[NSMutableArray alloc] init];
for (CALayer *sublayer in node.layer.sublayers) {
_ASCollectDisplayNodes(list, sublayer);
}
return list;
}
#pragma mark - Find all subnodes
static void _ASDisplayNodeFindAllSubnodes(NSMutableArray *array, ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node))
{
if (!node)
return;
for (ASDisplayNode *subnode in node.subnodes) {
if (block(subnode)) {
[array addObject:subnode];
}
_ASDisplayNodeFindAllSubnodes(array, subnode, block);
}
}
NSArray<ASDisplayNode *> *ASDisplayNodeFindAllSubnodes(ASDisplayNode *start, BOOL (^block)(ASDisplayNode *node))
{
NSMutableArray *list = [[NSMutableArray alloc] init];
_ASDisplayNodeFindAllSubnodes(list, start, block);
return list;
}
NSArray<__kindof ASDisplayNode *> *ASDisplayNodeFindAllSubnodesOfClass(ASDisplayNode *start, Class c)
{
return ASDisplayNodeFindAllSubnodes(start, ^(ASDisplayNode *n) {
return [n isKindOfClass:c];
});
}
#pragma mark - Find first subnode
static ASDisplayNode *_ASDisplayNodeFindFirstNode(ASDisplayNode *startNode, BOOL includeStartNode, BOOL (^block)(ASDisplayNode *node))
{
for (ASDisplayNode *subnode in startNode.subnodes) {
ASDisplayNode *foundNode = _ASDisplayNodeFindFirstNode(subnode, YES, block);
if (foundNode) {
return foundNode;
}
}
if (includeStartNode && block(startNode))
return startNode;
return nil;
}
__kindof ASDisplayNode *ASDisplayNodeFindFirstNode(ASDisplayNode *startNode, BOOL (^block)(ASDisplayNode *node))
{
return _ASDisplayNodeFindFirstNode(startNode, YES, block);
}
__kindof ASDisplayNode *ASDisplayNodeFindFirstSubnode(ASDisplayNode *startNode, BOOL (^block)(ASDisplayNode *node))
{
return _ASDisplayNodeFindFirstNode(startNode, NO, block);
}
__kindof ASDisplayNode *ASDisplayNodeFindFirstSubnodeOfClass(ASDisplayNode *start, Class c)
{
return ASDisplayNodeFindFirstSubnode(start, ^(ASDisplayNode *n) {
return [n isKindOfClass:c];
});
}
static inline BOOL _ASDisplayNodeIsAncestorOfDisplayNode(ASDisplayNode *possibleAncestor, ASDisplayNode *possibleDescendant)
{
ASDisplayNode *supernode = possibleDescendant;
while (supernode) {
if (supernode == possibleAncestor) {
return YES;
}
supernode = supernode.supernode;
}
return NO;
}
UIWindow * _Nullable ASFindWindowOfLayer(CALayer *layer)
{
UIView *view = ASFindClosestViewOfLayer(layer);
if (UIWindow *window = ASDynamicCast(view, UIWindow)) {
return window;
} else {
return view.window;
}
}
UIView * _Nullable ASFindClosestViewOfLayer(CALayer *layer)
{
while (layer != nil) {
if (UIView *view = ASDynamicCast(layer.delegate, UIView)) {
return view;
}
layer = layer.superlayer;
}
return nil;
}
ASDisplayNode *ASDisplayNodeFindClosestCommonAncestor(ASDisplayNode *node1, ASDisplayNode *node2)
{
ASDisplayNode *possibleAncestor = node1;
while (possibleAncestor) {
if (_ASDisplayNodeIsAncestorOfDisplayNode(possibleAncestor, node2)) {
break;
}
possibleAncestor = possibleAncestor.supernode;
}
ASDisplayNodeCAssertNotNil(possibleAncestor, @"Could not find a common ancestor between node1: %@ and node2: %@", node1, node2);
return possibleAncestor;
}
ASDisplayNode *ASDisplayNodeUltimateParentOfNode(ASDisplayNode *node)
{
// node <- supernode on each loop
// previous <- node on each loop where node is not nil
// previous is the final non-nil value of supernode, i.e. the root node
ASDisplayNode *previousNode = node;
while ((node = [node supernode])) {
previousNode = node;
}
return previousNode;
}
#pragma mark - Placeholders
UIColor *ASDisplayNodeDefaultPlaceholderColor()
{
static UIColor *defaultPlaceholderColor;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
defaultPlaceholderColor = [UIColor colorWithWhite:0.95 alpha:1.0];
});
return defaultPlaceholderColor;
}
UIColor *ASDisplayNodeDefaultTintColor()
{
static UIColor *defaultTintColor;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
defaultTintColor = [UIColor colorWithRed:0.0 green:0.478 blue:1.0 alpha:1.0];
});
return defaultTintColor;
}
#pragma mark - Hierarchy Notifications
void ASDisplayNodeDisableHierarchyNotifications(ASDisplayNode *node)
{
[node __incrementVisibilityNotificationsDisabled];
}
void ASDisplayNodeEnableHierarchyNotifications(ASDisplayNode *node)
{
[node __decrementVisibilityNotificationsDisabled];
}

View File

@ -0,0 +1,222 @@
//
// ASEditableTextNode.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASDisplayNode.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@protocol ASEditableTextNodeDelegate;
@class ASTextKitComponents;
@interface ASEditableTextNodeTargetForAction: NSObject
@property (nonatomic, strong, readonly) id _Nullable target;
- (instancetype)initWithTarget:(id _Nullable)target;
@end
/**
@abstract Implements a node that supports text editing.
@discussion Does not support layer backing.
*/
@interface ASEditableTextNode : ASDisplayNode <UITextInputTraits>
/**
* @abstract Initializes an editable text node using default TextKit components.
*
* @return An initialized ASEditableTextNode.
*/
- (instancetype)init;
/**
* @abstract Initializes an editable text node using the provided TextKit components.
*
* @param textKitComponents The TextKit stack used to render text.
* @param placeholderTextKitComponents The TextKit stack used to render placeholder text.
*
* @return An initialized ASEditableTextNode.
*/
- (instancetype)initWithTextKitComponents:(ASTextKitComponents *)textKitComponents
placeholderTextKitComponents:(ASTextKitComponents *)placeholderTextKitComponents;
//! @abstract The text node's delegate, which must conform to the <ASEditableTextNodeDelegate> protocol.
@property (nullable, weak) id <ASEditableTextNodeDelegate> delegate;
#pragma mark - Configuration
/**
@abstract Enable scrolling on the textView
@default true
*/
@property (nonatomic) BOOL scrollEnabled;
@property (nonatomic, strong) UIFont *baseFont;
/**
@abstract Access to underlying UITextView for more configuration options.
@warning This property should only be used on the main thread and should not be accessed before the editable text node's view is created.
*/
@property (nonatomic, readonly) UITextView *textView;
//! @abstract The attributes to apply to new text being entered by the user.
@property (nullable, nonatomic, copy) NSDictionary<NSString *, id> *typingAttributes;
//! @abstract The range of text currently selected. If length is zero, the range is the cursor location.
@property NSRange selectedRange;
#pragma mark - Placeholder
/**
@abstract Indicates if the receiver is displaying the placeholder text.
@discussion To update the placeholder, see the <attributedPlaceholderText> property.
@result YES if the placeholder is currently displayed; NO otherwise.
*/
- (BOOL)isDisplayingPlaceholder AS_WARN_UNUSED_RESULT;
/**
@abstract The styled placeholder text displayed by the text node while no text is entered
@discussion The placeholder is displayed when the user has not entered any text and the keyboard is not visible.
*/
@property (nullable, nonatomic, copy) NSAttributedString *attributedPlaceholderText;
#pragma mark - Modifying User Text
/**
@abstract The styled text displayed by the receiver.
@discussion When the placeholder is displayed (as indicated by -isDisplayingPlaceholder), this value is nil. Otherwise, this value is the attributed text the user has entered. This value can be modified regardless of whether the receiver is the first responder (and thus, editing) or not. Changing this value from nil to non-nil will result in the placeholder being hidden, and the new value being displayed.
*/
@property (nullable, nonatomic, copy) NSAttributedString *attributedText;
#pragma mark - Managing The Keyboard
//! @abstract The text input mode used by the receiver's keyboard, if it is visible. This value is undefined if the receiver is not the first responder.
@property (nonatomic, readonly) UITextInputMode *textInputMode;
/**
@abstract The textContainerInset of both the placeholder and typed textView. This value defaults to UIEdgeInsetsZero.
*/
@property (nonatomic) UIEdgeInsets textContainerInset;
/**
@abstract The maximum number of lines to display. Additional lines will require scrolling.
@default 0 (No limit)
*/
@property (nonatomic) NSUInteger maximumLinesToDisplay;
/**
@abstract Indicates whether the receiver's text view is the first responder, and thus has the keyboard visible and is prepared for editing by the user.
@result YES if the receiver's text view is the first-responder; NO otherwise.
*/
- (BOOL)isFirstResponder AS_WARN_UNUSED_RESULT;
//! @abstract Makes the receiver's text view the first responder.
- (BOOL)becomeFirstResponder;
//! @abstract Resigns the receiver's text view from first-responder status, if it has it.
- (BOOL)resignFirstResponder;
#pragma mark - Geometry
/**
@abstract Returns the frame of the given range of characters.
@param textRange A range of characters.
@discussion This method raises an exception if `textRange` is not a valid range of characters within the receiver's attributed text.
@result A CGRect that is the bounding box of the glyphs covered by the given range of characters, in the coordinate system of the receiver.
*/
- (CGRect)frameForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT;
/**
@abstract <UITextInputTraits> properties.
*/
@property (nonatomic) UITextAutocapitalizationType autocapitalizationType; // default is UITextAutocapitalizationTypeSentences
@property (nonatomic) UITextAutocorrectionType autocorrectionType; // default is UITextAutocorrectionTypeDefault
@property (nonatomic) UITextSpellCheckingType spellCheckingType; // default is UITextSpellCheckingTypeDefault;
@property (nonatomic) UIKeyboardType keyboardType; // default is UIKeyboardTypeDefault
@property (nonatomic) UIKeyboardAppearance keyboardAppearance; // default is UIKeyboardAppearanceDefault
@property (nonatomic) UIReturnKeyType returnKeyType; // default is UIReturnKeyDefault (See note under UIReturnKeyType enum)
@property (nonatomic) BOOL enablesReturnKeyAutomatically; // default is NO (when YES, will automatically disable return key when text widget has zero-length contents, and will automatically enable when text widget has non-zero-length contents)
@property (nonatomic, getter=isSecureTextEntry) BOOL secureTextEntry; // default is NO
@property (nonatomic, strong) NSString * _Nullable initialPrimaryLanguage;
- (void)dropAutocorrection;
@end
@interface ASEditableTextNode (Unavailable)
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE;
- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE;
@end
#pragma mark -
/**
* The methods declared by the ASEditableTextNodeDelegate protocol allow the adopting delegate to
* respond to notifications such as began and finished editing, selection changed and text updated;
* and manage whether a specified text should be replaced.
*/
@protocol ASEditableTextNodeDelegate <NSObject>
@optional
/**
@abstract Asks the delegate if editing should begin for the text node.
@param editableTextNode An editable text node.
@discussion YES if editing should begin; NO if editing should not begin -- the default returns YES.
*/
- (BOOL)editableTextNodeShouldBeginEditing:(ASEditableTextNode *)editableTextNode;
/**
@abstract Indicates to the delegate that the text node began editing.
@param editableTextNode An editable text node.
@discussion The invocation of this method coincides with the keyboard animating to become visible.
*/
- (void)editableTextNodeDidBeginEditing:(ASEditableTextNode *)editableTextNode;
/**
@abstract Asks the delegate whether the specified text should be replaced in the editable text node.
@param editableTextNode An editable text node.
@param range The current selection range. If the length of the range is 0, range reflects the current insertion point. If the user presses the Delete key, the length of the range is 1 and an empty string object replaces that single character.
@param text The text to insert.
@discussion YES if the old text should be replaced by the new text; NO if the replacement operation should be aborted.
@result The text node calls this method whenever the user types a new character or deletes an existing character. Implementation of this method is optional -- the default implementation returns YES.
*/
- (BOOL)editableTextNode:(ASEditableTextNode *)editableTextNode shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;
/**
@abstract Indicates to the delegate that the text node's selection has changed.
@param editableTextNode An editable text node.
@param fromSelectedRange The previously selected range.
@param toSelectedRange The current selected range. Equivalent to the <selectedRange> property.
@param dueToEditing YES if the selection change was due to editing; NO otherwise.
@discussion You can access the selection of the receiver via <selectedRange>.
*/
- (void)editableTextNodeDidChangeSelection:(ASEditableTextNode *)editableTextNode fromSelectedRange:(NSRange)fromSelectedRange toSelectedRange:(NSRange)toSelectedRange dueToEditing:(BOOL)dueToEditing;
/**
@abstract Indicates to the delegate that the text node's text was updated.
@param editableTextNode An editable text node.
@discussion This method is called each time the user updated the text node's text. It is not called for programmatic changes made to the text via the <attributedText> property.
*/
- (void)editableTextNodeDidUpdateText:(ASEditableTextNode *)editableTextNode;
/**
@abstract Indicates to the delegate that the text node has finished editing.
@param editableTextNode An editable text node.
@discussion The invocation of this method coincides with the keyboard animating to become hidden.
*/
- (void)editableTextNodeDidFinishEditing:(ASEditableTextNode *)editableTextNode;
- (BOOL)editableTextNodeShouldCopy:(ASEditableTextNode *)editableTextNode;
- (BOOL)editableTextNodeShouldPaste:(ASEditableTextNode *)editableTextNode;
- (ASEditableTextNodeTargetForAction * _Nullable)editableTextNodeTargetForAction:(SEL)action;
- (BOOL)editableTextNodeShouldReturn:(ASEditableTextNode *)editableTextNode;
- (void)editableTextNodeBackspaceWhileEmpty:(ASEditableTextNode *)editableTextNode;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,221 @@
//
// ASEditableTextNode.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASDisplayNode.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@protocol ASEditableTextNodeDelegate;
@class ASTextKitComponents;
@interface ASEditableTextNodeTargetForAction: NSObject
@property (nonatomic, strong, readonly) id _Nullable target;
- (instancetype)initWithTarget:(id _Nullable)target;
@end
/**
@abstract Implements a node that supports text editing.
@discussion Does not support layer backing.
*/
@interface ASEditableTextNode : ASDisplayNode <UITextInputTraits>
/**
* @abstract Initializes an editable text node using default TextKit components.
*
* @return An initialized ASEditableTextNode.
*/
- (instancetype)init;
/**
* @abstract Initializes an editable text node using the provided TextKit components.
*
* @param textKitComponents The TextKit stack used to render text.
* @param placeholderTextKitComponents The TextKit stack used to render placeholder text.
*
* @return An initialized ASEditableTextNode.
*/
- (instancetype)initWithTextKitComponents:(ASTextKitComponents *)textKitComponents
placeholderTextKitComponents:(ASTextKitComponents *)placeholderTextKitComponents;
//! @abstract The text node's delegate, which must conform to the <ASEditableTextNodeDelegate> protocol.
@property (nullable, weak) id <ASEditableTextNodeDelegate> delegate;
#pragma mark - Configuration
/**
@abstract Enable scrolling on the textView
@default true
*/
@property (nonatomic) BOOL scrollEnabled;
/**
@abstract Access to underlying UITextView for more configuration options.
@warning This property should only be used on the main thread and should not be accessed before the editable text node's view is created.
*/
@property (nonatomic, readonly) UITextView *textView;
//! @abstract The attributes to apply to new text being entered by the user.
@property (nullable, nonatomic, copy) NSDictionary<NSString *, id> *typingAttributes;
//! @abstract The range of text currently selected. If length is zero, the range is the cursor location.
@property NSRange selectedRange;
#pragma mark - Placeholder
/**
@abstract Indicates if the receiver is displaying the placeholder text.
@discussion To update the placeholder, see the <attributedPlaceholderText> property.
@result YES if the placeholder is currently displayed; NO otherwise.
*/
- (BOOL)isDisplayingPlaceholder AS_WARN_UNUSED_RESULT;
/**
@abstract The styled placeholder text displayed by the text node while no text is entered
@discussion The placeholder is displayed when the user has not entered any text and the keyboard is not visible.
*/
@property (nullable, nonatomic, copy) NSAttributedString *attributedPlaceholderText;
#pragma mark - Modifying User Text
/**
@abstract The styled text displayed by the receiver.
@discussion When the placeholder is displayed (as indicated by -isDisplayingPlaceholder), this value is nil. Otherwise, this value is the attributed text the user has entered. This value can be modified regardless of whether the receiver is the first responder (and thus, editing) or not. Changing this value from nil to non-nil will result in the placeholder being hidden, and the new value being displayed.
*/
@property (nullable, nonatomic, copy) NSAttributedString *attributedText;
#pragma mark - Managing The Keyboard
//! @abstract The text input mode used by the receiver's keyboard, if it is visible. This value is undefined if the receiver is not the first responder.
@property (nonatomic, readonly) UITextInputMode *textInputMode;
/**
@abstract The textContainerInset of both the placeholder and typed textView. This value defaults to UIEdgeInsetsZero.
*/
@property (nonatomic) UIEdgeInsets textContainerInset;
/**
@abstract The maximum number of lines to display. Additional lines will require scrolling.
@default 0 (No limit)
*/
@property (nonatomic) NSUInteger maximumLinesToDisplay;
/**
@abstract Indicates whether the receiver's text view is the first responder, and thus has the keyboard visible and is prepared for editing by the user.
@result YES if the receiver's text view is the first-responder; NO otherwise.
*/
- (BOOL)isFirstResponder AS_WARN_UNUSED_RESULT;
//! @abstract Makes the receiver's text view the first responder.
- (BOOL)becomeFirstResponder;
//! @abstract Resigns the receiver's text view from first-responder status, if it has it.
- (BOOL)resignFirstResponder;
#pragma mark - Geometry
/**
@abstract Returns the frame of the given range of characters.
@param textRange A range of characters.
@discussion This method raises an exception if `textRange` is not a valid range of characters within the receiver's attributed text.
@result A CGRect that is the bounding box of the glyphs covered by the given range of characters, in the coordinate system of the receiver.
*/
- (CGRect)frameForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT;
/**
@abstract <UITextInputTraits> properties.
*/
@property (nonatomic) UITextAutocapitalizationType autocapitalizationType; // default is UITextAutocapitalizationTypeSentences
@property (nonatomic) UITextAutocorrectionType autocorrectionType; // default is UITextAutocorrectionTypeDefault
@property (nonatomic) UITextSpellCheckingType spellCheckingType; // default is UITextSpellCheckingTypeDefault;
@property (nonatomic) UIKeyboardType keyboardType; // default is UIKeyboardTypeDefault
@property (nonatomic) UIKeyboardAppearance keyboardAppearance; // default is UIKeyboardAppearanceDefault
@property (nonatomic) UIReturnKeyType returnKeyType; // default is UIReturnKeyDefault (See note under UIReturnKeyType enum)
@property (nonatomic) BOOL enablesReturnKeyAutomatically; // default is NO (when YES, will automatically disable return key when text widget has zero-length contents, and will automatically enable when text widget has non-zero-length contents)
@property (nonatomic, getter=isSecureTextEntry) BOOL secureTextEntry; // default is NO
- (void)dropAutocorrection;
@end
@interface ASEditableTextNode (Unavailable)
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE;
- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE;
@end
#pragma mark -
/**
* The methods declared by the ASEditableTextNodeDelegate protocol allow the adopting delegate to
* respond to notifications such as began and finished editing, selection changed and text updated;
* and manage whether a specified text should be replaced.
*/
@protocol ASEditableTextNodeDelegate <NSObject>
@optional
/**
@abstract Asks the delegate if editing should begin for the text node.
@param editableTextNode An editable text node.
@discussion YES if editing should begin; NO if editing should not begin -- the default returns YES.
*/
- (BOOL)editableTextNodeShouldBeginEditing:(ASEditableTextNode *)editableTextNode;
/**
@abstract Indicates to the delegate that the text node began editing.
@param editableTextNode An editable text node.
@discussion The invocation of this method coincides with the keyboard animating to become visible.
*/
- (void)editableTextNodeDidBeginEditing:(ASEditableTextNode *)editableTextNode;
/**
@abstract Asks the delegate whether the specified text should be replaced in the editable text node.
@param editableTextNode An editable text node.
@param range The current selection range. If the length of the range is 0, range reflects the current insertion point. If the user presses the Delete key, the length of the range is 1 and an empty string object replaces that single character.
@param text The text to insert.
@discussion YES if the old text should be replaced by the new text; NO if the replacement operation should be aborted.
@result The text node calls this method whenever the user types a new character or deletes an existing character. Implementation of this method is optional -- the default implementation returns YES.
*/
- (BOOL)editableTextNode:(ASEditableTextNode *)editableTextNode shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;
/**
@abstract Indicates to the delegate that the text node's selection has changed.
@param editableTextNode An editable text node.
@param fromSelectedRange The previously selected range.
@param toSelectedRange The current selected range. Equivalent to the <selectedRange> property.
@param dueToEditing YES if the selection change was due to editing; NO otherwise.
@discussion You can access the selection of the receiver via <selectedRange>.
*/
- (void)editableTextNodeDidChangeSelection:(ASEditableTextNode *)editableTextNode fromSelectedRange:(NSRange)fromSelectedRange toSelectedRange:(NSRange)toSelectedRange dueToEditing:(BOOL)dueToEditing;
/**
@abstract Indicates to the delegate that the text node's text was updated.
@param editableTextNode An editable text node.
@discussion This method is called each time the user updated the text node's text. It is not called for programmatic changes made to the text via the <attributedText> property.
*/
- (void)editableTextNodeDidUpdateText:(ASEditableTextNode *)editableTextNode;
/**
@abstract Indicates to the delegate that the text node has finished editing.
@param editableTextNode An editable text node.
@discussion The invocation of this method coincides with the keyboard animating to become hidden.
*/
- (void)editableTextNodeDidFinishEditing:(ASEditableTextNode *)editableTextNode;
<<<<<<< HEAD
- (BOOL)editableTextNodeShouldPaste:(ASEditableTextNode *)editableTextNode;
- (ASEditableTextNodeTargetForAction * _Nullable)editableTextNodeTargetForAction:(SEL)action;
- (BOOL)editableTextNodeShouldReturn:(ASEditableTextNode *)editableTextNode;
=======
>>>>>>> 565da7d4935740d12fc204aa061faf093831da1e
@end
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,43 @@
//
// ASExperimentalFeatures.h
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASAvailability.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
NS_ASSUME_NONNULL_BEGIN
/**
* A bit mask of features. Make sure to update configuration.json when you add entries.
*/
typedef NS_OPTIONS(NSUInteger, ASExperimentalFeatures) {
ASExperimentalGraphicsContexts = 1 << 0, // exp_graphics_contexts
// If AS_ENABLE_TEXTNODE=0 or TextNode2 subspec is used this setting is a no op and ASTextNode2
// will be used in all cases
ASExperimentalTextNode = 1 << 1, // exp_text_node
ASExperimentalInterfaceStateCoalescing = 1 << 2, // exp_interface_state_coalesce
ASExperimentalUnfairLock = 1 << 3, // exp_unfair_lock
ASExperimentalLayerDefaults = 1 << 4, // exp_infer_layer_defaults
ASExperimentalCollectionTeardown = 1 << 5, // exp_collection_teardown
ASExperimentalFramesetterCache = 1 << 6, // exp_framesetter_cache
ASExperimentalSkipClearData = 1 << 7, // exp_skip_clear_data
ASExperimentalDidEnterPreloadSkipASMLayout = 1 << 8, // exp_did_enter_preload_skip_asm_layout
ASExperimentalDisableAccessibilityCache = 1 << 9, // exp_disable_a11y_cache
ASExperimentalDispatchApply = 1 << 10, // exp_dispatch_apply
ASExperimentalImageDownloaderPriority = 1 << 11, // exp_image_downloader_priority
ASExperimentalTextDrawing = 1 << 12, // exp_text_drawing
ASExperimentalFeatureAll = 0xFFFFFFFF
};
/// Convert flags -> name array.
AS_EXTERN NSArray<NSString *> *ASExperimentalFeaturesGetNames(ASExperimentalFeatures flags);
/// Convert name array -> flags.
AS_EXTERN ASExperimentalFeatures ASExperimentalFeaturesFromArray(NSArray<NSString *> *array);
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,52 @@
//
// ASExperimentalFeatures.mm
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASExperimentalFeatures.h>
#import <AsyncDisplayKit/ASCollections.h>
NSArray<NSString *> *ASExperimentalFeaturesGetNames(ASExperimentalFeatures flags)
{
NSArray *allNames = ASCreateOnce((@[@"exp_graphics_contexts",
@"exp_text_node",
@"exp_interface_state_coalesce",
@"exp_unfair_lock",
@"exp_infer_layer_defaults",
@"exp_collection_teardown",
@"exp_framesetter_cache",
@"exp_skip_clear_data",
@"exp_did_enter_preload_skip_asm_layout",
@"exp_disable_a11y_cache",
@"exp_dispatch_apply",
@"exp_image_downloader_priority",
@"exp_text_drawing"]));
if (flags == ASExperimentalFeatureAll) {
return allNames;
}
// Go through all names, testing each bit.
NSUInteger i = 0;
return ASArrayByFlatMapping(allNames, NSString *name, ({
(flags & (1 << i++)) ? name : nil;
}));
}
// O(N^2) but with counts this small, it's probably faster
// than hashing the strings.
ASExperimentalFeatures ASExperimentalFeaturesFromArray(NSArray<NSString *> *array)
{
NSArray *allNames = ASExperimentalFeaturesGetNames(ASExperimentalFeatureAll);
ASExperimentalFeatures result = 0;
for (NSString *str in array) {
NSUInteger i = [allNames indexOfObject:str];
if (i != NSNotFound) {
result |= (1 << i);
}
}
return result;
}

View File

@ -0,0 +1,416 @@
//
// ASImageNode+AnimatedImage.mm
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#ifndef MINIMAL_ASDK
#import <AsyncDisplayKit/ASImageNode.h>
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <AsyncDisplayKit/ASEqualityHelpers.h>
#import <AsyncDisplayKit/ASImageNode+Private.h>
#import <AsyncDisplayKit/ASImageNode+AnimatedImagePrivate.h>
#import <AsyncDisplayKit/ASImageProtocols.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <AsyncDisplayKit/ASNetworkImageNode.h>
#import <AsyncDisplayKit/ASThread.h>
#import <AsyncDisplayKit/ASWeakProxy.h>
#define ASAnimatedImageDebug 0
#ifndef MINIMAL_ASDK
@interface ASNetworkImageNode (Private)
- (void)_locked_setDefaultImage:(UIImage *)image;
@end
#endif
@implementation ASImageNode (AnimatedImage)
#pragma mark - GIF support
- (void)setAnimatedImage:(id <ASAnimatedImageProtocol>)animatedImage
{
ASLockScopeSelf();
[self _locked_setAnimatedImage:animatedImage];
}
- (void)_locked_setAnimatedImage:(id <ASAnimatedImageProtocol>)animatedImage
{
ASAssertLocked(__instanceLock__);
if (ASObjectIsEqual(_animatedImage, animatedImage) && (animatedImage == nil || animatedImage.playbackReady)) {
return;
}
__block id <ASAnimatedImageProtocol> previousAnimatedImage = _animatedImage;
_animatedImage = animatedImage;
if (animatedImage != nil) {
__weak ASImageNode *weakSelf = self;
if ([animatedImage respondsToSelector:@selector(setCoverImageReadyCallback:)]) {
animatedImage.coverImageReadyCallback = ^(UIImage *coverImage) {
// In this case the lock is already gone we have to call the unlocked version therefore
[weakSelf setCoverImageCompleted:coverImage];
};
}
animatedImage.playbackReadyCallback = ^{
// In this case the lock is already gone we have to call the unlocked version therefore
[weakSelf setShouldAnimate:YES];
};
if (animatedImage.playbackReady) {
[self _locked_setShouldAnimate:YES];
}
} else {
// Clean up after ourselves.
// Don't bother using a `_locked` version for setting contnst as it should be pretty safe calling it with
// reaquire the lock and would add overhead to introduce this version
self.contents = nil;
[self _locked_setCoverImage:nil];
}
// Push calling subclass to the next runloop cycle
// We have to schedule the block on the common modes otherwise the tracking mode will not be included and it will
// not fire e.g. while scrolling down
CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopCommonModes, ^(void) {
[self animatedImageSet:animatedImage previousAnimatedImage:previousAnimatedImage];
// Animated image can take while to dealloc, do it off the main queue
if (previousAnimatedImage != nil) {
ASPerformBackgroundDeallocation(&previousAnimatedImage);
}
});
// Don't need to wakeup the runloop as the current is already running
// CFRunLoopWakeUp(runLoop); // Should not be necessary
}
- (void)animatedImageSet:(id <ASAnimatedImageProtocol>)newAnimatedImage previousAnimatedImage:(id <ASAnimatedImageProtocol>)previousAnimatedImage
{
// Subclass hook should not be called with the lock held
ASAssertUnlocked(__instanceLock__);
// Subclasses may override
}
- (id <ASAnimatedImageProtocol>)animatedImage
{
ASLockScopeSelf();
return _animatedImage;
}
- (void)setAnimatedImagePaused:(BOOL)animatedImagePaused
{
ASLockScopeSelf();
_animatedImagePaused = animatedImagePaused;
[self _locked_setShouldAnimate:!animatedImagePaused];
}
- (BOOL)animatedImagePaused
{
ASLockScopeSelf();
return _animatedImagePaused;
}
- (void)setCoverImageCompleted:(UIImage *)coverImage
{
if (ASInterfaceStateIncludesDisplay(self.interfaceState)) {
ASLockScopeSelf();
[self _locked_setCoverImageCompleted:coverImage];
}
}
- (void)_locked_setCoverImageCompleted:(UIImage *)coverImage
{
ASAssertLocked(__instanceLock__);
_displayLinkLock.lock();
BOOL setCoverImage = (_displayLink == nil) || _displayLink.paused;
_displayLinkLock.unlock();
if (setCoverImage) {
[self _locked_setCoverImage:coverImage];
}
}
- (void)setCoverImage:(UIImage *)coverImage
{
ASLockScopeSelf();
[self _locked_setCoverImage:coverImage];
}
- (void)_locked_setCoverImage:(UIImage *)coverImage
{
ASAssertLocked(__instanceLock__);
//If we're a network image node, we want to set the default image so
//that it will correctly be restored if it exits the range.
#ifndef MINIMAL_ASDK
if ([self isKindOfClass:[ASNetworkImageNode class]]) {
[(ASNetworkImageNode *)self _locked_setDefaultImage:coverImage];
} else if (_displayLink == nil || _displayLink.paused == YES) {
[self _locked_setImage:coverImage];
}
#endif
}
- (NSString *)animatedImageRunLoopMode
{
AS::MutexLocker l(_displayLinkLock);
return _animatedImageRunLoopMode;
}
- (void)setAnimatedImageRunLoopMode:(NSString *)runLoopMode
{
AS::MutexLocker l(_displayLinkLock);
if (runLoopMode == nil) {
runLoopMode = ASAnimatedImageDefaultRunLoopMode;
}
if (_displayLink != nil) {
[_displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:_animatedImageRunLoopMode];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:runLoopMode];
}
_animatedImageRunLoopMode = [runLoopMode copy];
}
- (void)setShouldAnimate:(BOOL)shouldAnimate
{
ASLockScopeSelf();
[self _locked_setShouldAnimate:shouldAnimate];
}
- (void)_locked_setShouldAnimate:(BOOL)shouldAnimate
{
ASAssertLocked(__instanceLock__);
// This test is explicitly done and not ASPerformBlockOnMainThread as this would perform the block immediately
// on main if called on main thread and we have to call methods locked or unlocked based on which thread we are on
if (ASDisplayNodeThreadIsMain()) {
if (shouldAnimate) {
[self _locked_startAnimating];
} else {
[self _locked_stopAnimating];
}
} else {
// We have to dispatch to the main thread and call the regular methods as the lock is already gone if the
// block is called
dispatch_async(dispatch_get_main_queue(), ^{
if (shouldAnimate) {
[self startAnimating];
} else {
[self stopAnimating];
}
});
}
}
#pragma mark - Animating
- (void)startAnimating
{
ASDisplayNodeAssertMainThread();
ASLockScopeSelf();
[self _locked_startAnimating];
}
- (void)_locked_startAnimating
{
ASAssertLocked(__instanceLock__);
// It should be safe to call self.interfaceState in this case as it will only grab the lock of the superclass
if (!ASInterfaceStateIncludesVisible(self.interfaceState)) {
return;
}
if (_animatedImagePaused) {
return;
}
if (_animatedImage.playbackReady == NO) {
return;
}
#if ASAnimatedImageDebug
NSLog(@"starting animation: %p", self);
#endif
// Get frame interval before holding display link lock to avoid deadlock
NSUInteger frameInterval = self.animatedImage.frameInterval;
AS::MutexLocker l(_displayLinkLock);
if (_displayLink == nil) {
_playHead = 0;
_displayLink = [CADisplayLink displayLinkWithTarget:[ASWeakProxy weakProxyWithTarget:self] selector:@selector(displayLinkFired:)];
_displayLink.frameInterval = frameInterval;
_lastSuccessfulFrameIndex = NSUIntegerMax;
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:_animatedImageRunLoopMode];
} else {
_displayLink.paused = NO;
}
}
- (void)stopAnimating
{
ASDisplayNodeAssertMainThread();
ASLockScopeSelf();
[self _locked_stopAnimating];
}
- (void)_locked_stopAnimating
{
ASDisplayNodeAssertMainThread();
ASAssertLocked(__instanceLock__);
#if ASAnimatedImageDebug
NSLog(@"stopping animation: %p", self);
#endif
ASDisplayNodeAssertMainThread();
AS::MutexLocker l(_displayLinkLock);
_displayLink.paused = YES;
self.lastDisplayLinkFire = 0;
[_animatedImage clearAnimatedImageCache];
}
#pragma mark - ASDisplayNode
- (void)didEnterVisibleState
{
ASDisplayNodeAssertMainThread();
[super didEnterVisibleState];
if (self.animatedImage.coverImageReady) {
[self setCoverImage:self.animatedImage.coverImage];
}
if (self.animatedImage.playbackReady) {
[self startAnimating];
}
}
- (void)didExitVisibleState
{
ASDisplayNodeAssertMainThread();
[super didExitVisibleState];
[self stopAnimating];
}
- (void)didExitDisplayState
{
ASDisplayNodeAssertMainThread();
#if ASAnimatedImageDebug
NSLog(@"exiting display state: %p", self);
#endif
// Check to see if we're an animated image before calling super in case someone
// decides they want to clear out the animatedImage itself on exiting the display
// state
BOOL isAnimatedImage = self.animatedImage != nil;
[super didExitDisplayState];
// Also clear out the contents we've set to be good citizens, we'll put it back in when we become visible.
if (isAnimatedImage) {
self.contents = nil;
[self setCoverImage:nil];
}
}
#pragma mark - Display Link Callbacks
- (void)displayLinkFired:(CADisplayLink *)displayLink
{
ASDisplayNodeAssertMainThread();
CFTimeInterval timeBetweenLastFire;
if (self.lastDisplayLinkFire == 0) {
timeBetweenLastFire = 0;
} else if (AS_AVAILABLE_IOS_TVOS(10, 10)) {
timeBetweenLastFire = displayLink.targetTimestamp - displayLink.timestamp;
} else {
timeBetweenLastFire = CACurrentMediaTime() - self.lastDisplayLinkFire;
}
self.lastDisplayLinkFire = CACurrentMediaTime();
_playHead += timeBetweenLastFire;
while (_playHead > self.animatedImage.totalDuration) {
// Set playhead to zero to keep from showing different frames on different playthroughs
_playHead = 0;
_playedLoops++;
}
if (self.animatedImage.loopCount > 0 && _playedLoops >= self.animatedImage.loopCount) {
[self stopAnimating];
return;
}
NSUInteger frameIndex = [self frameIndexAtPlayHeadPosition:_playHead];
if (frameIndex == _lastSuccessfulFrameIndex) {
return;
}
CGImageRef frameImage = [self.animatedImage imageAtIndex:frameIndex];
if (frameImage == nil) {
//Pause the display link until we get a file ready notification
displayLink.paused = YES;
self.lastDisplayLinkFire = 0;
} else {
self.contents = (__bridge id)frameImage;
_lastSuccessfulFrameIndex = frameIndex;
[self displayDidFinish];
}
}
- (NSUInteger)frameIndexAtPlayHeadPosition:(CFTimeInterval)playHead
{
ASDisplayNodeAssertMainThread();
NSUInteger frameIndex = 0;
for (NSUInteger durationIndex = 0; durationIndex < self.animatedImage.frameCount; durationIndex++) {
playHead -= [self.animatedImage durationAtIndex:durationIndex];
if (playHead < 0) {
return frameIndex;
}
frameIndex++;
}
return frameIndex;
}
@end
#pragma mark - ASImageNode(AnimatedImageInvalidation)
@implementation ASImageNode(AnimatedImageInvalidation)
- (void)invalidateAnimatedImage
{
AS::MutexLocker l(_displayLinkLock);
#if ASAnimatedImageDebug
if (_displayLink) {
NSLog(@"invalidating display link");
}
#endif
[_displayLink invalidate];
_displayLink = nil;
}
@end
#endif

View File

@ -0,0 +1,219 @@
//
// ASImageNode.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASControlNode.h>
NS_ASSUME_NONNULL_BEGIN
#ifndef MINIMAL_ASDK
@protocol ASAnimatedImageProtocol;
#endif
/**
* Image modification block. Use to transform an image before display.
*
* @param image The image to be displayed.
*
* @return A transformed image.
*/
typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image);
/**
* @abstract Draws images.
* @discussion Supports cropping, tinting, and arbitrary image modification blocks.
*/
@interface ASImageNode : ASControlNode
/**
* @abstract The image to display.
*
* @discussion The node will efficiently display stretchable images by using
* the layer's contentsCenter property. Non-stretchable images work too, of
* course.
*/
@property (nullable) UIImage *image;
/**
@abstract The placeholder color.
*/
@property (nullable, copy) UIColor *placeholderColor;
/**
* @abstract Indicates whether efficient cropping of the receiver is enabled.
*
* @discussion Defaults to YES. See -setCropEnabled:recropImmediately:inBounds: for more
* information.
*/
@property (getter=isCropEnabled) BOOL cropEnabled;
/**
* @abstract Indicates that efficient downsizing of backing store should *not* be enabled.
*
* @discussion Defaults to NO. @see ASCroppedImageBackingSizeAndDrawRectInBounds for more
* information.
*/
@property BOOL forceUpscaling;
@property (nonatomic, assign) BOOL displayWithoutProcessing;
/**
* @abstract Forces image to be rendered at forcedSize.
* @discussion Defaults to CGSizeZero to indicate that the forcedSize should not be used.
* Setting forcedSize to non-CGSizeZero will force the backing of the layer contents to
* be forcedSize (automatically adjusted for contentsSize).
*/
@property CGSize forcedSize;
/**
* @abstract Enables or disables efficient cropping.
*
* @param cropEnabled YES to efficiently crop the receiver's contents such that
* contents outside of its bounds are not included; NO otherwise.
*
* @param recropImmediately If the receiver has an image, YES to redisplay the
* receiver immediately; NO otherwise.
*
* @param cropBounds The bounds into which the receiver will be cropped. Useful
* if bounds are to change in response to cropping (but have not yet done so).
*
* @discussion Efficient cropping is only performed when the receiver's view's
* contentMode is UIViewContentModeScaleAspectFill. By default, cropping is
* enabled. The crop alignment may be controlled via cropAlignmentFactor.
*/
- (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediately inBounds:(CGRect)cropBounds;
/**
* @abstract A value that controls how the receiver's efficient cropping is aligned.
*
* @discussion This value defines a rectangle that is to be featured by the
* receiver. The rectangle is specified as a "unit rectangle," using
* fractions of the source image's width and height, e.g. CGRectMake(0.5, 0,
* 0.5, 1.0) will feature the full right half a photo. If the cropRect is
* empty, the content mode of the receiver will be used to determine its
* dimensions, and only the cropRect's origin will be used for positioning. The
* default value of this property is CGRectMake(0.5, 0.5, 0.0, 0.0).
*/
@property CGRect cropRect;
/**
* @abstract An optional block which can perform drawing operations on image
* during the display phase.
*
* @discussion Can be used to add image effects (such as rounding, adding
* borders, or other pattern overlays) without extraneous display calls.
*/
@property (nullable) asimagenode_modification_block_t imageModificationBlock;
/**
* @abstract Marks the receiver as needing display and performs a block after
* display has finished.
*
* @param displayCompletionBlock The block to be performed after display has
* finished. Its `canceled` property will be YES if display was prevented or
* canceled (via displaySuspended); NO otherwise.
*
* @discussion displayCompletionBlock will be performed on the main-thread. If
* `displaySuspended` is YES, `displayCompletionBlock` is will be
* performed immediately and `YES` will be passed for `canceled`.
*/
- (void)setNeedsDisplayWithCompletion:(nullable void (^)(BOOL canceled))displayCompletionBlock;
#if TARGET_OS_TV
/**
* A bool to track if the current appearance of the node
* is the default focus appearance.
* Exposed here so the category methods can set it.
*/
@property BOOL isDefaultFocusAppearance;
#endif
@end
#if TARGET_OS_TV
@interface ASImageNode (tvOS)
@end
#endif
#ifndef MINIMAL_ASDK
@interface ASImageNode (AnimatedImage)
/**
* @abstract The animated image to playback
*
* @discussion Set this to an object which conforms to ASAnimatedImageProtocol
* to have the ASImageNode playback an animated image.
* @warning this method should not be overridden, it may not always be called as
* another method is used internally. If you need to know when the animatedImage
* is set, override @c animatedImageSet:previousAnimatedImage:
*/
@property (nullable) id <ASAnimatedImageProtocol> animatedImage;
/**
* @abstract Pause the playback of an animated image.
*
* @discussion Set to YES to pause playback of an animated image and NO to resume
* playback.
*/
@property BOOL animatedImagePaused;
/**
* @abstract The runloop mode used to animate the image.
*
* @discussion Defaults to NSRunLoopCommonModes. Another commonly used mode is NSDefaultRunLoopMode.
* Setting NSDefaultRunLoopMode will cause animation to pause while scrolling (if the ASImageNode is
* in a scroll view), which may improve scroll performance in some use cases.
*/
@property (copy) NSString *animatedImageRunLoopMode;
/**
* @abstract Method called when animated image has been set
*
* @discussion This method is for subclasses to override so they can know if an animated image
* has been set on the node.
*/
- (void)animatedImageSet:(nullable id <ASAnimatedImageProtocol>)newAnimatedImage previousAnimatedImage:(nullable id <ASAnimatedImageProtocol>)previousAnimatedImage ASDISPLAYNODE_REQUIRES_SUPER;
@end
#endif
@interface ASImageNode (Unavailable)
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock AS_UNAVAILABLE();
- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock AS_UNAVAILABLE();
@end
/**
* @abstract Image modification block that rounds (and optionally adds a border to) an image.
*
* @param borderWidth The width of the round border to draw, or zero if no border is desired.
* @param borderColor What colour border to draw.
*
* @see <imageModificationBlock>
*
* @return An ASImageNode image modification block.
*/
AS_EXTERN asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat borderWidth, UIColor * _Nullable borderColor);
/**
* @abstract Image modification block that applies a tint color à la UIImage configured with
* renderingMode set to UIImageRenderingModeAlwaysTemplate.
*
* @param color The color to tint the image.
*
* @see <imageModificationBlock>
*
* @return An ASImageNode image modification block.
*/
AS_EXTERN asimagenode_modification_block_t ASImageNodeTintColorModificationBlock(UIColor *color);
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,784 @@
//
// ASImageNode.mm
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASImageNode.h>
#import <tgmath.h>
#import <AsyncDisplayKit/_ASDisplayLayer.h>
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASGraphicsContext.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASTextNode.h>
#import <AsyncDisplayKit/ASImageNode+AnimatedImagePrivate.h>
#import <AsyncDisplayKit/ASImageNode+CGExtras.h>
#import <AsyncDisplayKit/AsyncDisplayKit+Debug.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <AsyncDisplayKit/ASEqualityHelpers.h>
#import <AsyncDisplayKit/ASHashing.h>
#import <AsyncDisplayKit/ASWeakMap.h>
#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
#import <AsyncDisplayKit/_ASCoreAnimationExtras.h>
// TODO: It would be nice to remove this dependency; it's the only subclass using more than +FrameworkSubclasses.h
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
typedef void (^ASImageNodeDrawParametersBlock)(ASWeakMapEntry *entry);
@interface ASImageNodeDrawParameters : NSObject {
@package
UIImage *_image;
BOOL _opaque;
CGRect _bounds;
CGFloat _contentsScale;
UIColor *_backgroundColor;
UIViewContentMode _contentMode;
BOOL _cropEnabled;
BOOL _forceUpscaling;
CGSize _forcedSize;
CGRect _cropRect;
CGRect _cropDisplayBounds;
asimagenode_modification_block_t _imageModificationBlock;
ASDisplayNodeContextModifier _willDisplayNodeContentWithRenderingContext;
ASDisplayNodeContextModifier _didDisplayNodeContentWithRenderingContext;
ASImageNodeDrawParametersBlock _didDrawBlock;
}
@end
@implementation ASImageNodeDrawParameters
@end
/**
* Contains all data that is needed to generate the content bitmap.
*/
@interface ASImageNodeContentsKey : NSObject
@property (nonatomic) UIImage *image;
@property CGSize backingSize;
@property CGRect imageDrawRect;
@property BOOL isOpaque;
@property (nonatomic, copy) UIColor *backgroundColor;
@property (nonatomic) ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext;
@property (nonatomic) ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext;
@property (nonatomic) asimagenode_modification_block_t imageModificationBlock;
@end
@implementation ASImageNodeContentsKey
- (BOOL)isEqual:(id)object
{
if (self == object) {
return YES;
}
// Optimization opportunity: The `isKindOfClass` call here could be avoided by not using the NSObject `isEqual:`
// convention and instead using a custom comparison function that assumes all items are heterogeneous.
// However, profiling shows that our entire `isKindOfClass` expression is only ~1/40th of the total
// overheard of our caching, so it's likely not high-impact.
if ([object isKindOfClass:[ASImageNodeContentsKey class]]) {
ASImageNodeContentsKey *other = (ASImageNodeContentsKey *)object;
return [_image isEqual:other.image]
&& CGSizeEqualToSize(_backingSize, other.backingSize)
&& CGRectEqualToRect(_imageDrawRect, other.imageDrawRect)
&& _isOpaque == other.isOpaque
&& [_backgroundColor isEqual:other.backgroundColor]
&& _willDisplayNodeContentWithRenderingContext == other.willDisplayNodeContentWithRenderingContext
&& _didDisplayNodeContentWithRenderingContext == other.didDisplayNodeContentWithRenderingContext
&& _imageModificationBlock == other.imageModificationBlock;
} else {
return NO;
}
}
- (NSUInteger)hash
{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wpadded"
struct {
NSUInteger imageHash;
CGSize backingSize;
CGRect imageDrawRect;
NSInteger isOpaque;
NSUInteger backgroundColorHash;
void *willDisplayNodeContentWithRenderingContext;
void *didDisplayNodeContentWithRenderingContext;
void *imageModificationBlock;
#pragma clang diagnostic pop
} data = {
_image.hash,
_backingSize,
_imageDrawRect,
_isOpaque,
_backgroundColor.hash,
(void *)_willDisplayNodeContentWithRenderingContext,
(void *)_didDisplayNodeContentWithRenderingContext,
(void *)_imageModificationBlock
};
return ASHashBytes(&data, sizeof(data));
}
@end
@implementation ASImageNode
{
@private
UIImage *_image;
ASWeakMapEntry *_weakCacheEntry; // Holds a reference that keeps our contents in cache.
UIColor *_placeholderColor;
void (^_displayCompletionBlock)(BOOL canceled);
// Drawing
ASTextNode *_debugLabelNode;
// Cropping.
BOOL _cropEnabled; // Defaults to YES.
BOOL _forceUpscaling; //Defaults to NO.
CGSize _forcedSize; //Defaults to CGSizeZero, indicating no forced size.
CGRect _cropRect; // Defaults to CGRectMake(0.5, 0.5, 0, 0)
CGRect _cropDisplayBounds; // Defaults to CGRectNull
}
@synthesize image = _image;
@synthesize imageModificationBlock = _imageModificationBlock;
#pragma mark - Lifecycle
- (instancetype)init
{
if (!(self = [super init]))
return nil;
// TODO can this be removed?
self.contentsScale = ASScreenScale();
self.contentMode = UIViewContentModeScaleAspectFill;
self.opaque = NO;
self.clipsToBounds = YES;
// If no backgroundColor is set to the image node and it's a subview of UITableViewCell, UITableView is setting
// the opaque value of all subviews to YES if highlighting / selection is happening and does not set it back to the
// initial value. With setting a explicit backgroundColor we can prevent that change.
self.backgroundColor = [UIColor clearColor];
_cropEnabled = YES;
_forceUpscaling = NO;
_cropRect = CGRectMake(0.5, 0.5, 0, 0);
_cropDisplayBounds = CGRectNull;
_placeholderColor = ASDisplayNodeDefaultPlaceholderColor();
#ifndef MINIMAL_ASDK
_animatedImageRunLoopMode = ASAnimatedImageDefaultRunLoopMode;
#endif
return self;
}
- (void)dealloc
{
// Invalidate all components around animated images
#ifndef MINIMAL_ASDK
[self invalidateAnimatedImage];
#endif
}
#pragma mark - Placeholder
- (UIImage *)placeholderImage
{
// FIXME: Replace this implementation with reusable CALayers that have .backgroundColor set.
// This would completely eliminate the memory and performance cost of the backing store.
CGSize size = self.calculatedSize;
if ((size.width * size.height) < CGFLOAT_EPSILON) {
return nil;
}
AS::MutexLocker l(__instanceLock__);
ASGraphicsBeginImageContextWithOptions(size, NO, 1);
[self.placeholderColor setFill];
UIRectFill(CGRectMake(0, 0, size.width, size.height));
UIImage *image = ASGraphicsGetImageAndEndCurrentContext();
return image;
}
#pragma mark - Layout and Sizing
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{
const auto image = ASLockedSelf(_image);
if (image == nil) {
return [super calculateSizeThatFits:constrainedSize];
}
return image.size;
}
#pragma mark - Setter / Getter
- (void)setImage:(UIImage *)image
{
AS::MutexLocker l(__instanceLock__);
[self _locked_setImage:image];
}
- (void)_locked_setImage:(UIImage *)image
{
ASAssertLocked(__instanceLock__);
if (ASObjectIsEqual(_image, image)) {
return;
}
UIImage *oldImage = _image;
_image = image;
if (image != nil) {
// We explicitly call setNeedsDisplay in this case, although we know setNeedsDisplay will be called with lock held.
// Therefore we have to be careful in methods that are involved with setNeedsDisplay to not run into a deadlock
[self setNeedsDisplay];
if (_displayWithoutProcessing && ASDisplayNodeThreadIsMain()) {
BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero);
if (stretchable) {
ASDisplayNodeSetResizableContents(self, image);
} else {
self.contents = (id)image.CGImage;
}
return;
}
} else {
self.contents = nil;
}
// Destruction of bigger images on the main thread can be expensive
// and can take some time, so we dispatch onto a bg queue to
// actually dealloc.
CGSize oldImageSize = oldImage.size;
BOOL shouldReleaseImageOnBackgroundThread = oldImageSize.width > kMinReleaseImageOnBackgroundSize.width
|| oldImageSize.height > kMinReleaseImageOnBackgroundSize.height;
if (shouldReleaseImageOnBackgroundThread) {
ASPerformBackgroundDeallocation(&oldImage);
}
}
- (UIImage *)image
{
return ASLockedSelf(_image);
}
- (UIColor *)placeholderColor
{
return ASLockedSelf(_placeholderColor);
}
- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
ASLockScopeSelf();
if (ASCompareAssignCopy(_placeholderColor, placeholderColor)) {
_placeholderEnabled = (placeholderColor != nil);
}
}
#pragma mark - Drawing
- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer
{
ASLockScopeSelf();
ASImageNodeDrawParameters *drawParameters = [[ASImageNodeDrawParameters alloc] init];
drawParameters->_image = _image;
drawParameters->_bounds = [self threadSafeBounds];
drawParameters->_opaque = self.opaque;
drawParameters->_contentsScale = _contentsScaleForDisplay;
drawParameters->_backgroundColor = self.backgroundColor;
drawParameters->_contentMode = self.contentMode;
drawParameters->_cropEnabled = _cropEnabled;
drawParameters->_forceUpscaling = _forceUpscaling;
drawParameters->_forcedSize = _forcedSize;
drawParameters->_cropRect = _cropRect;
drawParameters->_cropDisplayBounds = _cropDisplayBounds;
drawParameters->_imageModificationBlock = _imageModificationBlock;
drawParameters->_willDisplayNodeContentWithRenderingContext = _willDisplayNodeContentWithRenderingContext;
drawParameters->_didDisplayNodeContentWithRenderingContext = _didDisplayNodeContentWithRenderingContext;
// Hack for now to retain the weak entry that was created while this drawing happened
drawParameters->_didDrawBlock = ^(ASWeakMapEntry *entry){
ASLockScopeSelf();
_weakCacheEntry = entry;
};
return drawParameters;
}
+ (UIImage *)displayWithParameters:(id<NSObject>)parameter isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelled
{
ASImageNodeDrawParameters *drawParameter = (ASImageNodeDrawParameters *)parameter;
UIImage *image = drawParameter->_image;
if (image == nil) {
return nil;
}
if (true) {
return image;
}
CGRect drawParameterBounds = drawParameter->_bounds;
BOOL forceUpscaling = drawParameter->_forceUpscaling;
CGSize forcedSize = drawParameter->_forcedSize;
BOOL cropEnabled = drawParameter->_cropEnabled;
BOOL isOpaque = drawParameter->_opaque;
UIColor *backgroundColor = drawParameter->_backgroundColor;
UIViewContentMode contentMode = drawParameter->_contentMode;
CGFloat contentsScale = drawParameter->_contentsScale;
CGRect cropDisplayBounds = drawParameter->_cropDisplayBounds;
CGRect cropRect = drawParameter->_cropRect;
asimagenode_modification_block_t imageModificationBlock = drawParameter->_imageModificationBlock;
ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext = drawParameter->_willDisplayNodeContentWithRenderingContext;
ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext = drawParameter->_didDisplayNodeContentWithRenderingContext;
BOOL hasValidCropBounds = cropEnabled && !CGRectIsEmpty(cropDisplayBounds);
CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : drawParameterBounds);
ASDisplayNodeAssert(contentsScale > 0, @"invalid contentsScale at display time");
// if the image is resizable, bail early since the image has likely already been configured
BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero);
if (stretchable) {
if (imageModificationBlock != NULL) {
image = imageModificationBlock(image);
}
return image;
}
CGSize imageSize = image.size;
CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale);
CGSize boundsSizeInPixels = CGSizeMake(std::floor(bounds.size.width * contentsScale), std::floor(bounds.size.height * contentsScale));
BOOL contentModeSupported = contentMode == UIViewContentModeScaleAspectFill ||
contentMode == UIViewContentModeScaleAspectFit ||
contentMode == UIViewContentModeCenter;
CGSize backingSize = CGSizeZero;
CGRect imageDrawRect = CGRectZero;
if (boundsSizeInPixels.width * contentsScale < 1.0f || boundsSizeInPixels.height * contentsScale < 1.0f ||
imageSizeInPixels.width < 1.0f || imageSizeInPixels.height < 1.0f) {
return nil;
}
// If we're not supposed to do any cropping, just decode image at original size
if (!cropEnabled || !contentModeSupported || stretchable) {
backingSize = imageSizeInPixels;
imageDrawRect = (CGRect){.size = backingSize};
} else {
if (CGSizeEqualToSize(CGSizeZero, forcedSize) == NO) {
//scale forced size
forcedSize.width *= contentsScale;
forcedSize.height *= contentsScale;
}
ASCroppedImageBackingSizeAndDrawRectInBounds(imageSizeInPixels,
boundsSizeInPixels,
contentMode,
cropRect,
forceUpscaling,
forcedSize,
&backingSize,
&imageDrawRect);
}
if (backingSize.width <= 0.0f || backingSize.height <= 0.0f ||
imageDrawRect.size.width <= 0.0f || imageDrawRect.size.height <= 0.0f) {
return nil;
}
ASImageNodeContentsKey *contentsKey = [[ASImageNodeContentsKey alloc] init];
contentsKey.image = image;
contentsKey.backingSize = backingSize;
contentsKey.imageDrawRect = imageDrawRect;
contentsKey.isOpaque = isOpaque;
contentsKey.backgroundColor = backgroundColor;
contentsKey.willDisplayNodeContentWithRenderingContext = willDisplayNodeContentWithRenderingContext;
contentsKey.didDisplayNodeContentWithRenderingContext = didDisplayNodeContentWithRenderingContext;
contentsKey.imageModificationBlock = imageModificationBlock;
if (isCancelled()) {
return nil;
}
ASWeakMapEntry<UIImage *> *entry = [self.class contentsForkey:contentsKey
drawParameters:parameter
isCancelled:isCancelled];
// If nil, we were cancelled.
if (entry == nil) {
return nil;
}
if (drawParameter->_didDrawBlock) {
drawParameter->_didDrawBlock(entry);
}
return entry.value;
}
static ASWeakMap<ASImageNodeContentsKey *, UIImage *> *cache = nil;
+ (ASWeakMapEntry *)contentsForkey:(ASImageNodeContentsKey *)key drawParameters:(id)drawParameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
{
static dispatch_once_t onceToken;
static AS::Mutex *cacheLock = nil;
dispatch_once(&onceToken, ^{
cacheLock = new AS::Mutex();
});
{
AS::MutexLocker l(*cacheLock);
if (!cache) {
cache = [[ASWeakMap alloc] init];
}
ASWeakMapEntry *entry = [cache entryForKey:key];
if (entry != nil) {
return entry;
}
}
// cache miss
UIImage *contents = [self createContentsForkey:key drawParameters:drawParameters isCancelled:isCancelled];
if (contents == nil) { // If nil, we were cancelled
return nil;
}
{
AS::MutexLocker l(*cacheLock);
return [cache setObject:contents forKey:key];
}
}
+ (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key drawParameters:(id)drawParameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
{
// The following `ASGraphicsBeginImageContextWithOptions` call will sometimes take take longer than 5ms on an
// A5 processor for a 400x800 backingSize.
// Check for cancellation before we call it.
if (isCancelled()) {
return nil;
}
// Use contentsScale of 1.0 and do the contentsScale handling in boundsSizeInPixels so ASCroppedImageBackingSizeAndDrawRectInBounds
// will do its rounding on pixel instead of point boundaries
ASGraphicsBeginImageContextWithOptions(key.backingSize, key.isOpaque, 1.0);
BOOL contextIsClean = YES;
CGContextRef context = UIGraphicsGetCurrentContext();
if (context && key.willDisplayNodeContentWithRenderingContext) {
key.willDisplayNodeContentWithRenderingContext(context, drawParameters);
contextIsClean = NO;
}
// if view is opaque, fill the context with background color
if (key.isOpaque && key.backgroundColor) {
[key.backgroundColor setFill];
UIRectFill({ .size = key.backingSize });
contextIsClean = NO;
}
// iOS 9 appears to contain a thread safety regression when drawing the same CGImageRef on
// multiple threads concurrently. In fact, instead of crashing, it appears to deadlock.
// The issue is present in Mac OS X El Capitan and has been seen hanging Pro apps like Adobe Premiere,
// as well as iOS games, and a small number of ASDK apps that provide the same image reference
// to many separate ASImageNodes. A workaround is to set .displaysAsynchronously = NO for the nodes
// that may get the same pointer for a given UI asset image, etc.
// FIXME: We should replace @synchronized here, probably using a global, locked NSMutableSet, and
// only if the object already exists in the set we should create a semaphore to signal waiting threads
// upon removal of the object from the set when the operation completes.
// Another option is to have ASDisplayNode+AsyncDisplay coordinate these cases, and share the decoded buffer.
// Details tracked in https://github.com/facebook/AsyncDisplayKit/issues/1068
UIImage *image = key.image;
BOOL canUseCopy = (contextIsClean || ASImageAlphaInfoIsOpaque(CGImageGetAlphaInfo(image.CGImage)));
CGBlendMode blendMode = canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal;
@synchronized(image) {
[image drawInRect:key.imageDrawRect blendMode:blendMode alpha:1];
}
if (context && key.didDisplayNodeContentWithRenderingContext) {
key.didDisplayNodeContentWithRenderingContext(context, drawParameters);
}
// Check cancellation one last time before forming image.
if (isCancelled()) {
ASGraphicsEndImageContext();
return nil;
}
UIImage *result = ASGraphicsGetImageAndEndCurrentContext();
if (key.imageModificationBlock) {
result = key.imageModificationBlock(result);
}
return result;
}
- (void)displayDidFinish
{
[super displayDidFinish];
__instanceLock__.lock();
UIImage *image = _image;
void (^displayCompletionBlock)(BOOL canceled) = _displayCompletionBlock;
BOOL shouldPerformDisplayCompletionBlock = (image && displayCompletionBlock);
// Clear the ivar now. The block is retained and will be executed shortly.
if (shouldPerformDisplayCompletionBlock) {
_displayCompletionBlock = nil;
}
BOOL hasDebugLabel = (_debugLabelNode != nil);
__instanceLock__.unlock();
// Update the debug label if necessary
if (hasDebugLabel) {
// For debugging purposes we don't care about locking for now
CGSize imageSize = image.size;
CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale);
CGSize boundsSizeInPixels = CGSizeMake(std::floor(self.bounds.size.width * self.contentsScale), std::floor(self.bounds.size.height * self.contentsScale));
CGFloat pixelCountRatio = (imageSizeInPixels.width * imageSizeInPixels.height) / (boundsSizeInPixels.width * boundsSizeInPixels.height);
if (pixelCountRatio != 1.0) {
NSString *scaleString = [NSString stringWithFormat:@"%.2fx", pixelCountRatio];
_debugLabelNode.attributedText = [[NSAttributedString alloc] initWithString:scaleString attributes:[self debugLabelAttributes]];
_debugLabelNode.hidden = NO;
} else {
_debugLabelNode.hidden = YES;
_debugLabelNode.attributedText = nil;
}
}
// If we've got a block to perform after displaying, do it.
if (shouldPerformDisplayCompletionBlock) {
displayCompletionBlock(NO);
}
}
- (void)setNeedsDisplayWithCompletion:(void (^ _Nullable)(BOOL canceled))displayCompletionBlock
{
if (self.displaySuspended) {
if (displayCompletionBlock)
displayCompletionBlock(YES);
return;
}
// Stash the block and call-site queue. We'll invoke it in -displayDidFinish.
{
AS::MutexLocker l(__instanceLock__);
if (_displayCompletionBlock != displayCompletionBlock) {
_displayCompletionBlock = displayCompletionBlock;
}
}
[self setNeedsDisplay];
}
#pragma mark Interface State
- (void)clearContents
{
[super clearContents];
AS::MutexLocker l(__instanceLock__);
_weakCacheEntry = nil; // release contents from the cache.
}
#pragma mark - Cropping
- (BOOL)isCropEnabled
{
AS::MutexLocker l(__instanceLock__);
return _cropEnabled;
}
- (void)setCropEnabled:(BOOL)cropEnabled
{
[self setCropEnabled:cropEnabled recropImmediately:NO inBounds:self.bounds];
}
- (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediately inBounds:(CGRect)cropBounds
{
__instanceLock__.lock();
if (_cropEnabled == cropEnabled) {
__instanceLock__.unlock();
return;
}
_cropEnabled = cropEnabled;
_cropDisplayBounds = cropBounds;
UIImage *image = _image;
__instanceLock__.unlock();
// If we have an image to display, display it, respecting our recrop flag.
if (image != nil) {
ASPerformBlockOnMainThread(^{
if (recropImmediately)
[self displayImmediately];
else
[self setNeedsDisplay];
});
}
}
- (CGRect)cropRect
{
AS::MutexLocker l(__instanceLock__);
return _cropRect;
}
- (void)setCropRect:(CGRect)cropRect
{
{
AS::MutexLocker l(__instanceLock__);
if (CGRectEqualToRect(_cropRect, cropRect)) {
return;
}
_cropRect = cropRect;
}
// TODO: this logic needs to be updated to respect cropRect.
CGSize boundsSize = self.bounds.size;
CGSize imageSize = self.image.size;
BOOL isCroppingImage = ((boundsSize.width < imageSize.width) || (boundsSize.height < imageSize.height));
// Re-display if we need to.
ASPerformBlockOnMainThread(^{
if (self.nodeLoaded && self.contentMode == UIViewContentModeScaleAspectFill && isCroppingImage)
[self setNeedsDisplay];
});
}
- (BOOL)forceUpscaling
{
AS::MutexLocker l(__instanceLock__);
return _forceUpscaling;
}
- (void)setForceUpscaling:(BOOL)forceUpscaling
{
AS::MutexLocker l(__instanceLock__);
_forceUpscaling = forceUpscaling;
}
- (CGSize)forcedSize
{
AS::MutexLocker l(__instanceLock__);
return _forcedSize;
}
- (void)setForcedSize:(CGSize)forcedSize
{
AS::MutexLocker l(__instanceLock__);
_forcedSize = forcedSize;
}
- (asimagenode_modification_block_t)imageModificationBlock
{
AS::MutexLocker l(__instanceLock__);
return _imageModificationBlock;
}
- (void)setImageModificationBlock:(asimagenode_modification_block_t)imageModificationBlock
{
AS::MutexLocker l(__instanceLock__);
_imageModificationBlock = imageModificationBlock;
}
#pragma mark - Debug
- (void)layout
{
[super layout];
if (_debugLabelNode) {
CGSize boundsSize = self.bounds.size;
CGSize debugLabelSize = [_debugLabelNode layoutThatFits:ASSizeRangeMake(CGSizeZero, boundsSize)].size;
CGPoint debugLabelOrigin = CGPointMake(boundsSize.width - debugLabelSize.width,
boundsSize.height - debugLabelSize.height);
_debugLabelNode.frame = (CGRect) {debugLabelOrigin, debugLabelSize};
}
}
- (NSDictionary *)debugLabelAttributes
{
return @{
NSFontAttributeName: [UIFont systemFontOfSize:15.0],
NSForegroundColorAttributeName: [UIColor redColor]
};
}
@end
#pragma mark - Extras
asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat borderWidth, UIColor *borderColor)
{
return ^(UIImage *originalImage) {
ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale);
UIBezierPath *roundOutline = [UIBezierPath bezierPathWithOvalInRect:(CGRect){CGPointZero, originalImage.size}];
// Make the image round
[roundOutline addClip];
// Draw the original image
[originalImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1];
// Draw a border on top.
if (borderWidth > 0.0) {
[borderColor setStroke];
[roundOutline setLineWidth:borderWidth];
[roundOutline stroke];
}
return ASGraphicsGetImageAndEndCurrentContext();
};
}
asimagenode_modification_block_t ASImageNodeTintColorModificationBlock(UIColor *color)
{
return ^(UIImage *originalImage) {
ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale);
// Set color and render template
[color setFill];
UIImage *templateImage = [originalImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[templateImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1];
UIImage *modifiedImage = ASGraphicsGetImageAndEndCurrentContext();
// if the original image was stretchy, keep it stretchy
if (!UIEdgeInsetsEqualToEdgeInsets(originalImage.capInsets, UIEdgeInsetsZero)) {
modifiedImage = [modifiedImage resizableImageWithCapInsets:originalImage.capInsets resizingMode:originalImage.resizingMode];
}
return modifiedImage;
};
}

View File

@ -0,0 +1,791 @@
//
// ASImageNode.mm
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASImageNode.h>
#import <tgmath.h>
#import <AsyncDisplayKit/_ASDisplayLayer.h>
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
#import <AsyncDisplayKit/ASGraphicsContext.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASTextNode.h>
#import <AsyncDisplayKit/ASImageNode+AnimatedImagePrivate.h>
#import <AsyncDisplayKit/ASImageNode+CGExtras.h>
#import <AsyncDisplayKit/AsyncDisplayKit+Debug.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <AsyncDisplayKit/ASEqualityHelpers.h>
#import <AsyncDisplayKit/ASHashing.h>
#import <AsyncDisplayKit/ASWeakMap.h>
#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
#import <AsyncDisplayKit/_ASCoreAnimationExtras.h>
// TODO: It would be nice to remove this dependency; it's the only subclass using more than +FrameworkSubclasses.h
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
typedef void (^ASImageNodeDrawParametersBlock)(ASWeakMapEntry *entry);
@interface ASImageNodeDrawParameters : NSObject {
@package
UIImage *_image;
BOOL _opaque;
CGRect _bounds;
CGFloat _contentsScale;
UIColor *_backgroundColor;
UIViewContentMode _contentMode;
BOOL _cropEnabled;
BOOL _forceUpscaling;
CGSize _forcedSize;
CGRect _cropRect;
CGRect _cropDisplayBounds;
asimagenode_modification_block_t _imageModificationBlock;
ASDisplayNodeContextModifier _willDisplayNodeContentWithRenderingContext;
ASDisplayNodeContextModifier _didDisplayNodeContentWithRenderingContext;
ASImageNodeDrawParametersBlock _didDrawBlock;
}
@end
@implementation ASImageNodeDrawParameters
@end
/**
* Contains all data that is needed to generate the content bitmap.
*/
@interface ASImageNodeContentsKey : NSObject
@property (nonatomic) UIImage *image;
@property CGSize backingSize;
@property CGRect imageDrawRect;
@property BOOL isOpaque;
@property (nonatomic, copy) UIColor *backgroundColor;
@property (nonatomic) ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext;
@property (nonatomic) ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext;
@property (nonatomic) asimagenode_modification_block_t imageModificationBlock;
@end
@implementation ASImageNodeContentsKey
- (BOOL)isEqual:(id)object
{
if (self == object) {
return YES;
}
// Optimization opportunity: The `isKindOfClass` call here could be avoided by not using the NSObject `isEqual:`
// convention and instead using a custom comparison function that assumes all items are heterogeneous.
// However, profiling shows that our entire `isKindOfClass` expression is only ~1/40th of the total
// overheard of our caching, so it's likely not high-impact.
if ([object isKindOfClass:[ASImageNodeContentsKey class]]) {
ASImageNodeContentsKey *other = (ASImageNodeContentsKey *)object;
return [_image isEqual:other.image]
&& CGSizeEqualToSize(_backingSize, other.backingSize)
&& CGRectEqualToRect(_imageDrawRect, other.imageDrawRect)
&& _isOpaque == other.isOpaque
&& [_backgroundColor isEqual:other.backgroundColor]
&& _willDisplayNodeContentWithRenderingContext == other.willDisplayNodeContentWithRenderingContext
&& _didDisplayNodeContentWithRenderingContext == other.didDisplayNodeContentWithRenderingContext
&& _imageModificationBlock == other.imageModificationBlock;
} else {
return NO;
}
}
- (NSUInteger)hash
{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wpadded"
struct {
NSUInteger imageHash;
CGSize backingSize;
CGRect imageDrawRect;
NSInteger isOpaque;
NSUInteger backgroundColorHash;
void *willDisplayNodeContentWithRenderingContext;
void *didDisplayNodeContentWithRenderingContext;
void *imageModificationBlock;
#pragma clang diagnostic pop
} data = {
_image.hash,
_backingSize,
_imageDrawRect,
_isOpaque,
_backgroundColor.hash,
(void *)_willDisplayNodeContentWithRenderingContext,
(void *)_didDisplayNodeContentWithRenderingContext,
(void *)_imageModificationBlock
};
return ASHashBytes(&data, sizeof(data));
}
@end
@implementation ASImageNode
{
@private
UIImage *_image;
ASWeakMapEntry *_weakCacheEntry; // Holds a reference that keeps our contents in cache.
UIColor *_placeholderColor;
void (^_displayCompletionBlock)(BOOL canceled);
// Drawing
ASTextNode *_debugLabelNode;
// Cropping.
BOOL _cropEnabled; // Defaults to YES.
BOOL _forceUpscaling; //Defaults to NO.
CGSize _forcedSize; //Defaults to CGSizeZero, indicating no forced size.
CGRect _cropRect; // Defaults to CGRectMake(0.5, 0.5, 0, 0)
CGRect _cropDisplayBounds; // Defaults to CGRectNull
}
@synthesize image = _image;
@synthesize imageModificationBlock = _imageModificationBlock;
#pragma mark - Lifecycle
- (instancetype)init
{
if (!(self = [super init]))
return nil;
// TODO can this be removed?
self.contentsScale = ASScreenScale();
self.contentMode = UIViewContentModeScaleAspectFill;
self.opaque = NO;
self.clipsToBounds = YES;
// If no backgroundColor is set to the image node and it's a subview of UITableViewCell, UITableView is setting
// the opaque value of all subviews to YES if highlighting / selection is happening and does not set it back to the
// initial value. With setting a explicit backgroundColor we can prevent that change.
self.backgroundColor = [UIColor clearColor];
_cropEnabled = YES;
_forceUpscaling = NO;
_cropRect = CGRectMake(0.5, 0.5, 0, 0);
_cropDisplayBounds = CGRectNull;
_placeholderColor = ASDisplayNodeDefaultPlaceholderColor();
#ifndef MINIMAL_ASDK
_animatedImageRunLoopMode = ASAnimatedImageDefaultRunLoopMode;
#endif
return self;
}
- (void)dealloc
{
// Invalidate all components around animated images
#ifndef MINIMAL_ASDK
[self invalidateAnimatedImage];
#endif
}
#pragma mark - Placeholder
- (UIImage *)placeholderImage
{
// FIXME: Replace this implementation with reusable CALayers that have .backgroundColor set.
// This would completely eliminate the memory and performance cost of the backing store.
CGSize size = self.calculatedSize;
if ((size.width * size.height) < CGFLOAT_EPSILON) {
return nil;
}
ASDN::MutexLocker l(__instanceLock__);
ASGraphicsBeginImageContextWithOptions(size, NO, 1);
[self.placeholderColor setFill];
UIRectFill(CGRectMake(0, 0, size.width, size.height));
UIImage *image = ASGraphicsGetImageAndEndCurrentContext();
return image;
}
#pragma mark - Layout and Sizing
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{
let image = ASLockedSelf(_image);
if (image == nil) {
return [super calculateSizeThatFits:constrainedSize];
}
return image.size;
}
#pragma mark - Setter / Getter
- (void)setImage:(UIImage *)image
{
ASDN::MutexLocker l(__instanceLock__);
[self _locked_setImage:image];
}
- (void)_locked_setImage:(UIImage *)image
{
ASAssertLocked(__instanceLock__);
if (ASObjectIsEqual(_image, image)) {
return;
}
UIImage *oldImage = _image;
_image = image;
if (image != nil) {
// We explicitly call setNeedsDisplay in this case, although we know setNeedsDisplay will be called with lock held.
// Therefore we have to be careful in methods that are involved with setNeedsDisplay to not run into a deadlock
[self setNeedsDisplay];
<<<<<<< HEAD
if (_displayWithoutProcessing && ASDisplayNodeThreadIsMain()) {
BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero);
if (stretchable) {
ASDisplayNodeSetupLayerContentsWithResizableImage(self.layer, image);
} else {
self.contents = (id)image.CGImage;
}
return;
=======
// For debugging purposes we don't care about locking for now
if ([ASImageNode shouldShowImageScalingOverlay] && _debugLabelNode == nil) {
// do not use ASPerformBlockOnMainThread here, if it performs the block synchronously it will continue
// holding the lock while calling addSubnode.
dispatch_async(dispatch_get_main_queue(), ^{
_debugLabelNode = [[ASTextNode alloc] init];
_debugLabelNode.layerBacked = YES;
[self addSubnode:_debugLabelNode];
});
>>>>>>> 565da7d4935740d12fc204aa061faf093831da1e
}
} else {
self.contents = nil;
}
// Destruction of bigger images on the main thread can be expensive
// and can take some time, so we dispatch onto a bg queue to
// actually dealloc.
CGSize oldImageSize = oldImage.size;
BOOL shouldReleaseImageOnBackgroundThread = oldImageSize.width > kMinReleaseImageOnBackgroundSize.width
|| oldImageSize.height > kMinReleaseImageOnBackgroundSize.height;
if (shouldReleaseImageOnBackgroundThread) {
ASPerformBackgroundDeallocation(&oldImage);
}
}
- (UIImage *)image
{
return ASLockedSelf(_image);
}
- (UIColor *)placeholderColor
{
return ASLockedSelf(_placeholderColor);
}
- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
ASLockScopeSelf();
if (ASCompareAssignCopy(_placeholderColor, placeholderColor)) {
_placeholderEnabled = (placeholderColor != nil);
}
}
#pragma mark - Drawing
- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer
{
ASLockScopeSelf();
ASImageNodeDrawParameters *drawParameters = [[ASImageNodeDrawParameters alloc] init];
drawParameters->_image = _image;
drawParameters->_bounds = [self threadSafeBounds];
drawParameters->_opaque = self.opaque;
drawParameters->_contentsScale = _contentsScaleForDisplay;
drawParameters->_backgroundColor = self.backgroundColor;
drawParameters->_contentMode = self.contentMode;
drawParameters->_cropEnabled = _cropEnabled;
drawParameters->_forceUpscaling = _forceUpscaling;
drawParameters->_forcedSize = _forcedSize;
drawParameters->_cropRect = _cropRect;
drawParameters->_cropDisplayBounds = _cropDisplayBounds;
drawParameters->_imageModificationBlock = _imageModificationBlock;
drawParameters->_willDisplayNodeContentWithRenderingContext = _willDisplayNodeContentWithRenderingContext;
drawParameters->_didDisplayNodeContentWithRenderingContext = _didDisplayNodeContentWithRenderingContext;
// Hack for now to retain the weak entry that was created while this drawing happened
drawParameters->_didDrawBlock = ^(ASWeakMapEntry *entry){
ASLockScopeSelf();
_weakCacheEntry = entry;
};
return drawParameters;
}
+ (UIImage *)displayWithParameters:(id<NSObject>)parameter isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelled
{
ASImageNodeDrawParameters *drawParameter = (ASImageNodeDrawParameters *)parameter;
UIImage *image = drawParameter->_image;
if (image == nil) {
return nil;
}
if (true) {
return image;
}
CGRect drawParameterBounds = drawParameter->_bounds;
BOOL forceUpscaling = drawParameter->_forceUpscaling;
CGSize forcedSize = drawParameter->_forcedSize;
BOOL cropEnabled = drawParameter->_cropEnabled;
BOOL isOpaque = drawParameter->_opaque;
UIColor *backgroundColor = drawParameter->_backgroundColor;
UIViewContentMode contentMode = drawParameter->_contentMode;
CGFloat contentsScale = drawParameter->_contentsScale;
CGRect cropDisplayBounds = drawParameter->_cropDisplayBounds;
CGRect cropRect = drawParameter->_cropRect;
asimagenode_modification_block_t imageModificationBlock = drawParameter->_imageModificationBlock;
ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext = drawParameter->_willDisplayNodeContentWithRenderingContext;
ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext = drawParameter->_didDisplayNodeContentWithRenderingContext;
BOOL hasValidCropBounds = cropEnabled && !CGRectIsEmpty(cropDisplayBounds);
CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : drawParameterBounds);
ASDisplayNodeAssert(contentsScale > 0, @"invalid contentsScale at display time");
// if the image is resizable, bail early since the image has likely already been configured
BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero);
if (stretchable) {
if (imageModificationBlock != NULL) {
image = imageModificationBlock(image);
}
return image;
}
CGSize imageSize = image.size;
CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale);
CGSize boundsSizeInPixels = CGSizeMake(std::floor(bounds.size.width * contentsScale), std::floor(bounds.size.height * contentsScale));
BOOL contentModeSupported = contentMode == UIViewContentModeScaleAspectFill ||
contentMode == UIViewContentModeScaleAspectFit ||
contentMode == UIViewContentModeCenter;
CGSize backingSize = CGSizeZero;
CGRect imageDrawRect = CGRectZero;
if (boundsSizeInPixels.width * contentsScale < 1.0f || boundsSizeInPixels.height * contentsScale < 1.0f ||
imageSizeInPixels.width < 1.0f || imageSizeInPixels.height < 1.0f) {
return nil;
}
// If we're not supposed to do any cropping, just decode image at original size
if (!cropEnabled || !contentModeSupported || stretchable) {
backingSize = imageSizeInPixels;
imageDrawRect = (CGRect){.size = backingSize};
} else {
if (CGSizeEqualToSize(CGSizeZero, forcedSize) == NO) {
//scale forced size
forcedSize.width *= contentsScale;
forcedSize.height *= contentsScale;
}
ASCroppedImageBackingSizeAndDrawRectInBounds(imageSizeInPixels,
boundsSizeInPixels,
contentMode,
cropRect,
forceUpscaling,
forcedSize,
&backingSize,
&imageDrawRect);
}
if (backingSize.width <= 0.0f || backingSize.height <= 0.0f ||
imageDrawRect.size.width <= 0.0f || imageDrawRect.size.height <= 0.0f) {
return nil;
}
ASImageNodeContentsKey *contentsKey = [[ASImageNodeContentsKey alloc] init];
contentsKey.image = image;
contentsKey.backingSize = backingSize;
contentsKey.imageDrawRect = imageDrawRect;
contentsKey.isOpaque = isOpaque;
contentsKey.backgroundColor = backgroundColor;
contentsKey.willDisplayNodeContentWithRenderingContext = willDisplayNodeContentWithRenderingContext;
contentsKey.didDisplayNodeContentWithRenderingContext = didDisplayNodeContentWithRenderingContext;
contentsKey.imageModificationBlock = imageModificationBlock;
if (isCancelled()) {
return nil;
}
ASWeakMapEntry<UIImage *> *entry = [self.class contentsForkey:contentsKey
drawParameters:parameter
isCancelled:isCancelled];
// If nil, we were cancelled.
if (entry == nil) {
return nil;
}
if (drawParameter->_didDrawBlock) {
drawParameter->_didDrawBlock(entry);
}
return entry.value;
}
static ASWeakMap<ASImageNodeContentsKey *, UIImage *> *cache = nil;
// Allocate cacheLock on the heap to prevent destruction at app exit (https://github.com/TextureGroup/Texture/issues/136)
static ASDN::StaticMutex& cacheLock = *new ASDN::StaticMutex;
+ (ASWeakMapEntry *)contentsForkey:(ASImageNodeContentsKey *)key drawParameters:(id)drawParameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
{
{
ASDN::StaticMutexLocker l(cacheLock);
if (!cache) {
cache = [[ASWeakMap alloc] init];
}
ASWeakMapEntry *entry = [cache entryForKey:key];
if (entry != nil) {
return entry;
}
}
// cache miss
UIImage *contents = [self createContentsForkey:key drawParameters:drawParameters isCancelled:isCancelled];
if (contents == nil) { // If nil, we were cancelled
return nil;
}
{
ASDN::StaticMutexLocker l(cacheLock);
return [cache setObject:contents forKey:key];
}
}
+ (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key drawParameters:(id)drawParameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
{
// The following `ASGraphicsBeginImageContextWithOptions` call will sometimes take take longer than 5ms on an
// A5 processor for a 400x800 backingSize.
// Check for cancellation before we call it.
if (isCancelled()) {
return nil;
}
// Use contentsScale of 1.0 and do the contentsScale handling in boundsSizeInPixels so ASCroppedImageBackingSizeAndDrawRectInBounds
// will do its rounding on pixel instead of point boundaries
ASGraphicsBeginImageContextWithOptions(key.backingSize, key.isOpaque, 1.0);
BOOL contextIsClean = YES;
CGContextRef context = UIGraphicsGetCurrentContext();
if (context && key.willDisplayNodeContentWithRenderingContext) {
key.willDisplayNodeContentWithRenderingContext(context, drawParameters);
contextIsClean = NO;
}
// if view is opaque, fill the context with background color
if (key.isOpaque && key.backgroundColor) {
[key.backgroundColor setFill];
UIRectFill({ .size = key.backingSize });
contextIsClean = NO;
}
// iOS 9 appears to contain a thread safety regression when drawing the same CGImageRef on
// multiple threads concurrently. In fact, instead of crashing, it appears to deadlock.
// The issue is present in Mac OS X El Capitan and has been seen hanging Pro apps like Adobe Premiere,
// as well as iOS games, and a small number of ASDK apps that provide the same image reference
// to many separate ASImageNodes. A workaround is to set .displaysAsynchronously = NO for the nodes
// that may get the same pointer for a given UI asset image, etc.
// FIXME: We should replace @synchronized here, probably using a global, locked NSMutableSet, and
// only if the object already exists in the set we should create a semaphore to signal waiting threads
// upon removal of the object from the set when the operation completes.
// Another option is to have ASDisplayNode+AsyncDisplay coordinate these cases, and share the decoded buffer.
// Details tracked in https://github.com/facebook/AsyncDisplayKit/issues/1068
UIImage *image = key.image;
BOOL canUseCopy = (contextIsClean || ASImageAlphaInfoIsOpaque(CGImageGetAlphaInfo(image.CGImage)));
CGBlendMode blendMode = canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal;
@synchronized(image) {
[image drawInRect:key.imageDrawRect blendMode:blendMode alpha:1];
}
if (context && key.didDisplayNodeContentWithRenderingContext) {
key.didDisplayNodeContentWithRenderingContext(context, drawParameters);
}
// Check cancellation one last time before forming image.
if (isCancelled()) {
ASGraphicsEndImageContext();
return nil;
}
UIImage *result = ASGraphicsGetImageAndEndCurrentContext();
if (key.imageModificationBlock) {
result = key.imageModificationBlock(result);
}
return result;
}
- (void)displayDidFinish
{
[super displayDidFinish];
__instanceLock__.lock();
void (^displayCompletionBlock)(BOOL canceled) = _displayCompletionBlock;
UIImage *image = _image;
BOOL hasDebugLabel = (_debugLabelNode != nil);
__instanceLock__.unlock();
// Update the debug label if necessary
if (hasDebugLabel) {
// For debugging purposes we don't care about locking for now
CGSize imageSize = image.size;
CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale);
CGSize boundsSizeInPixels = CGSizeMake(std::floor(self.bounds.size.width * self.contentsScale), std::floor(self.bounds.size.height * self.contentsScale));
CGFloat pixelCountRatio = (imageSizeInPixels.width * imageSizeInPixels.height) / (boundsSizeInPixels.width * boundsSizeInPixels.height);
if (pixelCountRatio != 1.0) {
NSString *scaleString = [NSString stringWithFormat:@"%.2fx", pixelCountRatio];
_debugLabelNode.attributedText = [[NSAttributedString alloc] initWithString:scaleString attributes:[self debugLabelAttributes]];
_debugLabelNode.hidden = NO;
} else {
_debugLabelNode.hidden = YES;
_debugLabelNode.attributedText = nil;
}
}
// If we've got a block to perform after displaying, do it.
if (image && displayCompletionBlock) {
displayCompletionBlock(NO);
__instanceLock__.lock();
_displayCompletionBlock = nil;
__instanceLock__.unlock();
}
}
- (void)setNeedsDisplayWithCompletion:(void (^ _Nullable)(BOOL canceled))displayCompletionBlock
{
if (self.displaySuspended) {
if (displayCompletionBlock)
displayCompletionBlock(YES);
return;
}
// Stash the block and call-site queue. We'll invoke it in -displayDidFinish.
{
ASDN::MutexLocker l(__instanceLock__);
if (_displayCompletionBlock != displayCompletionBlock) {
_displayCompletionBlock = displayCompletionBlock;
}
}
[self setNeedsDisplay];
}
#pragma mark Interface State
- (void)clearContents
{
[super clearContents];
ASDN::MutexLocker l(__instanceLock__);
_weakCacheEntry = nil; // release contents from the cache.
}
#pragma mark - Cropping
- (BOOL)isCropEnabled
{
ASDN::MutexLocker l(__instanceLock__);
return _cropEnabled;
}
- (void)setCropEnabled:(BOOL)cropEnabled
{
[self setCropEnabled:cropEnabled recropImmediately:NO inBounds:self.bounds];
}
- (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediately inBounds:(CGRect)cropBounds
{
__instanceLock__.lock();
if (_cropEnabled == cropEnabled) {
__instanceLock__.unlock();
return;
}
_cropEnabled = cropEnabled;
_cropDisplayBounds = cropBounds;
UIImage *image = _image;
__instanceLock__.unlock();
// If we have an image to display, display it, respecting our recrop flag.
if (image != nil) {
ASPerformBlockOnMainThread(^{
if (recropImmediately)
[self displayImmediately];
else
[self setNeedsDisplay];
});
}
}
- (CGRect)cropRect
{
ASDN::MutexLocker l(__instanceLock__);
return _cropRect;
}
- (void)setCropRect:(CGRect)cropRect
{
{
ASDN::MutexLocker l(__instanceLock__);
if (CGRectEqualToRect(_cropRect, cropRect)) {
return;
}
_cropRect = cropRect;
}
// TODO: this logic needs to be updated to respect cropRect.
CGSize boundsSize = self.bounds.size;
CGSize imageSize = self.image.size;
BOOL isCroppingImage = ((boundsSize.width < imageSize.width) || (boundsSize.height < imageSize.height));
// Re-display if we need to.
ASPerformBlockOnMainThread(^{
if (self.nodeLoaded && self.contentMode == UIViewContentModeScaleAspectFill && isCroppingImage)
[self setNeedsDisplay];
});
}
- (BOOL)forceUpscaling
{
ASDN::MutexLocker l(__instanceLock__);
return _forceUpscaling;
}
- (void)setForceUpscaling:(BOOL)forceUpscaling
{
ASDN::MutexLocker l(__instanceLock__);
_forceUpscaling = forceUpscaling;
}
- (CGSize)forcedSize
{
ASDN::MutexLocker l(__instanceLock__);
return _forcedSize;
}
- (void)setForcedSize:(CGSize)forcedSize
{
ASDN::MutexLocker l(__instanceLock__);
_forcedSize = forcedSize;
}
- (asimagenode_modification_block_t)imageModificationBlock
{
ASDN::MutexLocker l(__instanceLock__);
return _imageModificationBlock;
}
- (void)setImageModificationBlock:(asimagenode_modification_block_t)imageModificationBlock
{
ASDN::MutexLocker l(__instanceLock__);
_imageModificationBlock = imageModificationBlock;
}
#pragma mark - Debug
- (void)layout
{
[super layout];
if (_debugLabelNode) {
CGSize boundsSize = self.bounds.size;
CGSize debugLabelSize = [_debugLabelNode layoutThatFits:ASSizeRangeMake(CGSizeZero, boundsSize)].size;
CGPoint debugLabelOrigin = CGPointMake(boundsSize.width - debugLabelSize.width,
boundsSize.height - debugLabelSize.height);
_debugLabelNode.frame = (CGRect) {debugLabelOrigin, debugLabelSize};
}
}
- (NSDictionary *)debugLabelAttributes
{
return @{
NSFontAttributeName: [UIFont systemFontOfSize:15.0],
NSForegroundColorAttributeName: [UIColor redColor]
};
}
@end
#pragma mark - Extras
asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat borderWidth, UIColor *borderColor)
{
return ^(UIImage *originalImage) {
ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale);
UIBezierPath *roundOutline = [UIBezierPath bezierPathWithOvalInRect:(CGRect){CGPointZero, originalImage.size}];
// Make the image round
[roundOutline addClip];
// Draw the original image
[originalImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1];
// Draw a border on top.
if (borderWidth > 0.0) {
[borderColor setStroke];
[roundOutline setLineWidth:borderWidth];
[roundOutline stroke];
}
return ASGraphicsGetImageAndEndCurrentContext();
};
}
asimagenode_modification_block_t ASImageNodeTintColorModificationBlock(UIColor *color)
{
return ^(UIImage *originalImage) {
ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale);
// Set color and render template
[color setFill];
UIImage *templateImage = [originalImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[templateImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1];
UIImage *modifiedImage = ASGraphicsGetImageAndEndCurrentContext();
// if the original image was stretchy, keep it stretchy
if (!UIEdgeInsetsEqualToEdgeInsets(originalImage.capInsets, UIEdgeInsetsZero)) {
modifiedImage = [modifiedImage resizableImageWithCapInsets:originalImage.capInsets resizingMode:originalImage.resizingMode];
}
return modifiedImage;
};
}

View File

@ -0,0 +1,158 @@
//
// ASLocking.h
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <Foundation/Foundation.h>
#import <pthread/sched.h>
#import <AsyncDisplayKit/ASAssert.h>
NS_ASSUME_NONNULL_BEGIN
#define kLockSetCapacity 32
/**
* An extension of NSLocking that supports -tryLock.
*/
@protocol ASLocking <NSLocking>
/// Try to take lock without blocking. Returns whether the lock was taken.
- (BOOL)tryLock;
@end
/**
* A set of locks acquired during ASLockSequence.
*/
typedef struct {
unsigned count;
CFTypeRef _Nullable locks[kLockSetCapacity];
} ASLockSet;
/**
* Declare a lock set that is automatically unlocked at the end of scope.
*
* We use this instead of a scope-locking macro because we want to be able
* to step through the lock sequence block in the debugger.
*/
#define ASScopedLockSet __unused ASLockSet __attribute__((cleanup(ASUnlockSet)))
/**
* A block that attempts to add a lock to a lock sequence.
* Such a block is provided to the caller of ASLockSequence.
*
* Returns whether the lock was added. You should return
* NO from your lock sequence body if it returns NO.
*
* For instance, you might write `return addLock(l1) && addLock(l2)`.
*
* @param lock The lock to attempt to add.
* @return YES if the lock was added, NO otherwise.
*/
typedef BOOL(^ASAddLockBlock)(id<ASLocking> lock);
/**
* A block that attempts to lock multiple locks in sequence.
* Such a block is provided by the caller of ASLockSequence.
*
* The block may be run multiple times, if not all locks are immediately
* available. Therefore the block should be idempotent.
*
* The block should attempt to invoke addLock multiple times with
* different locks. It should return NO as soon as any addLock
* operation fails.
*
* For instance, you might write `return addLock(l1) && addLock(l2)`.
*
* @param addLock A block you can call to attempt to add a lock.
* @return YES if all locks were added, NO otherwise.
*/
typedef BOOL(^ASLockSequenceBlock)(NS_NOESCAPE ASAddLockBlock addLock);
/**
* Unlock and release all of the locks in this lock set.
*/
NS_INLINE void ASUnlockSet(ASLockSet *lockSet) {
for (unsigned i = 0; i < lockSet->count; i++) {
CFTypeRef lock = lockSet->locks[i];
[(__bridge id<ASLocking>)lock unlock];
CFRelease(lock);
}
}
/**
* Take multiple locks "simultaneously," avoiding deadlocks
* caused by lock ordering.
*
* The block you provide should attempt to take a series of locks,
* using the provided `addLock` block. As soon as any addLock fails,
* you should return NO.
*
* For example:
* ASLockSequence(^(ASAddLockBlock addLock) ^{
* return addLock(l0) && addLock(l1);
* });
*
* Note: This function doesn't protect from lock ordering deadlocks if
* one of the locks is already locked (recursive.) Only locks taken
* inside this function are guaranteed not to cause a deadlock.
*/
NS_INLINE ASLockSet ASLockSequence(NS_NOESCAPE ASLockSequenceBlock body)
{
__block ASLockSet locks = (ASLockSet){0, {}};
BOOL (^addLock)(id<ASLocking>) = ^(id<ASLocking> obj) {
// nil lock = ignore.
if (!obj) {
return YES;
}
// If they go over capacity, assert and return YES.
// If we return NO, they will enter an infinite loop.
if (locks.count == kLockSetCapacity) {
ASDisplayNodeCFailAssert(@"Locking more than %d locks at once is not supported.", kLockSetCapacity);
return YES;
}
if ([obj tryLock]) {
locks.locks[locks.count++] = (__bridge_retained CFTypeRef)obj;
return YES;
}
return NO;
};
/**
* Repeatedly try running their block, passing in our `addLock`
* until it succeeds. If it fails, unlock all and yield the thread
* to reduce spinning.
*/
while (true) {
if (body(addLock)) {
// Success
return locks;
} else {
ASUnlockSet(&locks);
locks.count = 0;
sched_yield();
}
}
}
/**
* These Foundation classes already implement -tryLock.
*/
@interface NSLock (ASLocking) <ASLocking>
@end
@interface NSRecursiveLock (ASLocking) <ASLocking>
@end
@interface NSConditionLock (ASLocking) <ASLocking>
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,56 @@
//
// ASMainThreadDeallocation.h
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (ASMainThreadIvarTeardown)
/**
* Call this from -dealloc to schedule this instance's
* ivars for main thread deallocation as needed.
*
* This method includes a check for whether it's on the main thread,
* and it will do nothing in that case.
*/
- (void)scheduleIvarsForMainThreadDeallocation;
@end
@interface NSObject (ASNeedsMainThreadDeallocation)
/**
* Override this property to indicate that instances of this
* class need to be deallocated on the main thread.
* You do not access this property yourself.
*
* The NSObject implementation returns YES if the class name has
* a prefix UI, AV, or CA. This property is also overridden to
* return fixed values for other common classes, such as UIImage,
* UIGestureRecognizer, and UIResponder.
*/
@property (class, readonly) BOOL needsMainThreadDeallocation;
@end
@interface NSProxy (ASNeedsMainThreadDeallocation)
/**
* Override this property to indicate that instances of this
* class need to be deallocated on the main thread.
* You do not access this property yourself.
*
* The NSProxy implementation returns NO because
* proxies almost always hold weak references.
*/
@property (class, readonly) BOOL needsMainThreadDeallocation;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,206 @@
//
// ASMainThreadDeallocation.mm
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASMainThreadDeallocation.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <AsyncDisplayKit/ASLog.h>
#import <AsyncDisplayKit/ASThread.h>
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
@implementation NSObject (ASMainThreadIvarTeardown)
- (void)scheduleIvarsForMainThreadDeallocation
{
if (ASDisplayNodeThreadIsMain()) {
return;
}
NSValue *ivarsObj = [[self class] _ivarsThatMayNeedMainDeallocation];
// Unwrap the ivar array
unsigned int count = 0;
// Will be unused if assertions are disabled.
__unused int scanResult = sscanf(ivarsObj.objCType, "[%u^{objc_ivar}]", &count);
ASDisplayNodeAssert(scanResult == 1, @"Unexpected type in NSValue: %s", ivarsObj.objCType);
Ivar ivars[count];
[ivarsObj getValue:ivars];
for (Ivar ivar : ivars) {
id value = object_getIvar(self, ivar);
if (value == nil) {
continue;
}
if ([object_getClass(value) needsMainThreadDeallocation]) {
as_log_debug(ASMainThreadDeallocationLog(), "%@: Trampolining ivar '%s' value %@ for main deallocation.", self, ivar_getName(ivar), value);
// Release the ivar's reference before handing the object to the queue so we
// don't risk holding onto it longer than the queue does.
object_setIvar(self, ivar, nil);
ASPerformMainThreadDeallocation(&value);
} else {
as_log_debug(ASMainThreadDeallocationLog(), "%@: Not trampolining ivar '%s' value %@.", self, ivar_getName(ivar), value);
}
}
}
/**
* Returns an NSValue-wrapped array of all the ivars in this class or its superclasses
* up through ASDisplayNode, that we expect may need to be deallocated on main.
*
* This method caches its results.
*
* Result is of type NSValue<[Ivar]>
*/
+ (NSValue * _Nonnull)_ivarsThatMayNeedMainDeallocation NS_RETURNS_RETAINED
{
static NSCache<Class, NSValue *> *ivarsCache;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
ivarsCache = [[NSCache alloc] init];
});
NSValue *result = [ivarsCache objectForKey:self];
if (result != nil) {
return result;
}
// Cache miss.
unsigned int resultCount = 0;
static const int kMaxDealloc2MainIvarsPerClassTree = 64;
Ivar resultIvars[kMaxDealloc2MainIvarsPerClassTree];
// Get superclass results first.
Class c = class_getSuperclass(self);
if (c != [NSObject class]) {
NSValue *ivarsObj = [c _ivarsThatMayNeedMainDeallocation];
// Unwrap the ivar array and append it to our working array
unsigned int count = 0;
// Will be unused if assertions are disabled.
__unused int scanResult = sscanf(ivarsObj.objCType, "[%u^{objc_ivar}]", &count);
ASDisplayNodeAssert(scanResult == 1, @"Unexpected type in NSValue: %s", ivarsObj.objCType);
ASDisplayNodeCAssert(resultCount + count < kMaxDealloc2MainIvarsPerClassTree, @"More than %d dealloc2main ivars are not supported. Count: %d", kMaxDealloc2MainIvarsPerClassTree, resultCount + count);
[ivarsObj getValue:resultIvars + resultCount];
resultCount += count;
}
// Now gather ivars from this particular class.
unsigned int allMyIvarsCount;
Ivar *allMyIvars = class_copyIvarList(self, &allMyIvarsCount);
for (NSUInteger i = 0; i < allMyIvarsCount; i++) {
Ivar ivar = allMyIvars[i];
// NOTE: Would be great to exclude weak/unowned ivars, since we don't
// release them. Unfortunately the objc_ivar_management access is private and
// class_getWeakIvarLayout does not have a well-defined structure.
const char *type = ivar_getTypeEncoding(ivar);
if (type != NULL && strcmp(type, @encode(id)) == 0) {
// If it's `id` we have to include it just in case.
resultIvars[resultCount] = ivar;
resultCount += 1;
as_log_verbose(ASMainThreadDeallocationLog(), "%@: Marking ivar '%s' for possible main deallocation due to type id", self, ivar_getName(ivar));
} else {
// If it's an ivar with a static type, check the type.
Class c = ASGetClassFromType(type);
if ([c needsMainThreadDeallocation]) {
resultIvars[resultCount] = ivar;
resultCount += 1;
as_log_verbose(ASMainThreadDeallocationLog(), "%@: Marking ivar '%s' for main deallocation due to class %@", self, ivar_getName(ivar), c);
} else {
as_log_verbose(ASMainThreadDeallocationLog(), "%@: Skipping ivar '%s' for main deallocation.", self, ivar_getName(ivar));
}
}
}
free(allMyIvars);
// Encode the type (array of Ivars) into a string and wrap it in an NSValue
char arrayType[32];
snprintf(arrayType, 32, "[%u^{objc_ivar}]", resultCount);
result = [NSValue valueWithBytes:resultIvars objCType:arrayType];
[ivarsCache setObject:result forKey:self];
return result;
}
@end
@implementation NSObject (ASNeedsMainThreadDeallocation)
+ (BOOL)needsMainThreadDeallocation
{
const auto name = class_getName(self);
if (0 == strncmp(name, "AV", 2) || 0 == strncmp(name, "UI", 2) || 0 == strncmp(name, "CA", 2)) {
return YES;
}
return NO;
}
@end
@implementation CALayer (ASNeedsMainThreadDeallocation)
+ (BOOL)needsMainThreadDeallocation
{
return YES;
}
@end
@implementation UIColor (ASNeedsMainThreadDeallocation)
+ (BOOL)needsMainThreadDeallocation
{
return NO;
}
@end
@implementation UIGestureRecognizer (ASNeedsMainThreadDeallocation)
+ (BOOL)needsMainThreadDeallocation
{
return YES;
}
@end
@implementation UIImage (ASNeedsMainThreadDeallocation)
+ (BOOL)needsMainThreadDeallocation
{
return NO;
}
@end
@implementation UIResponder (ASNeedsMainThreadDeallocation)
+ (BOOL)needsMainThreadDeallocation
{
return YES;
}
@end
@implementation NSProxy (ASNeedsMainThreadDeallocation)
+ (BOOL)needsMainThreadDeallocation
{
return NO;
}
@end

View File

@ -0,0 +1,92 @@
//
// ASMapNode.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASAvailability.h>
#if TARGET_OS_IOS && AS_USE_MAPKIT
#import <AsyncDisplayKit/ASImageNode.h>
#import <MapKit/MapKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Map Annotation options.
* The default behavior is to ignore the annotations' positions, use the region or options specified instead.
* Swift: to select the default behavior, use [].
*/
typedef NS_OPTIONS(NSUInteger, ASMapNodeShowAnnotationsOptions)
{
/** The annotations' positions are ignored, use the region or options specified instead. */
ASMapNodeShowAnnotationsOptionsIgnored = 0,
/** The annotations' positions are used to calculate the region to show in the map, equivalent to showAnnotations:animated. */
ASMapNodeShowAnnotationsOptionsZoomed = 1 << 0,
/** This will only have an effect if combined with the Zoomed state with liveMap turned on.*/
ASMapNodeShowAnnotationsOptionsAnimated = 1 << 1
};
@interface ASMapNode : ASImageNode
/**
The current options of ASMapNode. This can be set at any time and ASMapNode will animate the change.<br><br>This property may be set from a background thread before the node is loaded, and will automatically be applied to define the behavior of the static snapshot (if .liveMap = NO) or the internal MKMapView (otherwise).<br><br> Changes to the region and camera options will only be animated when when the liveMap mode is enabled, otherwise these options will be applied statically to the new snapshot. <br><br> The options object is used to specify properties even when the liveMap mode is enabled, allowing seamless transitions between the snapshot and liveMap (as well as back to the snapshot).
*/
@property (nonatomic) MKMapSnapshotOptions *options;
/** The region is simply the sub-field on the options object. If the objects object is reset,
this will in effect be overwritten and become the value of the .region property on that object.
Defaults to MKCoordinateRegionForMapRect(MKMapRectWorld).
*/
@property (nonatomic) MKCoordinateRegion region;
/**
This is the MKMapView that is the live map part of ASMapNode. This will be nil if .liveMap = NO. Note, MKMapView is *not* thread-safe.
*/
@property (nullable, readonly) MKMapView *mapView;
/**
Set this to YES to turn the snapshot into an interactive MKMapView and vice versa. Defaults to NO. This property may be set on a background thread before the node is loaded, and will automatically be actioned, once the node is loaded.
*/
@property (getter=isLiveMap) BOOL liveMap;
/**
@abstract Whether ASMapNode should automatically request a new map snapshot to correspond to the new node size.
@default Default value is YES.
@discussion If mapSize is set then this will be set to NO, since the size will be the same in all orientations.
*/
@property BOOL needsMapReloadOnBoundsChange;
/**
Set the delegate of the MKMapView. This can be set even before mapView is created and will be set on the map in the case that the liveMap mode is engaged.
If the live map view has been created, this may only be set on the main thread.
*/
@property (nonatomic, weak) id <MKMapViewDelegate> mapDelegate;
/**
* @abstract The annotations to display on the map.
*/
@property (copy) NSArray<id<MKAnnotation>> *annotations;
/**
* @abstract This property specifies how to show the annotations.
* @default Default value is ASMapNodeShowAnnotationsIgnored
*/
@property ASMapNodeShowAnnotationsOptions showAnnotationsOptions;
/**
* @abstract The block which should return annotation image for static map based on provided annotation.
* @discussion This block is executed on an arbitrary serial queue. If this block is nil, standard pin is used.
*/
@property (nullable) UIImage * _Nullable (^imageForStaticMapAnnotationBlock)(id<MKAnnotation> annotation, CGPoint *centerOffset);
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,99 @@
//
// ASMapNode.h
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
<<<<<<< HEAD
#ifndef MINIMAL_ASDK
=======
#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASAvailability.h>
#if TARGET_OS_IOS && AS_USE_MAPKIT
>>>>>>> 565da7d4935740d12fc204aa061faf093831da1e
#import <AsyncDisplayKit/ASImageNode.h>
#import <MapKit/MapKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Map Annotation options.
* The default behavior is to ignore the annotations' positions, use the region or options specified instead.
* Swift: to select the default behavior, use [].
*/
typedef NS_OPTIONS(NSUInteger, ASMapNodeShowAnnotationsOptions)
{
/** The annotations' positions are ignored, use the region or options specified instead. */
ASMapNodeShowAnnotationsOptionsIgnored = 0,
/** The annotations' positions are used to calculate the region to show in the map, equivalent to showAnnotations:animated. */
ASMapNodeShowAnnotationsOptionsZoomed = 1 << 0,
/** This will only have an effect if combined with the Zoomed state with liveMap turned on.*/
ASMapNodeShowAnnotationsOptionsAnimated = 1 << 1
};
@interface ASMapNode : ASImageNode
/**
The current options of ASMapNode. This can be set at any time and ASMapNode will animate the change.<br><br>This property may be set from a background thread before the node is loaded, and will automatically be applied to define the behavior of the static snapshot (if .liveMap = NO) or the internal MKMapView (otherwise).<br><br> Changes to the region and camera options will only be animated when when the liveMap mode is enabled, otherwise these options will be applied statically to the new snapshot. <br><br> The options object is used to specify properties even when the liveMap mode is enabled, allowing seamless transitions between the snapshot and liveMap (as well as back to the snapshot).
*/
@property (nonatomic) MKMapSnapshotOptions *options;
/** The region is simply the sub-field on the options object. If the objects object is reset,
this will in effect be overwritten and become the value of the .region property on that object.
Defaults to MKCoordinateRegionForMapRect(MKMapRectWorld).
*/
@property (nonatomic) MKCoordinateRegion region;
/**
This is the MKMapView that is the live map part of ASMapNode. This will be nil if .liveMap = NO. Note, MKMapView is *not* thread-safe.
*/
@property (nullable, readonly) MKMapView *mapView;
/**
Set this to YES to turn the snapshot into an interactive MKMapView and vice versa. Defaults to NO. This property may be set on a background thread before the node is loaded, and will automatically be actioned, once the node is loaded.
*/
@property (getter=isLiveMap) BOOL liveMap;
/**
@abstract Whether ASMapNode should automatically request a new map snapshot to correspond to the new node size.
@default Default value is YES.
@discussion If mapSize is set then this will be set to NO, since the size will be the same in all orientations.
*/
@property BOOL needsMapReloadOnBoundsChange;
/**
Set the delegate of the MKMapView. This can be set even before mapView is created and will be set on the map in the case that the liveMap mode is engaged.
If the live map view has been created, this may only be set on the main thread.
*/
@property (nonatomic, weak) id <MKMapViewDelegate> mapDelegate;
/**
* @abstract The annotations to display on the map.
*/
@property (copy) NSArray<id<MKAnnotation>> *annotations;
/**
* @abstract This property specifies how to show the annotations.
* @default Default value is ASMapNodeShowAnnotationsIgnored
*/
@property ASMapNodeShowAnnotationsOptions showAnnotationsOptions;
/**
* @abstract The block which should return annotation image for static map based on provided annotation.
* @discussion This block is executed on an arbitrary serial queue. If this block is nil, standard pin is used.
*/
@property (nullable) UIImage * _Nullable (^imageForStaticMapAnnotationBlock)(id<MKAnnotation> annotation, CGPoint *centerOffset);
@end
NS_ASSUME_NONNULL_END
#endif
#endif

Some files were not shown because too many files have changed in this diff Show More