diff --git a/Wallet/BUILD b/Wallet/BUILD index 383843f696..647cb3d39d 100644 --- a/Wallet/BUILD +++ b/Wallet/BUILD @@ -37,6 +37,8 @@ swift_library( deps = [ "//submodules/GZip:GZip", "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/SSignalKit/SSignalKit:SSignalKit", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", ], ) diff --git a/Wallet/SupportFiles/Empty.swift b/Wallet/SupportFiles/Empty.swift index 555a316570..cdeba7ab08 100644 --- a/Wallet/SupportFiles/Empty.swift +++ b/Wallet/SupportFiles/Empty.swift @@ -1,6 +1,9 @@ import Foundation import UIKit import GZip +import AsyncDisplayKit +import SSignalKit +import SwiftSignalKit @objc(Application) final class Application: UIApplication { @@ -16,10 +19,24 @@ final class AppDelegate: NSObject, UIApplicationDelegate { self.window?.rootViewController?.view.backgroundColor = .green self.window?.makeKeyAndVisible() + let node = ASEditableTextNode() + node.frame = CGRect(origin: CGPoint(x: 50.0, y: 50.0), size: CGSize(width: 100.0, height: 100.0)) + node.backgroundColor = .blue + self.window?.rootViewController?.view.addSubnode(node) if #available(iOS 13.0, *) { self.window?.rootViewController?.overrideUserInterfaceStyle = .dark } + let disposable = SSignal.single("abcd")?.start(next: { next in + print("from signal: \(String(describing: next))") + }) + disposable?.dispose() + + let disposable2 = Signal.single(1234).start(next: { next in + print("from swift signal: \(next)") + }) + disposable2.dispose() + return true } } diff --git a/Wallet/Wallet.tulsiproj/Configs/Default.tulsigen b/Wallet/Wallet.tulsiproj/Configs/Default.tulsigen index 5cb19fab09..5f613e0844 100644 --- a/Wallet/Wallet.tulsiproj/Configs/Default.tulsigen +++ b/Wallet/Wallet.tulsiproj/Configs/Default.tulsigen @@ -9,7 +9,7 @@ ], "optionSet" : { "BazelBuildOptionsDebug" : { - "p" : "$(inherited) -j 1" + "p" : "$(inherited) -k --features=swift.use_global_module_cache --spawn_strategy=standalone --strategy=SwiftCompile=standalone" }, "BazelBuildOptionsRelease" : { "p" : "$(inherited)" diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/ASPagerFlowLayout.m b/submodules/AsyncDisplayKit/AsyncDisplayKit/ASPagerFlowLayout.m deleted file mode 100644 index df8ce69868..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/ASPagerFlowLayout.m +++ /dev/null @@ -1,96 +0,0 @@ -// -// ASPagerFlowLayout.m -// AsyncDisplayKit -// -// Created by Levi McCallum on 2/12/16. -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#ifndef MINIMAL_ASDK -#import - -@interface ASPagerFlowLayout () { - BOOL _didRotate; - CGRect _cachedCollectionViewBounds; - NSIndexPath *_currentIndexPath; -} - -@end - -@implementation ASPagerFlowLayout - -- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity -{ - NSInteger currentPage = ceil(proposedContentOffset.x / self.collectionView.bounds.size.width); - _currentIndexPath = [NSIndexPath indexPathForItem:currentPage inSection:0]; - - return [super targetContentOffsetForProposedContentOffset:proposedContentOffset withScrollingVelocity:velocity]; -} - - -- (void)prepareForAnimatedBoundsChange:(CGRect)oldBounds -{ - // Cache the current page if a rotation did happen. This happens before the rotation animation - // is occuring and the bounds changed so we use this as an opportunity to cache the current index path - if (_cachedCollectionViewBounds.size.width != self.collectionView.bounds.size.width) { - _cachedCollectionViewBounds = self.collectionView.bounds; - - // Figurring out current page based on the old bounds visible space - CGRect visibleRect = oldBounds; - - CGFloat visibleXCenter = CGRectGetMidX(visibleRect); - NSArray *layoutAttributes = [self layoutAttributesForElementsInRect:visibleRect]; - for (UICollectionViewLayoutAttributes *attributes in layoutAttributes) { - if ([attributes representedElementCategory] == UICollectionElementCategoryCell && attributes.center.x == visibleXCenter) { - _currentIndexPath = attributes.indexPath; - break; - } - } - - _didRotate = YES; - } - - [super prepareForAnimatedBoundsChange:oldBounds]; -} -- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset -{ - // Don't mess around if the user is interacting with the page node. Although if just a rotation happened we should - // try to use the current index path to not end up setting the target content offset to something in between pages - if (_didRotate || (!self.collectionView.isDecelerating && !self.collectionView.isTracking)) { - _didRotate = NO; - if (_currentIndexPath) { - return [self _targetContentOffsetForItemAtIndexPath:_currentIndexPath proposedContentOffset:proposedContentOffset]; - } - } - - return [super targetContentOffsetForProposedContentOffset:proposedContentOffset]; -} - -- (CGPoint)_targetContentOffsetForItemAtIndexPath:(NSIndexPath *)indexPath proposedContentOffset:(CGPoint)proposedContentOffset -{ - if ([self _dataSourceIsEmpty]) { - return proposedContentOffset; - } - - UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:_currentIndexPath]; - if (attributes == nil) { - return proposedContentOffset; - } - - CGFloat xOffset = (CGRectGetWidth(self.collectionView.bounds) - CGRectGetWidth(attributes.frame)) / 2.0; - return CGPointMake(attributes.frame.origin.x - xOffset, proposedContentOffset.y); -} - -- (BOOL)_dataSourceIsEmpty -{ - return ([self.collectionView numberOfSections] == 0 || - [self.collectionView numberOfItemsInSection:0] == 0); -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.h b/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.h deleted file mode 100644 index 8e0602911e..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// ASLayoutElementInspectorCell.h -// AsyncDisplayKit -// -// Created by Hannah Troisi on 3/27/16. -// Copyright © 2016 Facebook. All rights reserved. -// - -#ifndef MINIMAL_ASDK - -#import - -typedef NS_ENUM(NSInteger, ASLayoutElementPropertyType) { - ASLayoutElementPropertyFlexGrow = 0, - ASLayoutElementPropertyFlexShrink, - ASLayoutElementPropertyAlignSelf, - ASLayoutElementPropertyFlexBasis, - ASLayoutElementPropertySpacingBefore, - ASLayoutElementPropertySpacingAfter, - ASLayoutElementPropertyAscender, - ASLayoutElementPropertyDescender, - ASLayoutElementPropertyCount -}; - -@interface ASLayoutElementInspectorCell : ASCellNode - -- (instancetype)initWithProperty:(ASLayoutElementPropertyType)property layoutElementToEdit:(id)layoutable NS_DESIGNATED_INITIALIZER; - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.m b/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.m deleted file mode 100644 index 7e5ca1ad9c..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.m +++ /dev/null @@ -1,570 +0,0 @@ -// -// ASLayoutElementInspectorCell.m -// AsyncDisplayKit -// -// Created by Hannah Troisi on 3/27/16. -// Copyright © 2016 Facebook. All rights reserved. -// -#ifndef MINIMAL_ASDK -#import -#import - -typedef NS_ENUM(NSInteger, CellDataType) { - CellDataTypeBool, - CellDataTypeFloat, -}; - -__weak static ASLayoutElementInspectorCell *__currentlyOpenedCell = nil; - -@protocol InspectorCellEditingBubbleProtocol -- (void)valueChangedToIndex:(NSUInteger)index; -@end - -@interface ASLayoutElementInspectorCellEditingBubble : ASDisplayNode -@property (nonatomic, strong, readwrite) id delegate; -- (instancetype)initWithEnumOptions:(BOOL)yes enumStrings:(NSArray *)options currentOptionIndex:(NSUInteger)currentOption; -- (instancetype)initWithSliderMinValue:(CGFloat)min maxValue:(CGFloat)max currentValue:(CGFloat)current -;@end - -@interface ASLayoutElementInspectorCell () -@end - -@implementation ASLayoutElementInspectorCell -{ - ASLayoutElementPropertyType _propertyType; - CellDataType _dataType; - id _layoutElementToEdit; - - ASButtonNode *_buttonNode; - ASTextNode *_textNode; - ASTextNode *_textNode2; - - ASLayoutElementInspectorCellEditingBubble *_textBubble; -} - -#pragma mark - Lifecycle - -- (instancetype)initWithProperty:(ASLayoutElementPropertyType)property layoutElementToEdit:(id)layoutElement -{ - self = [super init]; - if (self) { - - _propertyType = property; - _dataType = [ASLayoutElementInspectorCell dataTypeForProperty:property]; - _layoutElementToEdit = layoutElement; - - self.automaticallyManagesSubnodes = YES; - - _buttonNode = [self makeBtnNodeWithTitle:[ASLayoutElementInspectorCell propertyStringForPropertyType:property]]; - [_buttonNode addTarget:self action:@selector(buttonTapped:) forControlEvents:ASControlNodeEventTouchUpInside]; - - _textNode = [[ASTextNode alloc] init]; - _textNode.attributedText = [ASLayoutElementInspectorCell propertyValueAttributedStringForProperty:property withLayoutElement:layoutElement]; - - [self updateButtonStateForProperty:property withLayoutElement:layoutElement]; - - _textNode2 = [[ASTextNode alloc] init]; - _textNode2.attributedText = [ASLayoutElementInspectorCell propertyValueDetailAttributedStringForProperty:property withLayoutElement:layoutElement]; - - } - return self; -} - -- (void)updateButtonStateForProperty:(ASLayoutElementPropertyType)property withLayoutElement:(id)layoutElement -{ - if (property == ASLayoutElementPropertyFlexGrow) { - _buttonNode.selected = layoutElement.style.flexGrow; - } - else if (property == ASLayoutElementPropertyFlexShrink) { - _buttonNode.selected = layoutElement.style.flexShrink; - } -} - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - ASStackLayoutSpec *horizontalSpec = [ASStackLayoutSpec horizontalStackLayoutSpec]; - horizontalSpec.children = @[_buttonNode, _textNode]; - horizontalSpec.style.flexGrow = 1.0; - horizontalSpec.alignItems = ASStackLayoutAlignItemsCenter; - horizontalSpec.justifyContent = ASStackLayoutJustifyContentSpaceBetween; - - ASLayoutSpec *childSpec; - if (_textBubble) { - ASStackLayoutSpec *verticalSpec = [ASStackLayoutSpec verticalStackLayoutSpec]; - verticalSpec.children = @[horizontalSpec, _textBubble]; - verticalSpec.spacing = 8; - verticalSpec.style.flexGrow = 1.0; - _textBubble.style.flexGrow = 1.0; - childSpec = verticalSpec; - } else { - childSpec = horizontalSpec; - } - ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(2, 4, 2, 4) child:childSpec]; - insetSpec.style.flexGrow =1.0; - - return insetSpec; -} - -+ (NSAttributedString *)propertyValueAttributedStringForProperty:(ASLayoutElementPropertyType)property withLayoutElement:(id)layoutElement -{ - NSString *valueString; - - switch (property) { - case ASLayoutElementPropertyFlexGrow: - valueString = layoutElement.style.flexGrow ? @"YES" : @"NO"; - break; - case ASLayoutElementPropertyFlexShrink: - valueString = layoutElement.style.flexShrink ? @"YES" : @"NO"; - break; - case ASLayoutElementPropertyAlignSelf: - valueString = [ASLayoutElementInspectorCell alignSelfEnumValueString:layoutElement.style.alignSelf]; - break; - case ASLayoutElementPropertyFlexBasis: - if (layoutElement.style.flexBasis.unit && layoutElement.style.flexBasis.value) { // ENUM TYPE - valueString = [NSString stringWithFormat:@"%0.0f %@", layoutElement.style.flexBasis.value, - [ASLayoutElementInspectorCell ASRelativeDimensionEnumString:layoutElement.style.alignSelf]]; - } else { - valueString = @"0 pts"; - } - break; - case ASLayoutElementPropertySpacingBefore: - valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.spacingBefore]; - break; - case ASLayoutElementPropertySpacingAfter: - valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.spacingAfter]; - break; - case ASLayoutElementPropertyAscender: - valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.ascender]; - break; - case ASLayoutElementPropertyDescender: - valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.descender]; - break; - default: - valueString = @"?"; - break; - } - return [ASLayoutElementInspectorCell attributedStringFromString:valueString]; -} - -+ (NSAttributedString *)propertyValueDetailAttributedStringForProperty:(ASLayoutElementPropertyType)property withLayoutElement:(id)layoutElement -{ - NSString *valueString; - - switch (property) { - case ASLayoutElementPropertyFlexGrow: - case ASLayoutElementPropertyFlexShrink: - case ASLayoutElementPropertyAlignSelf: - case ASLayoutElementPropertyFlexBasis: - case ASLayoutElementPropertySpacingBefore: - case ASLayoutElementPropertySpacingAfter: - case ASLayoutElementPropertyAscender: - case ASLayoutElementPropertyDescender: - default: - return nil; - } - return [ASLayoutElementInspectorCell attributedStringFromString:valueString]; -} - -- (void)endEditingValue -{ - _textBubble = nil; - __currentlyOpenedCell = nil; - _buttonNode.selected = NO; - [self setNeedsLayout]; -} - -- (void)beginEditingValue -{ - _textBubble.delegate = self; - __currentlyOpenedCell = self; - [self setNeedsLayout]; -} - -- (void)valueChangedToIndex:(NSUInteger)index -{ - switch (_propertyType) { - - case ASLayoutElementPropertyAlignSelf: - _layoutElementToEdit.style.alignSelf = (ASStackLayoutAlignSelf)index; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[ASLayoutElementInspectorCell alignSelfEnumValueString:index]]; - break; - - case ASLayoutElementPropertySpacingBefore: - _layoutElementToEdit.style.spacingBefore = (CGFloat)index; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingBefore]]; - break; - - case ASLayoutElementPropertySpacingAfter: - _layoutElementToEdit.style.spacingAfter = (CGFloat)index; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingAfter]]; - break; - - case ASLayoutElementPropertyAscender: - _layoutElementToEdit.style.ascender = (CGFloat)index; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.ascender]]; - break; - - case ASLayoutElementPropertyDescender: - _layoutElementToEdit.style.descender = (CGFloat)index; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.descender]]; - break; - - default: - break; - } - - [self setNeedsLayout]; -} - -#pragma mark - gesture handling - -- (void)buttonTapped:(ASButtonNode *)sender -{ - BOOL selfIsEditing = (self == __currentlyOpenedCell); - [__currentlyOpenedCell endEditingValue]; - if (selfIsEditing) { - sender.selected = NO; - return; - } - -// NSUInteger currentAlignSelfValue; -// NSUInteger nextAlignSelfValue; -// CGFloat newValue; - - sender.selected = !sender.selected; - switch (_propertyType) { - - case ASLayoutElementPropertyFlexGrow: - _layoutElementToEdit.style.flexGrow = sender.isSelected ? 1.0 : 0.0; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:sender.selected ? @"YES" : @"NO"]; - break; - - case ASLayoutElementPropertyFlexShrink: - _layoutElementToEdit.style.flexShrink = sender.isSelected ? 1.0 : 0.0; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:sender.selected ? @"YES" : @"NO"]; - break; - - case ASLayoutElementPropertyAlignSelf: - _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithEnumOptions:YES - enumStrings:[ASLayoutElementInspectorCell alignSelfEnumStringArray] - currentOptionIndex:_layoutElementToEdit.style.alignSelf]; - - [self beginEditingValue]; -// if ([self layoutSpec]) { -// currentAlignSelfValue = [[self layoutSpec] alignSelf]; -// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0; -// [[self layoutSpec] setAlignSelf:nextAlignSelfValue]; -// -// } else if ([self node]) { -// currentAlignSelfValue = [[self node] alignSelf]; -// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0; -// [[self node] setAlignSelf:nextAlignSelfValue]; -// } - break; - - case ASLayoutElementPropertySpacingBefore: - _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.spacingBefore]; - [self beginEditingValue]; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingBefore]]; - break; - - case ASLayoutElementPropertySpacingAfter: - _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.spacingAfter]; - [self beginEditingValue]; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingAfter]]; - break; - - - case ASLayoutElementPropertyAscender: - _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.ascender]; - [self beginEditingValue]; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.ascender]]; - break; - - case ASLayoutElementPropertyDescender: - _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.descender]; - [self beginEditingValue]; - _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.descender]]; - break; - - default: - break; - } - [self setNeedsLayout]; -} - -#pragma mark - cast layoutElementToEdit - -- (ASDisplayNode *)node -{ - if (_layoutElementToEdit.layoutElementType == ASLayoutElementTypeDisplayNode) { - return (ASDisplayNode *)_layoutElementToEdit; - } - return nil; -} - -- (ASLayoutSpec *)layoutSpec -{ - if (_layoutElementToEdit.layoutElementType == ASLayoutElementTypeLayoutSpec) { - return (ASLayoutSpec *)_layoutElementToEdit; - } - return nil; -} - -#pragma mark - data / property type helper methods - -+ (CellDataType)dataTypeForProperty:(ASLayoutElementPropertyType)property -{ - switch (property) { - - case ASLayoutElementPropertyFlexGrow: - case ASLayoutElementPropertyFlexShrink: - return CellDataTypeBool; - - case ASLayoutElementPropertySpacingBefore: - case ASLayoutElementPropertySpacingAfter: - case ASLayoutElementPropertyAscender: - case ASLayoutElementPropertyDescender: - return CellDataTypeFloat; - - default: - break; - } - return CellDataTypeBool; -} - -+ (NSString *)propertyStringForPropertyType:(ASLayoutElementPropertyType)property -{ - NSString *string; - switch (property) { - case ASLayoutElementPropertyFlexGrow: - string = @"FlexGrow"; - break; - case ASLayoutElementPropertyFlexShrink: - string = @"FlexShrink"; - break; - case ASLayoutElementPropertyAlignSelf: - string = @"AlignSelf"; - break; - case ASLayoutElementPropertyFlexBasis: - string = @"FlexBasis"; - break; - case ASLayoutElementPropertySpacingBefore: - string = @"SpacingBefore"; - break; - case ASLayoutElementPropertySpacingAfter: - string = @"SpacingAfter"; - break; - case ASLayoutElementPropertyAscender: - string = @"Ascender"; - break; - case ASLayoutElementPropertyDescender: - string = @"Descender"; - break; - default: - string = @"Unknown"; - break; - } - return string; -} - -+ (NSDictionary *)alignSelfTypeNames -{ - return @{@(ASStackLayoutAlignSelfAuto) : @"Auto", - @(ASStackLayoutAlignSelfStart) : @"Start", - @(ASStackLayoutAlignSelfEnd) : @"End", - @(ASStackLayoutAlignSelfCenter) : @"Center", - @(ASStackLayoutAlignSelfStretch) : @"Stretch"}; -} - -+ (NSString *)alignSelfEnumValueString:(NSUInteger)type -{ - return [[self class] alignSelfTypeNames][@(type)]; -} - -+ (NSArray *)alignSelfEnumStringArray -{ - return @[@"ASStackLayoutAlignSelfAuto", - @"ASStackLayoutAlignSelfStart", - @"ASStackLayoutAlignSelfEnd", - @"ASStackLayoutAlignSelfCenter", - @"ASStackLayoutAlignSelfStretch"]; -} - -+ (NSDictionary *)ASRelativeDimensionTypeNames -{ - return @{@(ASDimensionUnitPoints) : @"pts", - @(ASDimensionUnitFraction) : @"%"}; -} - -+ (NSString *)ASRelativeDimensionEnumString:(NSUInteger)type -{ - return [[self class] ASRelativeDimensionTypeNames][@(type)]; -} - -#pragma mark - formatting helper methods - -+ (NSAttributedString *)attributedStringFromString:(NSString *)string -{ - return [ASLayoutElementInspectorCell attributedStringFromString:string withTextColor:[UIColor whiteColor]]; -} - -+ (NSAttributedString *)attributedStringFromString:(NSString *)string withTextColor:(nullable UIColor *)color -{ - NSDictionary *attributes = @{NSForegroundColorAttributeName : color, - NSFontAttributeName : [UIFont fontWithName:@"Menlo-Regular" size:12]}; - - return [[NSAttributedString alloc] initWithString:string attributes:attributes]; -} - -- (ASButtonNode *)makeBtnNodeWithTitle:(NSString *)title -{ - UIColor *orangeColor = [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1]; - UIImage *orangeStretchBtnImg = [ASLayoutElementInspectorCell imageForButtonWithBackgroundColor:orangeColor - borderColor:[UIColor whiteColor] - borderWidth:3]; - UIImage *greyStretchBtnImg = [ASLayoutElementInspectorCell imageForButtonWithBackgroundColor:[UIColor darkGrayColor] - borderColor:[UIColor lightGrayColor] - borderWidth:3]; - UIImage *clearStretchBtnImg = [ASLayoutElementInspectorCell imageForButtonWithBackgroundColor:[UIColor clearColor] - borderColor:[UIColor whiteColor] - borderWidth:3]; - ASButtonNode *btn = [[ASButtonNode alloc] init]; - btn.contentEdgeInsets = UIEdgeInsetsMake(5, 5, 5, 5); - [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:title] forState:ASControlStateNormal]; - [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:title withTextColor:[UIColor lightGrayColor]] forState:ASControlStateDisabled]; - [btn setBackgroundImage:clearStretchBtnImg forState:ASControlStateNormal]; - [btn setBackgroundImage:orangeStretchBtnImg forState:ASControlStateSelected]; - [btn setBackgroundImage:greyStretchBtnImg forState:ASControlStateDisabled]; - - return btn; -} - -#define CORNER_RADIUS 3 -+ (UIImage *)imageForButtonWithBackgroundColor:(UIColor *)backgroundColor borderColor:(UIColor *)borderColor borderWidth:(CGFloat)width -{ - CGSize unstretchedSize = CGSizeMake(2 * CORNER_RADIUS + 1, 2 * CORNER_RADIUS + 1); - CGRect rect = (CGRect) {CGPointZero, unstretchedSize}; - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:CORNER_RADIUS]; - - // create a graphics context for the following status button - UIGraphicsBeginImageContextWithOptions(unstretchedSize, NO, 0); - - [path addClip]; - [backgroundColor setFill]; - [path fill]; - - path.lineWidth = width; - [borderColor setStroke]; - [path stroke]; - - UIImage *btnImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - return [btnImage stretchableImageWithLeftCapWidth:CORNER_RADIUS topCapHeight:CORNER_RADIUS]; -} - -@end - - - -@implementation ASLayoutElementInspectorCellEditingBubble -{ - NSMutableArray *_textNodes; - ASDisplayNode *_slider; -} - -- (instancetype)initWithEnumOptions:(BOOL)yes enumStrings:(NSArray *)options currentOptionIndex:(NSUInteger)currentOption -{ - self = [super init]; - if (self) { - self.automaticallyManagesSubnodes = YES; - self.backgroundColor = [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1]; - - _textNodes = [[NSMutableArray alloc] init]; - int index = 0; - for (NSString *optionStr in options) { - ASButtonNode *btn = [[ASButtonNode alloc] init]; - [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:optionStr] forState:ASControlStateNormal]; - [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:optionStr withTextColor:[UIColor redColor]] - forState:ASControlStateSelected]; - [btn addTarget:self action:@selector(enumOptionSelected:) forControlEvents:ASControlNodeEventTouchUpInside]; - btn.selected = (index == currentOption) ? YES : NO; - [_textNodes addObject:btn]; - index++; - } - } - return self; -} - -- (instancetype)initWithSliderMinValue:(CGFloat)min maxValue:(CGFloat)max currentValue:(CGFloat)current -{ - if (self = [super init]) { - self.userInteractionEnabled = YES; - self.automaticallyManagesSubnodes = YES; - self.backgroundColor = [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1]; - - __weak id weakSelf = self; - _slider = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull{ - UISlider *slider = [[UISlider alloc] init]; - slider.minimumValue = min; - slider.maximumValue = max; - slider.value = current; - [slider addTarget:weakSelf action:@selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged]; - - return slider; - }]; - _slider.userInteractionEnabled = YES; - } - return self; -} - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - _slider.style.preferredSize = CGSizeMake(constrainedSize.max.width, 25); - - NSMutableArray *children = [[NSMutableArray alloc] init]; - if (_textNodes) { - ASStackLayoutSpec *textStack = [ASStackLayoutSpec verticalStackLayoutSpec]; - textStack.children = _textNodes; - textStack.spacing = 2; - [children addObject:textStack]; - } - if (_slider) { - _slider.style.flexGrow = 1.0; - [children addObject:_slider]; - } - - ASStackLayoutSpec *verticalStackSpec = [ASStackLayoutSpec verticalStackLayoutSpec]; - verticalStackSpec.children = children; - verticalStackSpec.spacing = 2; - verticalStackSpec.style.flexGrow = 1.0; - verticalStackSpec.style.alignSelf = ASStackLayoutAlignSelfStretch; - - ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(8, 8, 8, 8) child:verticalStackSpec]; - - return insetSpec; -} - -#pragma mark - gesture handling -- (void)enumOptionSelected:(ASButtonNode *)sender -{ - sender.selected = !sender.selected; - for (ASButtonNode *node in _textNodes) { - if (node != sender) { - node.selected = NO; - } - } - [self.delegate valueChangedToIndex:[_textNodes indexOfObject:sender]]; - [self setNeedsLayout]; -} - -- (void)sliderValueChanged:(UISlider *)sender -{ - [self.delegate valueChangedToIndex:roundf(sender.value)]; -} - -@end - - -#endif diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.m b/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.m deleted file mode 100644 index b71c2f7413..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.m +++ /dev/null @@ -1,426 +0,0 @@ -// -// ASLayoutElementInspectorNode.m -// Sample -// -// Created by Hannah Troisi on 3/19/16. -// Copyright © 2016 Facebook. All rights reserved. -// - -#import -#ifndef MINIMAL_ASDK -#import -#endif -#import -#import -#import -#import -#import - -@interface ASLayoutElementInspectorNode () -#ifndef MINIMAL_ASDK - -#endif -@end - -@implementation ASLayoutElementInspectorNode -{ -#ifndef MINIMAL_ASDK - ASTableNode *_tableNode; -#endif -} - -#pragma mark - class methods -+ (instancetype)sharedInstance -{ - static ASLayoutElementInspectorNode *__inspector = nil; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - __inspector = [[ASLayoutElementInspectorNode alloc] init]; - }); - - return __inspector; -} - -#pragma mark - lifecycle -- (instancetype)init -{ - self = [super init]; - if (self) { - -#ifndef MINIMAL_ASDK - _tableNode = [[ASTableNode alloc] init]; - _tableNode.delegate = self; - _tableNode.dataSource = self; - - [self addSubnode:_tableNode]; // required because of manual layout -#endif - } - return self; -} - -- (void)didLoad -{ - [super didLoad]; -#ifndef MINIMAL_ASDK - _tableNode.view.backgroundColor = [UIColor colorWithRed:40/255.0 green:43/255.0 blue:53/255.0 alpha:1]; - _tableNode.view.separatorStyle = UITableViewCellSeparatorStyleNone; - _tableNode.view.allowsSelection = NO; - _tableNode.view.sectionHeaderHeight = 40; -#endif -} - -- (void)layout -{ - [super layout]; -#ifndef MINIMAL_ASDK - _tableNode.frame = self.bounds; -#endif -} - -#pragma mark - intstance methods -- (void)setLayoutElementToEdit:(id)layoutElementToEdit -{ - if (_layoutElementToEdit != layoutElementToEdit) { - _layoutElementToEdit = layoutElementToEdit; - } -#ifndef MINIMAL_ASDK - [_tableNode reloadData]; -#endif -} - -#pragma mark - ASTableDataSource - -#ifndef MINIMAL_ASDK -- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath -{ - if (indexPath.section == 0) { - NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1], - NSFontAttributeName : [UIFont fontWithName:@"Menlo-Regular" size:12]}; - ASTextCellNode *textCell = [[ASTextCellNode alloc] initWithAttributes:attributes insets:UIEdgeInsetsMake(0, 4, 0, 0)]; - textCell.text = [_layoutElementToEdit description]; - return textCell; - } else { - return [[ASLayoutElementInspectorCell alloc] initWithProperty:(ASLayoutElementPropertyType)indexPath.row layoutElementToEdit:_layoutElementToEdit]; - } -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - if (section == 0) { - return 1; - } else { - return ASLayoutElementPropertyCount; - } -} - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView -{ - return 2; -} - -- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section -{ - UILabel *headerTitle = [[UILabel alloc] initWithFrame:CGRectZero]; - - NSString *title; - if (section == 0) { - title = @" Item"; - } else { - title = @" Properties"; - } - - NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor whiteColor], - NSFontAttributeName : [UIFont fontWithName:@"Menlo-Bold" size:12]}; - headerTitle.attributedText = [[NSAttributedString alloc] initWithString:title attributes:attributes]; - - return headerTitle; -} - -#endif - -//- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -//{ -// // navigate layout hierarchy -// -// _parentNodeNavBtn.alignSelf = ASStackLayoutAlignSelfCenter; -// _childNodeNavBtn.alignSelf = ASStackLayoutAlignSelfCenter; -// -// ASStackLayoutSpec *horizontalStackNav = [ASStackLayoutSpec horizontalStackLayoutSpec]; -// horizontalStackNav.style.flexGrow = 1.0; -// horizontalStackNav.alignSelf = ASStackLayoutAlignSelfCenter; -// horizontalStackNav.children = @[_siblingNodeLefttNavBtn, _siblingNodeRightNavBtn]; -// -// ASStackLayoutSpec *horizontalStack = [ASStackLayoutSpec horizontalStackLayoutSpec]; -// horizontalStack.style.flexGrow = 1.0; -// ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; -// -// spacer.style.flexGrow = 1.0; -// horizontalStack.children = @[_flexGrowBtn, spacer]; -// _flexGrowValue.alignSelf = ASStackLayoutAlignSelfEnd; // FIXME: make framework give a warning if you use ASAlignmentBottom!!!!! -// -// ASStackLayoutSpec *horizontalStack2 = [ASStackLayoutSpec horizontalStackLayoutSpec]; -// horizontalStack2.style.flexGrow = 1.0; -// horizontalStack2.children = @[_flexShrinkBtn, spacer]; -// _flexShrinkValue.alignSelf = ASStackLayoutAlignSelfEnd; -// -// ASStackLayoutSpec *horizontalStack3 = [ASStackLayoutSpec horizontalStackLayoutSpec]; -// horizontalStack3.style.flexGrow = 1.0; -// horizontalStack3.children = @[_flexBasisBtn, spacer, _flexBasisValue]; -// _flexBasisValue.alignSelf = ASStackLayoutAlignSelfEnd; -// -// ASStackLayoutSpec *itemDescriptionStack = [ASStackLayoutSpec verticalStackLayoutSpec]; -// itemDescriptionStack.children = @[_itemDescription]; -// itemDescriptionStack.spacing = 5; -// itemDescriptionStack.style.flexGrow = 1.0; -// -// ASStackLayoutSpec *layoutableStack = [ASStackLayoutSpec verticalStackLayoutSpec]; -// layoutableStack.children = @[_layoutablePropertiesSectionTitle, horizontalStack, horizontalStack2, horizontalStack3, _alignSelfBtn]; -// layoutableStack.spacing = 5; -// layoutableStack.style.flexGrow = 1.0; -// -// ASStackLayoutSpec *layoutSpecStack = [ASStackLayoutSpec verticalStackLayoutSpec]; -// layoutSpecStack.children = @[_layoutSpecPropertiesSectionTitle, _alignItemsBtn]; -// layoutSpecStack.spacing = 5; -// layoutSpecStack.style.flexGrow = 1.0; -// -// ASStackLayoutSpec *debugHelpStack = [ASStackLayoutSpec verticalStackLayoutSpec]; -// debugHelpStack.children = @[_debugSectionTitle, _vizNodeInsetSizeBtn, _vizNodeBordersBtn]; -// debugHelpStack.spacing = 5; -// debugHelpStack.style.flexGrow = 1.0; -// -// ASStackLayoutSpec *verticalLayoutableStack = [ASStackLayoutSpec verticalStackLayoutSpec]; -// verticalLayoutableStack.style.flexGrow = 1.0; -// verticalLayoutableStack.spacing = 20; -// verticalLayoutableStack.children = @[_parentNodeNavBtn, horizontalStackNav, _childNodeNavBtn, itemDescriptionStack, layoutableStack, layoutSpecStack, debugHelpStack]; -// verticalLayoutableStack.alignItems = ASStackLayoutAlignItemsStretch; // stretch headerStack to fill horizontal space -// -// ASLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(100, 10, 10, 10) child:verticalLayoutableStack]; -// insetSpec.style.flexGrow = 1.0; -// return insetSpec; -//} -// -//#pragma mark - configure Inspector node for layoutable -//- (void)updateInspectorWithLayoutable -//{ -// _itemDescription.attributedText = [self attributedStringFromLayoutable:_layoutElementToEdit]; -// -// if ([self node]) { -// UIColor *nodeBackgroundColor = [[self node] backgroundColor]; -// UIImage *colorBtnImg = [ASLayoutElementInspectorNode imageForButtonWithBackgroundColor:nodeBackgroundColor -// borderColor:[UIColor whiteColor] -// borderWidth:3]; -// [_itemBackgroundColorBtn setBackgroundImage:colorBtnImg forState:ASControlStateNormal]; -// } else { -// _itemBackgroundColorBtn.enabled = NO; -// } -// -// _flexGrowBtn.selected = [self.layoutElementToEdit flexGrow]; -// _flexGrowValue.attributedText = [self attributedStringFromString: (_flexGrowBtn.selected) ? @"YES" : @"NO"]; -// -// _flexShrinkBtn.selected = self.layoutElementToEdit.style.flexShrink; -// _flexShrinkValue.attributedText = [self attributedStringFromString: (_flexShrinkBtn.selected) ? @"YES" : @"NO"]; -// -// // _flexBasisBtn.selected = self.layoutElementToEdit.style.flexShrink; -// // _flexBasisValue.attributedText = [self attributedStringFromString: (_flexBasisBtn.selected) ? @"YES" : @"NO"]; -// -// -// NSUInteger alignSelfValue = [self.layoutElementToEdit alignSelf]; -// NSString *newTitle = [@"alignSelf:" stringByAppendingString:[self alignSelfName:alignSelfValue]]; -// [_alignSelfBtn setAttributedTitle:[self attributedStringFromString:newTitle] forState:ASControlStateNormal]; -// -// if ([self layoutSpec]) { -// _alignItemsBtn.enabled = YES; -//// NSUInteger alignItemsValue = [[self layoutSpec] alignItems]; -//// newTitle = [@"alignItems:" stringByAppendingString:[self alignSelfName:alignItemsValue]]; -//// [_alignItemsBtn setAttributedTitle:[self attributedStringFromString:newTitle] forState:ASControlStateNormal]; -// } -// -// [self setNeedsLayout]; -//} - - -//- (void)enableInspectorNodesForLayoutable -//{ -// if ([self layoutSpec]) { -// -// _itemBackgroundColorBtn.enabled = YES; -// _flexGrowBtn.enabled = YES; -// _flexShrinkBtn.enabled = YES; -// _flexBasisBtn.enabled = YES; -// _alignSelfBtn.enabled = YES; -// _spacingBeforeBtn.enabled = YES; -// _spacingAfterBtn.enabled = YES; -// _alignItemsBtn.enabled = YES; -// -// } else if ([self node]) { -// -// _itemBackgroundColorBtn.enabled = YES; -// _flexGrowBtn.enabled = YES; -// _flexShrinkBtn.enabled = YES; -// _flexBasisBtn.enabled = YES; -// _alignSelfBtn.enabled = YES; -// _spacingBeforeBtn.enabled = YES; -// _spacingAfterBtn.enabled = YES; -// _alignItemsBtn.enabled = NO; -// -// } else { -// -// _itemBackgroundColorBtn.enabled = NO; -// _flexGrowBtn.enabled = NO; -// _flexShrinkBtn.enabled = NO; -// _flexBasisBtn.enabled = NO; -// _alignSelfBtn.enabled = NO; -// _spacingBeforeBtn.enabled = NO; -// _spacingAfterBtn.enabled = NO; -// _alignItemsBtn.enabled = YES; -// } -//} - -//+ (NSDictionary *)alignSelfTypeNames -//{ -// return @{@(ASStackLayoutAlignSelfAuto) : @"Auto", -// @(ASStackLayoutAlignSelfStart) : @"Start", -// @(ASStackLayoutAlignSelfEnd) : @"End", -// @(ASStackLayoutAlignSelfCenter) : @"Center", -// @(ASStackLayoutAlignSelfStretch) : @"Stretch"}; -//} -// -//- (NSString *)alignSelfName:(NSUInteger)type -//{ -// return [[self class] alignSelfTypeNames][@(type)]; -//} -// -//+ (NSDictionary *)alignItemTypeNames -//{ -// return @{@(ASStackLayoutAlignItemsBaselineFirst) : @"BaselineFirst", -// @(ASStackLayoutAlignItemsBaselineLast) : @"BaselineLast", -// @(ASStackLayoutAlignItemsCenter) : @"Center", -// @(ASStackLayoutAlignItemsEnd) : @"End", -// @(ASStackLayoutAlignItemsStart) : @"Start", -// @(ASStackLayoutAlignItemsStretch) : @"Stretch"}; -//} -// -//- (NSString *)alignItemName:(NSUInteger)type -//{ -// return [[self class] alignItemTypeNames][@(type)]; -//} - -//#pragma mark - gesture handling -//- (void)changeColor:(ASButtonNode *)sender -//{ -// if ([self node]) { -// NSArray *colorArray = @[[UIColor orangeColor], -// [UIColor redColor], -// [UIColor greenColor], -// [UIColor purpleColor]]; -// -// UIColor *nodeBackgroundColor = [(ASDisplayNode *)self.layoutElementToEdit backgroundColor]; -// -// NSUInteger colorIndex = [colorArray indexOfObject:nodeBackgroundColor]; -// colorIndex = (colorIndex + 1 < [colorArray count]) ? colorIndex + 1 : 0; -// -// [[self node] setBackgroundColor: [colorArray objectAtIndex:colorIndex]]; -// } -// -// [self updateInspectorWithLayoutable]; -//} -// -//- (void)setFlexGrowValue:(ASButtonNode *)sender -//{ -// [sender setSelected:!sender.isSelected]; // FIXME: fix ASControlNode documentation that this is automatic - unlike highlighted, it is up to the application to decide when a button should be selected or not. Selected is a more persistant thing and highlighted is for the moment, like as a user has a finger on it, -// -// if ([self layoutSpec]) { -// [[self layoutSpec] setFlexGrow:sender.isSelected]; -// } else if ([self node]) { -// [[self node] setFlexGrow:sender.isSelected]; -// } -// -// [self updateInspectorWithLayoutable]; -//} -// -//- (void)setFlexShrinkValue:(ASButtonNode *)sender -//{ -// [sender setSelected:!sender.isSelected]; // FIXME: fix ASControlNode documentation that this is automatic - unlike highlighted, it is up to the application to decide when a button should be selected or not. Selected is a more persistant thing and highlighted is for the moment, like as a user has a finger on it, -// -// if ([self layoutSpec]) { -// [[self layoutSpec] setFlexShrink:sender.isSelected]; -// } else if ([self node]) { -// [[self node] setFlexShrink:sender.isSelected]; -// } -// -// [self updateInspectorWithLayoutable]; -//} -// -//- (void)setAlignSelfValue:(ASButtonNode *)sender -//{ -// NSUInteger currentAlignSelfValue; -// NSUInteger nextAlignSelfValue; -// -// if ([self layoutSpec]) { -// currentAlignSelfValue = [[self layoutSpec] alignSelf]; -// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0; -// [[self layoutSpec] setAlignSelf:nextAlignSelfValue]; -// -// } else if ([self node]) { -// currentAlignSelfValue = [[self node] alignSelf]; -// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0; -// [[self node] setAlignSelf:nextAlignSelfValue]; -// } -// -// [self updateInspectorWithLayoutable]; -//} -// -//- (void)setAlignItemsValue:(ASButtonNode *)sender -//{ -// NSUInteger currentAlignItemsValue; -// NSUInteger nextAlignItemsValue; -// -// if ([self layoutSpec]) { -// currentAlignItemsValue = [[self layoutSpec] alignSelf]; -// nextAlignItemsValue = (currentAlignItemsValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignItemsValue + 1 : 0; -//// [[self layoutSpec] setAlignItems:nextAlignItemsValue]; -// -// } else if ([self node]) { -// currentAlignItemsValue = [[self node] alignSelf]; -// nextAlignItemsValue = (currentAlignItemsValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignItemsValue + 1 : 0; -//// [[self node] setAlignItems:nextAlignItemsValue]; -// } -// -// [self updateInspectorWithLayoutable]; -//} -//- (void)setFlexBasisValue:(ASButtonNode *)sender -//{ -// [sender setSelected:!sender.isSelected]; // FIXME: fix ASControlNode documentation that this is automatic - unlike highlighted, it is up to the application to decide when a button should be selected or not. Selected is a more persistant thing and highlighted is for the moment, like as a user has a finger on it, -// FIXME: finish -//} -// -//- (void)setVizNodeInsets:(ASButtonNode *)sender -//{ -// BOOL newState = !sender.selected; -// -// if (newState == YES) { -// self.vizNodeInsetSize = 0; -// [self.delegate toggleVisualization:NO]; // FIXME -// [self.delegate toggleVisualization:YES]; // FIXME -// _vizNodeBordersBtn.selected = YES; -// -// } else { -// self.vizNodeInsetSize = 10; -// [self.delegate toggleVisualization:NO]; // FIXME -// [self.delegate toggleVisualization:YES]; // FIXME -// } -// -// sender.selected = newState; -//} -// -//- (void)setVizNodeBorders:(ASButtonNode *)sender -//{ -// BOOL newState = !sender.selected; -// -// [self.delegate toggleVisualization:newState]; // FIXME -// -// sender.selected = newState; -//} - -@end diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASChangeSetDataController.h b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASChangeSetDataController.h deleted file mode 100644 index 49df1dc24d..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASChangeSetDataController.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// ASChangeSetDataController.h -// AsyncDisplayKit -// -// Created by Huy Nguyen on 19/10/15. -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#ifndef MINIMAL_ASDK - -#import - -/** - * @abstract Subclass of ASDataController that simulates ordering of operations in batch updates defined in UITableView and UICollectionView. - * - * @discussion The ordering is achieved by using _ASHierarchyChangeSet to enqueue and sort operations. - * More information about the ordering and the index paths used for operations can be found here: - * https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/TableView_iPhone/ManageInsertDeleteRow/ManageInsertDeleteRow.html#//apple_ref/doc/uid/TP40007451-CH10-SW17 - * - * @see ASDataController - * @see _ASHierarchyChangeSet - */ -@interface ASChangeSetDataController : ASDataController - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASChangeSetDataController.mm b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASChangeSetDataController.mm deleted file mode 100644 index c41820dbb0..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASChangeSetDataController.mm +++ /dev/null @@ -1,212 +0,0 @@ -// -// ASChangeSetDataController.m -// AsyncDisplayKit -// -// Created by Huy Nguyen on 19/10/15. -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#ifndef MINIMAL_ASDK - -#import "ASChangeSetDataController.h" -#import "_ASHierarchyChangeSet.h" -#import "ASAssert.h" -#import "ASDataController+Subclasses.h" - -@implementation ASChangeSetDataController { - NSInteger _changeSetBatchUpdateCounter; - _ASHierarchyChangeSet *_changeSet; -} - -- (void)dealloc -{ - ASDisplayNodeCAssert(_changeSetBatchUpdateCounter == 0, @"ASChangeSetDataController deallocated in the middle of a batch update."); -} - -#pragma mark - Batching (External API) - -- (void)beginUpdates -{ - ASDisplayNodeAssertMainThread(); - if (_changeSetBatchUpdateCounter <= 0) { - _changeSetBatchUpdateCounter = 0; - _changeSet = [[_ASHierarchyChangeSet alloc] initWithOldData:[self itemCountsFromDataSource]]; - } - _changeSetBatchUpdateCounter++; -} - -- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion -{ - ASDisplayNodeAssertMainThread(); - _changeSetBatchUpdateCounter--; - - // Prevent calling endUpdatesAnimated:completion: in an unbalanced way - NSAssert(_changeSetBatchUpdateCounter >= 0, @"endUpdatesAnimated:completion: called without having a balanced beginUpdates call"); - - [_changeSet addCompletionHandler:completion]; - if (_changeSetBatchUpdateCounter == 0) { - void (^batchCompletion)(BOOL) = _changeSet.completionHandler; - - /** - * If the initial reloadData has not been called, just bail because we don't have - * our old data source counts. - * See ASUICollectionViewTests.testThatIssuingAnUpdateBeforeInitialReloadIsUnacceptable - * For the issue that UICollectionView has that we're choosing to workaround. - */ - if (!self.initialReloadDataHasBeenCalled) { - if (batchCompletion != nil) { - batchCompletion(YES); - } - _changeSet = nil; - return; - } - - [self invalidateDataSourceItemCounts]; - - // Attempt to mark the update completed. This is when update validation will occur inside the changeset. - // If an invalid update exception is thrown, we catch it and inject our "validationErrorSource" object, - // which is the table/collection node's data source, into the exception reason to help debugging. - @try { - [_changeSet markCompletedWithNewItemCounts:[self itemCountsFromDataSource]]; - } @catch (NSException *e) { - id responsibleDataSource = self.validationErrorSource; - if (e.name == ASCollectionInvalidUpdateException && responsibleDataSource != nil) { - [NSException raise:ASCollectionInvalidUpdateException format:@"%@: %@", [responsibleDataSource class], e.reason]; - } else { - @throw e; - } - } - - ASDataControllerLogEvent(self, @"triggeredUpdate: %@", _changeSet); - - [super beginUpdates]; - - for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) { - [super deleteRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions]; - } - - for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { - [super deleteSections:change.indexSet withAnimationOptions:change.animationOptions]; - } - - for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { - [super insertSections:change.indexSet withAnimationOptions:change.animationOptions]; - } - - for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) { - [super insertRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions]; - } - -#if ASEVENTLOG_ENABLE - NSString *changeSetDescription = ASObjectDescriptionMakeTiny(_changeSet); - batchCompletion = ^(BOOL finished) { - if (batchCompletion != nil) { - batchCompletion(finished); - } - ASDataControllerLogEvent(self, @"finishedUpdate: %@", changeSetDescription); - }; -#endif - - [super endUpdatesAnimated:animated completion:batchCompletion]; - - _changeSet = nil; - } -} - -- (BOOL)batchUpdating -{ - BOOL batchUpdating = (_changeSetBatchUpdateCounter != 0); - // _changeSet must be available during batch update - ASDisplayNodeAssertTrue(batchUpdating == (_changeSet != nil)); - return batchUpdating; -} - -- (void)waitUntilAllUpdatesAreCommitted -{ - ASDisplayNodeAssertMainThread(); - if (self.batchUpdating) { - // This assertion will be enabled soon. -// ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd)); - return; - } - - [super waitUntilAllUpdatesAreCommitted]; -} - -#pragma mark - Section Editing (External API) - -- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet insertSections:sections animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet deleteSections:sections animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet reloadSections:sections animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:animationOptions]; - [_changeSet insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:animationOptions]; - [self endUpdates]; -} - -#pragma mark - Row Editing (External API) - -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet insertItems:indexPaths animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet deleteItems:indexPaths animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet reloadItems:indexPaths animationOptions:animationOptions]; - [self endUpdates]; -} - -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet deleteItems:@[indexPath] animationOptions:animationOptions]; - [_changeSet insertItems:@[newIndexPath] animationOptions:animationOptions]; - [self endUpdates]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASCollectionDataController.h b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASCollectionDataController.h deleted file mode 100644 index adc1d8d957..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASCollectionDataController.h +++ /dev/null @@ -1,57 +0,0 @@ -// -// ASCollectionDataController.h -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#ifndef MINIMAL_ASDK - -#import - -#import -#import - -@class ASDisplayNode; -@class ASCollectionDataController; -@protocol ASSectionContext; - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASCollectionDataControllerSource - -/** - The constrained size range for layout. - */ -- (ASSizeRange)dataController:(ASCollectionDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -- (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController sections:(NSIndexSet *)sections; - -- (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section; - -- (nullable id)dataController:(ASCollectionDataController *)dataController contextForSection:(NSInteger)section; - -@optional - -- (ASCellNode *)dataController:(ASCollectionDataController *)dataController supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -- (ASCellNodeBlock)dataController:(ASCollectionDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -@end - -@interface ASCollectionDataController : ASDataController - -- (instancetype)initWithDataSource:(id)dataSource eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER; - -- (nullable ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -- (nullable id)contextForSection:(NSInteger)section; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASCollectionDataController.mm b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASCollectionDataController.mm deleted file mode 100644 index 2dc4bb203c..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ /dev/null @@ -1,320 +0,0 @@ -// -// ASCollectionDataController.mm -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// -#ifndef MINIMAL_ASDK -#import - -#import -#import -#import -#import -#import -#import -#import -#import - -//#define LOG(...) NSLog(__VA_ARGS__) -#define LOG(...) - -@interface ASCollectionDataController () { - BOOL _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath; - NSInteger _nextSectionID; - NSMutableArray *_sections; - NSArray *_pendingSections; - - /** - * supplementaryKinds can only be accessed on the main thread - * and so we set this in the -prepare stage, and then read it during the -will - * stage of each update operation. - */ - NSArray *_supplementaryKindsForPendingOperation; -} - -- (id)collectionDataSource; - -@end - -@implementation ASCollectionDataController { - NSMutableDictionary *> *_pendingNodeContexts; -} - -- (instancetype)initWithDataSource:(id)dataSource eventLog:(ASEventLog *)eventLog -{ - self = [super initWithDataSource:dataSource eventLog:eventLog]; - if (self != nil) { - _pendingNodeContexts = [NSMutableDictionary dictionary]; - _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath = [dataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]; - - ASDisplayNodeAssertTrue(_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath || [dataSource respondsToSelector:@selector(dataController:supplementaryNodeOfKind:atIndexPath:)]); - - _nextSectionID = 0; - _sections = [NSMutableArray array]; - } - return self; -} - -- (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount -{ - ASDisplayNodeAssertMainThread(); - NSIndexSet *sections = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newSectionCount)]; - - [_sections removeAllObjects]; - [self _populatePendingSectionsFromDataSource:sections]; - - for (NSString *kind in [self supplementaryKindsInSections:sections]) { - LOG(@"Populating elements of kind: %@", kind); - NSMutableArray *contexts = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts]; - _pendingNodeContexts[kind] = contexts; - } -} - -- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount -{ - NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newSectionCount)]; - - [self applyPendingSections:sectionIndexes]; - - // Assert that ASDataController has already deleted all the old sections for us. - ASDisplayNodeAssert([self editingNodesOfKind:ASDataControllerRowNodeKind].count == 0, @"Expected that all old sections were deleted before %@. Sections: %@", NSStringFromSelector(_cmd), [self editingNodesOfKind:ASDataControllerRowNodeKind]); - - [_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, __unused BOOL * _Nonnull stop) { - // Insert each section - NSMutableArray *sections = [NSMutableArray arrayWithCapacity:newSectionCount]; - for (int i = 0; i < newSectionCount; i++) { - [sections addObject:[NSMutableArray array]]; - } - [self insertSections:sections ofKind:kind atIndexSet:sectionIndexes completion:nil]; - - [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; - }]; - }]; - [_pendingNodeContexts removeAllObjects]; -} - -- (void)prepareForInsertSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - [self _populatePendingSectionsFromDataSource:sections]; - - for (NSString *kind in [self supplementaryKindsInSections:sections]) { - LOG(@"Populating elements of kind: %@, for sections: %@", kind, sections); - NSMutableArray *contexts = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts]; - _pendingNodeContexts[kind] = contexts; - } -} - -- (void)willInsertSections:(NSIndexSet *)sections -{ - [self applyPendingSections:sections]; - - [_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, BOOL * _Nonnull stop) { - NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count]; - for (NSUInteger i = 0; i < sections.count; i++) { - [sectionArray addObject:[NSMutableArray array]]; - } - - [self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil]; - [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; - }]; - }]; - [_pendingNodeContexts removeAllObjects]; -} - -- (void)willDeleteSections:(NSIndexSet *)sections -{ - [_sections removeObjectsAtIndexes:sections]; -} - -- (void)prepareForInsertRowsAtIndexPaths:(NSArray *)indexPaths -{ - ASDisplayNodeAssertMainThread(); - NSIndexSet *sections = [NSIndexSet as_sectionsFromIndexPaths:indexPaths]; - for (NSString *kind in [self supplementaryKindsInSections:sections]) { - LOG(@"Populating elements of kind: %@, for index paths: %@", kind, indexPaths); - NSMutableArray *contexts = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts]; - _pendingNodeContexts[kind] = contexts; - } -} - -- (void)willInsertRowsAtIndexPaths:(NSArray *)indexPaths -{ - [_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, BOOL * _Nonnull stop) { - [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; - }]; - }]; - - [_pendingNodeContexts removeAllObjects]; -} - -- (void)prepareForDeleteRowsAtIndexPaths:(NSArray *)indexPaths -{ - ASDisplayNodeAssertMainThread(); - NSIndexSet *sections = [NSIndexSet as_sectionsFromIndexPaths:indexPaths]; - _supplementaryKindsForPendingOperation = [self supplementaryKindsInSections:sections]; - for (NSString *kind in _supplementaryKindsForPendingOperation) { - NSMutableArray *contexts = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts]; - _pendingNodeContexts[kind] = contexts; - } -} - -- (void)willDeleteRowsAtIndexPaths:(NSArray *)indexPaths -{ - for (NSString *kind in _supplementaryKindsForPendingOperation) { - NSArray *deletedIndexPaths = ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths([self editingNodesOfKind:kind], indexPaths); - - [self deleteNodesOfKind:kind atIndexPaths:deletedIndexPaths completion:nil]; - - // If any of the contexts remain after the deletion, re-insert them, e.g. - // UICollectionElementKindSectionHeader remains even if item 0 is deleted. - NSMutableArray *reinsertedContexts = [NSMutableArray array]; - for (ASIndexedNodeContext *context in _pendingNodeContexts[kind]) { - if ([deletedIndexPaths containsObject:context.indexPath]) { - [reinsertedContexts addObject:context]; - } - } - - [self batchLayoutNodesFromContexts:reinsertedContexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { - [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; - }]; - } - [_pendingNodeContexts removeAllObjects]; - _supplementaryKindsForPendingOperation = nil; -} - -- (void)_populatePendingSectionsFromDataSource:(NSIndexSet *)sectionIndexes -{ - ASDisplayNodeAssertMainThread(); - - NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionIndexes.count]; - [sectionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { - id context = [self.collectionDataSource dataController:self contextForSection:idx]; - [sections addObject:[[ASSection alloc] initWithSectionID:_nextSectionID context:context]]; - _nextSectionID++; - }]; - _pendingSections = sections; -} - -- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withSections:(NSIndexSet *)sections mutableContexts:(NSMutableArray *)contexts -{ - __weak id environment = [self.environmentDelegate dataControllerEnvironment]; - - [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - for (NSUInteger sec = range.location; sec < NSMaxRange(range); sec++) { - NSUInteger itemCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:sec]; - for (NSUInteger i = 0; i < itemCount; i++) { - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sec]; - [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environment:environment]; - } - } - }]; -} - -- (void)_populateSupplementaryNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths mutableContexts:(NSMutableArray *)contexts -{ - __weak id environment = [self.environmentDelegate dataControllerEnvironment]; - - NSMutableIndexSet *sections = [NSMutableIndexSet indexSet]; - for (NSIndexPath *indexPath in indexPaths) { - [sections addIndex:indexPath.section]; - } - - [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - for (NSUInteger sec = range.location; sec < NSMaxRange(range); sec++) { - NSUInteger itemCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:sec]; - for (NSUInteger i = 0; i < itemCount; i++) { - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sec]; - [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environment:environment]; - } - } - }]; -} - -- (void)_populateSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath mutableContexts:(NSMutableArray *)contexts environment:(id)environment -{ - ASCellNodeBlock supplementaryCellBlock; - if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) { - supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; - } else { - ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath]; - supplementaryCellBlock = ^{ return supplementaryNode; }; - } - - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - ASIndexedNodeContext *context = [[ASIndexedNodeContext alloc] initWithNodeBlock:supplementaryCellBlock - indexPath:indexPath - supplementaryElementKind:kind - constrainedSize:constrainedSize - environment:environment]; - [contexts addObject:context]; -} - -#pragma mark - Sizing query - -- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - if ([kind isEqualToString:ASDataControllerRowNodeKind]) { - return [super constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - } else { - ASDisplayNodeAssertMainThread(); - return [self.collectionDataSource dataController:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; - } -} - -#pragma mark - External supplementary store and section context querying - -- (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - NSArray *nodesOfKind = [self completedNodesOfKind:kind]; - NSInteger section = indexPath.section; - if (section < nodesOfKind.count) { - NSArray *nodesOfKindInSection = nodesOfKind[section]; - NSInteger itemIndex = indexPath.item; - if (itemIndex < nodesOfKindInSection.count) { - return nodesOfKindInSection[itemIndex]; - } - } - return nil; -} - -- (id)contextForSection:(NSInteger)section -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssertTrue(section >= 0 && section < _sections.count); - return _sections[section].context; -} - -#pragma mark - Private Helpers - -- (NSArray *)supplementaryKindsInSections:(NSIndexSet *)sections -{ - return [self.collectionDataSource supplementaryNodeKindsInDataController:self sections:sections]; -} - -- (id)collectionDataSource -{ - return (id)self.dataSource; -} - -- (void)applyPendingSections:(NSIndexSet *)sectionIndexes -{ - [_sections insertObjects:_pendingSections atIndexes:sectionIndexes]; - _pendingSections = nil; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASDataController.mm b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASDataController.mm deleted file mode 100644 index 047b59099a..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASDataController.mm +++ /dev/null @@ -1,1093 +0,0 @@ -// -// ASDataController.mm -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// -#ifndef MINIMAL_ASDK -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -//#define LOG(...) NSLog(__VA_ARGS__) -#define LOG(...) - -#define AS_MEASURE_AVOIDED_DATACONTROLLER_WORK 0 - -#define RETURN_IF_NO_DATASOURCE(val) if (_dataSource == nil) { return val; } -#define ASSERT_ON_EDITING_QUEUE ASDisplayNodeAssertNotNil(dispatch_get_specific(&kASDataControllerEditingQueueKey), @"%@ must be called on the editing transaction queue.", NSStringFromSelector(_cmd)) - -const static NSUInteger kASDataControllerSizingCountPerProcessor = 5; -const static char * kASDataControllerEditingQueueKey = "kASDataControllerEditingQueueKey"; -const static char * kASDataControllerEditingQueueContext = "kASDataControllerEditingQueueContext"; - -NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; -NSString * const ASCollectionInvalidUpdateException = @"ASCollectionInvalidUpdateException"; - -#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK -@interface ASDataController (AvoidedWorkMeasuring) -+ (void)_didLayoutNode; -+ (void)_expectToInsertNodes:(NSUInteger)count; -@end -#endif - -@interface ASDataController () { - NSMutableDictionary *_nodeContexts; // Main thread only. This is modified immediately during edits i.e. these are in the dataSource's index space. - NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available. - NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable. - NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propagated to _completedNodes. - BOOL _itemCountsFromDataSourceAreValid; // Main thread only. - std::vector _itemCountsFromDataSource; // Main thread only. - - ASMainSerialQueue *_mainSerialQueue; - - dispatch_queue_t _editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. - dispatch_group_t _editingTransactionGroup; // Group of all edit transaction blocks. Useful for waiting. - - BOOL _initialReloadDataHasBeenCalled; - - BOOL _delegateDidInsertNodes; - BOOL _delegateDidDeleteNodes; - BOOL _delegateDidInsertSections; - BOOL _delegateDidDeleteSections; -} - -@end - -@implementation ASDataController - -#pragma mark - Lifecycle - -- (instancetype)initWithDataSource:(id)dataSource eventLog:(ASEventLog *)eventLog -{ - if (!(self = [super init])) { - return nil; - } - - _dataSource = dataSource; - -#if ASEVENTLOG_ENABLE - _eventLog = eventLog; -#endif - - _nodeContexts = [NSMutableDictionary dictionary]; - _completedNodes = [NSMutableDictionary dictionary]; - _editingNodes = [NSMutableDictionary dictionary]; - - _nodeContexts[ASDataControllerRowNodeKind] = [NSMutableArray array]; - _completedNodes[ASDataControllerRowNodeKind] = [NSMutableArray array]; - _editingNodes[ASDataControllerRowNodeKind] = [NSMutableArray array]; - - _mainSerialQueue = [[ASMainSerialQueue alloc] init]; - - const char *queueName = [[NSString stringWithFormat:@"org.AsyncDisplayKit.ASDataController.editingTransactionQueue:%p", self] cStringUsingEncoding:NSASCIIStringEncoding]; - _editingTransactionQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL); - dispatch_queue_set_specific(_editingTransactionQueue, &kASDataControllerEditingQueueKey, &kASDataControllerEditingQueueContext, NULL); - _editingTransactionGroup = dispatch_group_create(); - - return self; -} - -- (instancetype)init -{ - ASDisplayNodeFailAssert(@"Failed to call designated initializer."); - id fakeDataSource = nil; - ASEventLog *eventLog = nil; - return [self initWithDataSource:fakeDataSource eventLog:eventLog]; -} - -- (void)setDelegate:(id)delegate -{ - if (_delegate == delegate) { - return; - } - - _delegate = delegate; - - // Interrogate our delegate to understand its capabilities, optimizing away expensive respondsToSelector: calls later. - _delegateDidInsertNodes = [_delegate respondsToSelector:@selector(dataController:didInsertNodes:atIndexPaths:withAnimationOptions:)]; - _delegateDidDeleteNodes = [_delegate respondsToSelector:@selector(dataController:didDeleteNodes:atIndexPaths:withAnimationOptions:)]; - _delegateDidInsertSections = [_delegate respondsToSelector:@selector(dataController:didInsertSections:atIndexSet:withAnimationOptions:)]; - _delegateDidDeleteSections = [_delegate respondsToSelector:@selector(dataController:didDeleteSectionsAtIndexSet:withAnimationOptions:)]; -} - -+ (NSUInteger)parallelProcessorCount -{ - static NSUInteger parallelProcessorCount; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - parallelProcessorCount = [[NSProcessInfo processInfo] activeProcessorCount]; - }); - - return parallelProcessorCount; -} - -#pragma mark - Cell Layout - -- (void)batchLayoutNodesFromContexts:(NSArray *)contexts batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler -{ - ASSERT_ON_EDITING_QUEUE; -#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK - [ASDataController _expectToInsertNodes:contexts.count]; -#endif - - if (contexts.count == 0) { - batchCompletionHandler(@[], @[]); - return; - } - - ASProfilingSignpostStart(2, _dataSource); - - NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor; - NSUInteger count = contexts.count; - - // Processing in batches - for (NSUInteger i = 0; i < count; i += blockSize) { - NSRange batchedRange = NSMakeRange(i, MIN(count - i, blockSize)); - NSArray *batchedContexts = [contexts subarrayWithRange:batchedRange]; - NSArray *nodes = [self _layoutNodesFromContexts:batchedContexts]; - NSArray *indexPaths = [ASIndexedNodeContext indexPathsFromContexts:batchedContexts]; - batchCompletionHandler(nodes, indexPaths); - } - - ASProfilingSignpostEnd(2, _dataSource); -} - -/** - * Measure and layout the given node with the constrained size range. - */ -- (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrainedSize -{ - ASDisplayNodeAssert(ASSizeRangeHasSignificantArea(constrainedSize), @"Attempt to layout cell node with invalid size range %@", NSStringFromASSizeRange(constrainedSize)); - - CGRect frame = CGRectZero; - frame.size = [node layoutThatFits:constrainedSize].size; - node.frame = frame; -} - -/** - * Measures and defines the layout for each node in optimized batches on an editing queue, inserting the results into the backing store. - */ -- (void)_batchLayoutAndInsertNodesFromContexts:(NSArray *)contexts withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASSERT_ON_EDITING_QUEUE; - - [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { - // Insert finished nodes into data storage - [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; -} - -- (NSArray *)_layoutNodesFromContexts:(NSArray *)contexts -{ - ASSERT_ON_EDITING_QUEUE; - - NSUInteger nodeCount = contexts.count; - if (!nodeCount || _dataSource == nil) { - return nil; - } - - __strong ASCellNode **allocatedNodeBuffer = (__strong ASCellNode **)calloc(nodeCount, sizeof(ASCellNode *)); - - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - ASDispatchApply(nodeCount, queue, 0, ^(size_t i) { - RETURN_IF_NO_DATASOURCE(); - - // Allocate the node. - ASIndexedNodeContext *context = contexts[i]; - ASCellNode *node = context.node; - if (node == nil) { - ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, self.dataSource); - node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps. - } - - // Layout the node if the size range is valid. - ASSizeRange sizeRange = context.constrainedSize; - if (ASSizeRangeHasSignificantArea(sizeRange)) { - [self _layoutNode:node withConstrainedSize:sizeRange]; - } - -#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK - [ASDataController _didLayoutNode]; -#endif - allocatedNodeBuffer[i] = node; - }); - - BOOL canceled = _dataSource == nil; - - // Create nodes array - NSArray *nodes = canceled ? nil : [NSArray arrayWithObjects:allocatedNodeBuffer count:nodeCount]; - - // Nil out buffer indexes to allow arc to free the stored cells. - for (int i = 0; i < nodeCount; i++) { - allocatedNodeBuffer[i] = nil; - } - free(allocatedNodeBuffer); - - return nodes; -} - -- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - return [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath]; -} - -#pragma mark - External Data Querying + Editing - -- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock -{ - ASSERT_ON_EDITING_QUEUE; - if (!indexPaths.count || _dataSource == nil) { - return; - } - - NSMutableArray *editingNodes = _editingNodes[kind]; - ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes); - - // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. - NSMutableArray *completedNodes = ASTwoDimensionalArrayDeepMutableCopy(editingNodes); - - [_mainSerialQueue performBlockOnMainThread:^{ - _completedNodes[kind] = completedNodes; - if (completionBlock) { - completionBlock(nodes, indexPaths); - } - }]; -} - -- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock -{ - ASSERT_ON_EDITING_QUEUE; - if (!indexPaths.count || _dataSource == nil) { - return; - } - - LOG(@"_deleteNodesAtIndexPaths:%@ ofKind:%@, full index paths in _editingNodes = %@", indexPaths, kind, ASIndexPathsForTwoDimensionalArray(_editingNodes[kind])); - ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[kind], indexPaths); - - [_mainSerialQueue performBlockOnMainThread:^{ - NSMutableArray *allNodes = _completedNodes[kind]; - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(allNodes, indexPaths); - ASDeleteElementsInMultidimensionalArrayAtIndexPaths(allNodes, indexPaths); - if (completionBlock) { - completionBlock(nodes, indexPaths); - } - }]; -} - -- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock -{ - ASSERT_ON_EDITING_QUEUE; - if (!indexSet.count|| _dataSource == nil) { - return; - } - - if (_editingNodes[kind] == nil) { - _editingNodes[kind] = [NSMutableArray array]; - } - - [_editingNodes[kind] insertObjects:sections atIndexes:indexSet]; - - // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. - NSArray *sectionsForCompleted = ASTwoDimensionalArrayDeepMutableCopy(sections); - - [_mainSerialQueue performBlockOnMainThread:^{ - [_completedNodes[kind] insertObjects:sectionsForCompleted atIndexes:indexSet]; - if (completionBlock) { - completionBlock(sections, indexSet); - } - }]; -} - -- (void)deleteSections:(NSIndexSet *)indexSet completion:(void (^)())completionBlock -{ - ASSERT_ON_EDITING_QUEUE; - if (!indexSet.count || _dataSource == nil) { - return; - } - - [_editingNodes enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray *sections, BOOL * _Nonnull stop) { - [sections removeObjectsAtIndexes:indexSet]; - }]; - [_mainSerialQueue performBlockOnMainThread:^{ - [_completedNodes enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray *sections, BOOL * _Nonnull stop) { - [sections removeObjectsAtIndexes:indexSet]; - }]; - if (completionBlock) { - completionBlock(); - } - }]; -} - -#pragma mark - Internal Data Querying + Editing - -/** - * Inserts the specified nodes into the given index paths and notifies the delegate of newly inserted nodes. - * - * @discussion Nodes are first inserted into the editing store, then the completed store is replaced by a deep copy - * of the editing nodes. The delegate is invoked on the main thread. - */ -- (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASSERT_ON_EDITING_QUEUE; - - [self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - ASDisplayNodeAssertMainThread(); - - if (_delegateDidInsertNodes) - [_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; -} - -/** - * Removes the specified nodes at the given index paths and notifies the delegate of the nodes removed. - * - * @discussion Nodes are first removed from the editing store then removed from the completed store on the main thread. - * Once the backing stores are consistent, the delegate is invoked on the main thread. - */ -- (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASSERT_ON_EDITING_QUEUE; - - [self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) { - ASDisplayNodeAssertMainThread(); - - if (_delegateDidDeleteNodes) - [_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - }]; -} - -/** - * Inserts sections, represented as arrays, into the backing store at the given indices and notifies the delegate. - * - * @discussion The section arrays are inserted into the editing store, then a deep copy of the sections are inserted - * in the completed store on the main thread. The delegate is invoked on the main thread. - */ -- (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASSERT_ON_EDITING_QUEUE; - - [self insertSections:sections ofKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSArray *sections, NSIndexSet *indexSet) { - ASDisplayNodeAssertMainThread(); - - if (_delegateDidInsertSections) - [_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions]; - }]; -} - -/** - * Removes sections at the given indices from the backing store and notifies the delegate. - * - * @discussion Section array are first removed from the editing store, then the associated section in the completed - * store is removed on the main thread. The delegate is invoked on the main thread. - */ -- (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASSERT_ON_EDITING_QUEUE; - - [self deleteSections:indexSet completion:^() { - ASDisplayNodeAssertMainThread(); - - if (_delegateDidDeleteSections) - [_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - }]; -} - -#pragma mark - Initial Load & Full Reload (External API) - -- (void)reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions completion:(void (^)())completion -{ - [self _reloadDataWithAnimationOptions:animationOptions synchronously:NO completion:completion]; -} - -- (void)reloadDataImmediatelyWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - [self _reloadDataWithAnimationOptions:animationOptions synchronously:YES completion:nil]; -} - -- (void)_reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions synchronously:(BOOL)synchronously completion:(void (^)())completion -{ - ASDisplayNodeAssertMainThread(); - - _initialReloadDataHasBeenCalled = YES; - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - [self invalidateDataSourceItemCounts]; - NSUInteger sectionCount = [self itemCountsFromDataSource].size(); - NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; - NSArray *newContexts = [self _populateNodeContextsFromDataSourceForSections:sectionIndexes]; - - // Update _nodeContexts - NSMutableArray *allContexts = _nodeContexts[ASDataControllerRowNodeKind]; - [allContexts removeAllObjects]; - NSArray *nodeIndexPaths = [ASIndexedNodeContext indexPathsFromContexts:newContexts]; - for (int i = 0; i < sectionCount; i++) { - [allContexts addObject:[[NSMutableArray alloc] init]]; - } - ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(allContexts, nodeIndexPaths, newContexts); - - // Allow subclasses to perform setup before going into the edit transaction - [self prepareForReloadDataWithSectionCount:sectionCount]; - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - LOG(@"Edit Transaction - reloadData"); - - /** - * Leave the current data in the collection view until the first batch of nodes are laid out. - * Once the first batch is laid out, in one operation, replace all the sections and insert - * the first batch of items. - * - * We previously would replace all the sections immediately, and then start adding items as they - * were laid out. This resulted in more traffic to the UICollectionView and it also caused all the - * section headers to bunch up until the items come and fill out the sections. - */ - __block BOOL isFirstBatch = YES; - [self batchLayoutNodesFromContexts:newContexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { - if (isFirstBatch) { - // -beginUpdates - [_mainSerialQueue performBlockOnMainThread:^{ - [_delegate dataControllerBeginUpdates:self]; - [_delegate dataControllerWillDeleteAllData:self]; - }]; - - // deleteSections: - // Remove everything that existed before the reload, now that we're ready to insert replacements - NSUInteger oldSectionCount = [_editingNodes[ASDataControllerRowNodeKind] count]; - if (oldSectionCount) { - NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, oldSectionCount)]; - [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - } - - [self willReloadDataWithSectionCount:sectionCount]; - - // insertSections: - NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; - for (int i = 0; i < sectionCount; i++) { - [sections addObject:[[NSMutableArray alloc] init]]; - } - [self _insertSections:sections atIndexSet:sectionIndexes withAnimationOptions:animationOptions]; - } - - // insertItemsAtIndexPaths: - [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; - - if (isFirstBatch) { - // -endUpdates - [_mainSerialQueue performBlockOnMainThread:^{ - [_delegate dataController:self endUpdatesAnimated:NO completion:nil]; - }]; - isFirstBatch = NO; - } - }]; - - if (completion) { - [_mainSerialQueue performBlockOnMainThread:completion]; - } - }); - if (synchronously) { - [self waitUntilAllUpdatesAreCommitted]; - } -} - -- (void)waitUntilAllUpdatesAreCommitted -{ - ASDisplayNodeAssertMainThread(); - - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - // Schedule block in main serial queue to wait until all operations are finished that are - // where scheduled while waiting for the _editingTransactionQueue to finish - [_mainSerialQueue performBlockOnMainThread:^{ }]; -} - -#pragma mark - Data Source Access (Calling _dataSource) - -/** - * Fetches row contexts for the provided sections from the data source. - */ -- (NSArray *)_populateNodeContextsFromDataSourceForSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - - __weak id environment = [self.environmentDelegate dataControllerEnvironment]; - - std::vector counts = [self itemCountsFromDataSource]; - NSMutableArray *contexts = [NSMutableArray array]; - [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - for (NSUInteger sectionIndex = range.location; sectionIndex < NSMaxRange(range); sectionIndex++) { - NSUInteger itemCount = counts[sectionIndex]; - for (NSUInteger i = 0; i < itemCount; i++) { - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sectionIndex]; - ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; - - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; - [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock - indexPath:indexPath - supplementaryElementKind:nil - constrainedSize:constrainedSize - environment:environment]]; - } - } - }]; - return contexts; -} - -- (void)invalidateDataSourceItemCounts -{ - ASDisplayNodeAssertMainThread(); - _itemCountsFromDataSourceAreValid = NO; -} - -- (std::vector)itemCountsFromDataSource -{ - ASDisplayNodeAssertMainThread(); - if (NO == _itemCountsFromDataSourceAreValid) { - id source = self.dataSource; - NSInteger sectionCount = [source numberOfSectionsInDataController:self]; - std::vector newCounts; - newCounts.reserve(sectionCount); - for (NSInteger i = 0; i < sectionCount; i++) { - newCounts.push_back([source dataController:self rowsInSection:i]); - } - _itemCountsFromDataSource = newCounts; - _itemCountsFromDataSourceAreValid = YES; - } - return _itemCountsFromDataSource; -} - -#pragma mark - Batching (External API) - -- (void)beginUpdates -{ - ASDisplayNodeAssertMainThread(); - // TODO: make this -waitUntilAllUpdatesAreCommitted? - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [_mainSerialQueue performBlockOnMainThread:^{ - // Deep copy _completedNodes to _externalCompletedNodes. - // Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate. - _externalCompletedNodes = ASTwoDimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]); - - LOG(@"beginUpdates - begin updates call to delegate"); - [_delegate dataControllerBeginUpdates:self]; - }]; - }); -} - -- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion -{ - LOG(@"endUpdatesWithCompletion - beginning"); - ASDisplayNodeAssertMainThread(); - - // Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates. - // Each subsequent command in the queue will also wait on the full asynchronous completion of the prior command's edit transaction. - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [_mainSerialQueue performBlockOnMainThread:^{ - // Now that the transaction is done, _completedNodes can be accessed externally again. - _externalCompletedNodes = nil; - - LOG(@"endUpdatesWithCompletion - calling delegate end"); - [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; - }]; - }); -} - -- (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet animated:(BOOL)animated -{ - ASDisplayNodeAssertMainThread(); - - void (^batchCompletion)(BOOL) = changeSet.completionHandler; - - /** - * If the initial reloadData has not been called, just bail because we don't have - * our old data source counts. - * See ASUICollectionViewTests.testThatIssuingAnUpdateBeforeInitialReloadIsUnacceptable - * For the issue that UICollectionView has that we're choosing to workaround. - */ - if (!self.initialReloadDataHasBeenCalled) { - if (batchCompletion != nil) { - batchCompletion(YES); - } - return; - } - - [self invalidateDataSourceItemCounts]; - - // Attempt to mark the update completed. This is when update validation will occur inside the changeset. - // If an invalid update exception is thrown, we catch it and inject our "validationErrorSource" object, - // which is the table/collection node's data source, into the exception reason to help debugging. - @try { - [changeSet markCompletedWithNewItemCounts:[self itemCountsFromDataSource]]; - } @catch (NSException *e) { - id responsibleDataSource = self.validationErrorSource; - if (e.name == ASCollectionInvalidUpdateException && responsibleDataSource != nil) { - [NSException raise:ASCollectionInvalidUpdateException format:@"%@: %@", [responsibleDataSource class], e.reason]; - } else { - @throw e; - } - } - - ASDataControllerLogEvent(self, @"triggeredUpdate: %@", changeSet); - - [self beginUpdates]; - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) { - [self deleteRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions]; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { - [self deleteSections:change.indexSet withAnimationOptions:change.animationOptions]; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { - [self insertSections:change.indexSet withAnimationOptions:change.animationOptions]; - } - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) { - [self insertRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions]; - } - -#if ASEVENTLOG_ENABLE - NSString *changeSetDescription = ASObjectDescriptionMakeTiny(changeSet); - batchCompletion = ^(BOOL finished) { - if (batchCompletion != nil) { - batchCompletion(finished); - } - ASDataControllerLogEvent(self, @"finishedUpdate: %@", changeSetDescription); - }; -#endif - - [self endUpdatesAnimated:animated completion:batchCompletion]; -} - -#pragma mark - Section Editing (External API) - -- (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - insertSections: %@", sections); - if (!_initialReloadDataHasBeenCalled) { - return; - } - - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - NSArray *contexts = [self _populateNodeContextsFromDataSourceForSections:sections]; - - // Update _nodeContexts - { - NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count]; - for (NSUInteger i = 0; i < sections.count; i++) { - [sectionArray addObject:[NSMutableArray array]]; - } - NSMutableArray *allRowContexts = _nodeContexts[ASDataControllerRowNodeKind]; - [allRowContexts insertObjects:sectionArray atIndexes:sections]; - ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(allRowContexts, [ASIndexedNodeContext indexPathsFromContexts:contexts], contexts); - } - - [self prepareForInsertSections:sections]; - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willInsertSections:sections]; - - LOG(@"Edit Transaction - insertSections: %@", sections); - NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count]; - for (NSUInteger i = 0; i < sections.count; i++) { - [sectionArray addObject:[NSMutableArray array]]; - } - - [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; - - [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; - }); -} - -- (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - deleteSections: %@", sections); - if (!_initialReloadDataHasBeenCalled) { - return; - } - - [_nodeContexts[ASDataControllerRowNodeKind] removeObjectsAtIndexes:sections]; - - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - [self prepareForDeleteSections:sections]; - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willDeleteSections:sections]; - - // remove elements - LOG(@"Edit Transaction - deleteSections: %@", sections); - [self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; - }); -} - -#pragma mark - Backing store manipulation optional hooks (Subclass API) - -- (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)prepareForInsertSections:(NSIndexSet *)sections -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)prepareForDeleteSections:(NSIndexSet *)sections -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willInsertSections:(NSIndexSet *)sections -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willDeleteSections:(NSIndexSet *)sections -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)prepareForInsertRowsAtIndexPaths:(NSArray *)indexPaths -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willInsertRowsAtIndexPaths:(NSArray *)indexPaths -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)prepareForDeleteRowsAtIndexPaths:(NSArray *)indexPaths -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -- (void)willDeleteRowsAtIndexPaths:(NSArray *)indexPaths -{ - // Optional template hook for subclasses (See ASDataController+Subclasses.h) -} - -#pragma mark - Row Editing (External API) - -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - if (!_initialReloadDataHasBeenCalled) { - return; - } - - LOG(@"Edit Command - insertRows: %@", indexPaths); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - // Sort indexPath to avoid messing up the index when inserting in several batches - NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; - NSMutableArray *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - - __weak id environment = [self.environmentDelegate dataControllerEnvironment]; - - for (NSIndexPath *indexPath in sortedIndexPaths) { - ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; - [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock - indexPath:indexPath - supplementaryElementKind:nil - constrainedSize:constrainedSize - environment:environment]]; - } - - ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(_nodeContexts[ASDataControllerRowNodeKind], sortedIndexPaths, contexts); - [self prepareForInsertRowsAtIndexPaths:indexPaths]; - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willInsertRowsAtIndexPaths:indexPaths]; - - LOG(@"Edit Transaction - insertRows: %@", indexPaths); - [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; - }); -} - -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions -{ - ASDisplayNodeAssertMainThread(); - - if (!_initialReloadDataHasBeenCalled) { - return; - } - - LOG(@"Edit Command - deleteRows: %@", indexPaths); - - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - // Sort indexPath in order to avoid messing up the index when deleting in several batches. - // FIXME: Shouldn't deletes be sorted in descending order? - NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; - - ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_nodeContexts[ASDataControllerRowNodeKind], sortedIndexPaths); - [self prepareForDeleteRowsAtIndexPaths:sortedIndexPaths]; - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willDeleteRowsAtIndexPaths:sortedIndexPaths]; - - LOG(@"Edit Transaction - deleteRows: %@", indexPaths); - [self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; - }); -} - -- (void)relayoutAllNodes -{ - ASDisplayNodeAssertMainThread(); - if (!_initialReloadDataHasBeenCalled) { - return; - } - - LOG(@"Edit Command - relayoutRows"); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - // Can't relayout right away because _completedNodes may not be up-to-date, - // i.e there might be some nodes that were measured using the old constrained size but haven't been added to _completedNodes - // (see _layoutNodes:atIndexPaths:withAnimationOptions:). - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [_mainSerialQueue performBlockOnMainThread:^{ - for (NSString *kind in _completedNodes) { - [self _relayoutNodesOfKind:kind]; - } - }]; - }); -} - -- (void)_relayoutNodesOfKind:(NSString *)kind -{ - ASDisplayNodeAssertMainThread(); - NSArray *nodes = [self completedNodesOfKind:kind]; - if (!nodes.count) { - return; - } - - NSUInteger sectionIndex = 0; - for (NSMutableArray *section in nodes) { - NSUInteger rowIndex = 0; - for (ASCellNode *node in section) { - RETURN_IF_NO_DATASOURCE(); - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex]; - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - if (ASSizeRangeHasSignificantArea(constrainedSize)) { - [self _layoutNode:node withConstrainedSize:constrainedSize]; - } - rowIndex += 1; - } - sectionIndex += 1; - } -} - -#pragma mark - Data Querying (Subclass API) - -- (NSMutableArray *)editingNodesOfKind:(NSString *)kind -{ - return _editingNodes[kind] ? : [NSMutableArray array]; -} - -- (NSMutableArray *)completedNodesOfKind:(NSString *)kind -{ - return _completedNodes[kind]; -} - -#pragma mark - Data Querying (External API) - -- (NSUInteger)numberOfSections -{ - ASDisplayNodeAssertMainThread(); - return [_nodeContexts[ASDataControllerRowNodeKind] count]; -} - -- (NSUInteger)numberOfRowsInSection:(NSUInteger)section -{ - ASDisplayNodeAssertMainThread(); - NSArray *contextSections = _nodeContexts[ASDataControllerRowNodeKind]; - return (section < contextSections.count) ? [contextSections[section] count] : 0; -} - -- (NSUInteger)completedNumberOfSections -{ - ASDisplayNodeAssertMainThread(); - return [[self completedNodes] count]; -} - -- (NSUInteger)completedNumberOfRowsInSection:(NSUInteger)section -{ - ASDisplayNodeAssertMainThread(); - NSArray *completedNodes = [self completedNodes]; - return (section < completedNodes.count) ? [completedNodes[section] count] : 0; -} - -- (ASCellNode *)nodeAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - if (indexPath == nil) { - return nil; - } - - NSArray *contexts = _nodeContexts[ASDataControllerRowNodeKind]; - NSInteger section = indexPath.section; - NSInteger row = indexPath.row; - ASIndexedNodeContext *context = nil; - - if (section >= 0 && row >= 0 && section < contexts.count) { - NSArray *completedNodesSection = contexts[section]; - if (row < completedNodesSection.count) { - context = completedNodesSection[row]; - } - } - return context.node; -} - -- (ASCellNode *)nodeAtCompletedIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - if (indexPath == nil) { - return nil; - } - - NSArray *completedNodes = [self completedNodes]; - NSInteger section = indexPath.section; - NSInteger row = indexPath.row; - ASCellNode *node = nil; - - if (section >= 0 && row >= 0 && section < completedNodes.count) { - NSArray *completedNodesSection = completedNodes[section]; - if (row < completedNodesSection.count) { - node = completedNodesSection[row]; - } - } - - return node; -} - -- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; -{ - ASDisplayNodeAssertMainThread(); - if (cellNode == nil) { - return nil; - } - - NSString *kind = cellNode.supplementaryElementKind ?: ASDataControllerRowNodeKind; - NSArray *contexts = _nodeContexts[kind]; - - // Check if the cached index path is still correct. - NSIndexPath *indexPath = cellNode.cachedIndexPath; - if (indexPath != nil) { - ASIndexedNodeContext *context = ASGetElementInTwoDimensionalArray(contexts, indexPath); - if (context.nodeIfAllocated == cellNode) { - return indexPath; - } else { - indexPath = nil; - } - } - - // Loop through each section to look for the node context - NSInteger section = 0; - for (NSArray *nodeContexts in contexts) { - NSUInteger item = [nodeContexts indexOfObjectPassingTest:^BOOL(ASIndexedNodeContext * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - return obj.nodeIfAllocated == cellNode; - }]; - if (item != NSNotFound) { - indexPath = [NSIndexPath indexPathForItem:item inSection:section]; - break; - } - section += 1; - } - cellNode.cachedIndexPath = indexPath; - return indexPath; -} - -- (NSIndexPath *)completedIndexPathForNode:(ASCellNode *)cellNode -{ - ASDisplayNodeAssertMainThread(); - if (cellNode == nil) { - return nil; - } - - NSInteger section = 0; - // Loop through each section to look for the cellNode - NSString *kind = cellNode.supplementaryElementKind ?: ASDataControllerRowNodeKind; - for (NSArray *sectionNodes in [self completedNodesOfKind:kind]) { - NSUInteger item = [sectionNodes indexOfObjectIdenticalTo:cellNode]; - if (item != NSNotFound) { - return [NSIndexPath indexPathForItem:item inSection:section]; - } - section += 1; - } - - return nil; -} - -/// Returns nodes that can be queried externally. _externalCompletedNodes is used if available, _completedNodes otherwise. -- (NSArray *)completedNodes -{ - ASDisplayNodeAssertMainThread(); - return _externalCompletedNodes ? : _completedNodes[ASDataControllerRowNodeKind]; -} - -- (void)moveCompletedNodeAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath -{ - ASDisplayNodeAssertMainThread(); - ASMoveElementInTwoDimensionalArray(_externalCompletedNodes, indexPath, newIndexPath); - ASMoveElementInTwoDimensionalArray(_completedNodes[ASDataControllerRowNodeKind], indexPath, newIndexPath); -} - -@end - -#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK - -static volatile int64_t _totalExpectedItems = 0; -static volatile int64_t _totalMeasuredNodes = 0; - -@implementation ASDataController (WorkMeasuring) - -+ (void)_didLayoutNode -{ - int64_t measured = OSAtomicIncrement64(&_totalMeasuredNodes); - int64_t expected = _totalExpectedItems; - if (measured % 20 == 0 || measured == expected) { - NSLog(@"Data controller avoided work (underestimated): %lld / %lld", measured, expected); - } -} - -+ (void)_expectToInsertNodes:(NSUInteger)count -{ - OSAtomicAdd64((int64_t)count, &_totalExpectedItems); -} - -@end -#endif - -#endif diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASFlowLayoutController.h b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASFlowLayoutController.h deleted file mode 100644 index 7eeb4d3533..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASFlowLayoutController.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// ASFlowLayoutController.h -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// -#ifndef MINIMAL_ASDK -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASCellNode; - -typedef NS_ENUM(NSUInteger, ASFlowLayoutDirection) { - ASFlowLayoutDirectionVertical, - ASFlowLayoutDirectionHorizontal, -}; - -@protocol ASFlowLayoutControllerDataSource - -- (NSArray *> *)completedNodes; // This provides access to ASDataController's _completedNodes multidimensional array. - -@end - -/** - * An optimized flow layout controller that supports only vertical or horizontal scrolling, not simultaneously two-dimensional scrolling. - * It is used for all ASTableViews, and may be used with ASCollectionView. - */ -@interface ASFlowLayoutController : ASAbstractLayoutController - -@property (nonatomic, readonly, assign) ASFlowLayoutDirection layoutDirection; -@property (nonatomic, readwrite, weak) id dataSource; - -- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASFlowLayoutController.mm b/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASFlowLayoutController.mm deleted file mode 100644 index 4a23ec25a6..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Details/ASFlowLayoutController.mm +++ /dev/null @@ -1,203 +0,0 @@ -// -// ASFlowLayoutController.mm -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// -#ifndef MINIMAL_ASDK -#import -#import -#import -#import -#import - -#include -#include - -@interface ASFlowLayoutController() -{ - ASIndexPathRange _visibleRange; - std::vector _rangesByType; // All ASLayoutRangeTypes besides visible. -} - -@end - -@implementation ASFlowLayoutController - -- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection -{ - if (!(self = [super init])) { - return nil; - } - _layoutDirection = layoutDirection; - _rangesByType = std::vector(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 diff --git a/submodules/AsyncDisplayKit/AsyncDisplayKit/Private/ASDataController+Subclasses.h b/submodules/AsyncDisplayKit/AsyncDisplayKit/Private/ASDataController+Subclasses.h deleted file mode 100644 index 8061e25a8d..0000000000 --- a/submodules/AsyncDisplayKit/AsyncDisplayKit/Private/ASDataController+Subclasses.h +++ /dev/null @@ -1,181 +0,0 @@ -// -// 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 - -@class ASIndexedNodeContext; - -typedef void (^ASDataControllerCompletionBlock)(NSArray *nodes, NSArray *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 *)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 *)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 *)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 *)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 *)indexPaths; - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/BUILD b/submodules/AsyncDisplayKit/BUILD index bd59107e16..cfed6ac319 100644 --- a/submodules/AsyncDisplayKit/BUILD +++ b/submodules/AsyncDisplayKit/BUILD @@ -1,116 +1,11 @@ -public_header_names = [ - "ASAvailability.h", - "ASBaseDefines.h", - "ASDisplayNode.h", - "ASDisplayNode+Ancestry.h", - "ASDisplayNode+Convenience.h", - "ASDisplayNodeExtras.h", - "ASConfiguration.h", - "ASConfigurationDelegate.h", - "ASConfigurationInternal.h", +public_headers = glob([ + "Source/Public/AsyncDisplayKit/*.h", +]) - "ASControlNode.h", - "ASImageNode.h", - "ASTextNode.h", - "ASTextNode2.h", - "ASEditableTextNode.h", - "ASButtonNode.h", - - "ASImageProtocols.h", - - "ASRangeManagingNode.h", - "ASSectionContext.h", - - "ASElementMap.h", - - "ASSupplementaryNodeSource.h", - - "ASScrollNode.h", - - "ASNodeController+Beta.h", - "ASViewController.h", - "ASNavigationController.h", - "ASTabBarController.h", - "ASRangeControllerUpdateRangeProtocol+Beta.h", - - "ASLayout.h", - "ASDimension.h", - "ASDimensionInternal.h", - "ASLayoutElement.h", - "ASLayoutSpec.h", - "ASBackgroundLayoutSpec.h", - "ASCenterLayoutSpec.h", - "ASCornerLayoutSpec.h", - "ASRelativeLayoutSpec.h", - "ASInsetLayoutSpec.h", - "ASOverlayLayoutSpec.h", - "ASRatioLayoutSpec.h", - "ASAbsoluteLayoutSpec.h", - "ASStackLayoutDefines.h", - "ASStackLayoutSpec.h", - - "_ASAsyncTransaction.h", - "_ASAsyncTransactionGroup.h", - "_ASAsyncTransactionContainer.h", - "ASCollections.h", - "_ASDisplayLayer.h", - "_ASDisplayView.h", - "ASDisplayNode+Beta.h", - "ASTextNode+Beta.h", - "ASTextNodeTypes.h", - "ASBlockTypes.h", - "ASContextTransitioning.h", - "ASControlNode+Subclasses.h", - "ASDisplayNode+Subclasses.h", - "ASEqualityHelpers.h", - "ASEventLog.h", - "ASHashing.h", - "ASHighlightOverlayLayer.h", - "ASImageContainerProtocolCategories.h", - "ASLocking.h", - "ASLog.h", - "ASMainThreadDeallocation.h", - "ASMutableAttributedStringBuilder.h", - "ASRunLoopQueue.h", - "ASTextKitComponents.h", - "ASThread.h", - "ASTraitCollection.h", - "ASVisibilityProtocols.h", - "ASWeakSet.h", - - "CoreGraphics+ASConvenience.h", - "NSMutableAttributedString+TextKitAdditions.h", - "UICollectionViewLayout+ASConvenience.h", - "UIView+ASConvenience.h", - "UIImage+ASConvenience.h", - "ASGraphicsContext.h", - "NSArray+Diffing.h", - "ASObjectDescriptionHelpers.h", - "UIResponder+AsyncDisplayKit.h", - - "AsyncDisplayKit+Debug.h", - - "ASExperimentalFeatures.h", - "ASAssert.h", -] - -public_headers = ["Source/" + name for name in public_header_names] private_headers = glob([ - "Source/*.h", -], exclude = public_headers) - -exported_headers_rule_name = "AsyncDisplayKit_ExportedHeaders" -exported_headers_path = "includedir" -name = "AsyncDisplayKit" - -genrule( - name = exported_headers_rule_name, - srcs = public_headers, - outs = [exported_headers_path + "/" + name + "/" + x.split('/')[::-1][0] for x in public_headers], - cmd = "cp $(SRCS) $(RULEDIR)" + "/" + exported_headers_path + "/" + name, -) -exported_headers = [":" + exported_headers_rule_name] + "Source/Implementation/AsyncDisplayKit/*.h", +]) objc_library( name = "AsyncDisplayKit", @@ -120,12 +15,17 @@ objc_library( "Source/**/*.m", "Source/**/*.mm", ]) + private_headers, - hdrs = exported_headers, + hdrs = public_headers, defines = [ "MINIMAL_ASDK", ], + copts = [ + "-w", + "-isystem", + "submodules/AsyncDisplayKit/Source/Implementation", + ], includes = [ - exported_headers_path, + "Source/Public", ], sdk_frameworks = [ "Foundation", diff --git a/submodules/AsyncDisplayKit/Source/ASAbsoluteLayoutSpec.h b/submodules/AsyncDisplayKit/Source/ASAbsoluteLayoutSpec.h deleted file mode 100644 index 61bfbf8cd5..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASAbsoluteLayoutSpec.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// ASAbsoluteLayoutSpec.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 - -/** How much space the spec will take up. */ -typedef NS_ENUM(NSInteger, ASAbsoluteLayoutSpecSizing) { - /** The spec will take up the maximum size possible. */ - ASAbsoluteLayoutSpecSizingDefault, - /** Computes a size for the spec that is the union of all childrens' frames. */ - ASAbsoluteLayoutSpecSizingSizeToFit, -}; - -NS_ASSUME_NONNULL_BEGIN - -/** - A layout spec that positions children at fixed positions. - */ -@interface ASAbsoluteLayoutSpec : ASLayoutSpec - -/** - How much space will the spec taken up - */ -@property (nonatomic) ASAbsoluteLayoutSpecSizing sizing; - -/** - @param sizing How much space the spec will take up - @param children Children to be positioned at fixed positions - */ -+ (instancetype)absoluteLayoutSpecWithSizing:(ASAbsoluteLayoutSpecSizing)sizing children:(NSArray> *)children NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -/** - @param children Children to be positioned at fixed positions - */ -+ (instancetype)absoluteLayoutSpecWithChildren:(NSArray> *)children NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASAbsoluteLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/ASAbsoluteLayoutSpec.mm deleted file mode 100644 index 781211ddd7..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASAbsoluteLayoutSpec.mm +++ /dev/null @@ -1,104 +0,0 @@ -// -// ASAbsoluteLayoutSpec.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 "ASAbsoluteLayoutSpec.h" - -#import -#import -#import -#import -#import - -#pragma mark - ASAbsoluteLayoutSpec - -@implementation ASAbsoluteLayoutSpec - -#pragma mark - Class - -+ (instancetype)absoluteLayoutSpecWithChildren:(NSArray *)children NS_RETURNS_RETAINED -{ - return [[self alloc] initWithChildren:children]; -} - -+ (instancetype)absoluteLayoutSpecWithSizing:(ASAbsoluteLayoutSpecSizing)sizing children:(NSArray> *)children NS_RETURNS_RETAINED -{ - return [[self alloc] initWithSizing:sizing children:children]; -} - -#pragma mark - Lifecycle - -- (instancetype)init -{ - return [self initWithChildren:nil]; -} - -- (instancetype)initWithChildren:(NSArray *)children -{ - return [self initWithSizing:ASAbsoluteLayoutSpecSizingDefault children:children]; -} - -- (instancetype)initWithSizing:(ASAbsoluteLayoutSpecSizing)sizing children:(NSArray> *)children -{ - if (!(self = [super init])) { - return nil; - } - - _sizing = sizing; - self.children = children; - - return self; -} - -#pragma mark - ASLayoutSpec - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - CGSize size = { - ASPointsValidForSize(constrainedSize.max.width) == NO ? ASLayoutElementParentDimensionUndefined : constrainedSize.max.width, - ASPointsValidForSize(constrainedSize.max.height) == NO ? ASLayoutElementParentDimensionUndefined : constrainedSize.max.height - }; - - NSArray *children = self.children; - ASLayout *rawSublayouts[children.count]; - int i = 0; - - for (id child in children) { - CGPoint layoutPosition = child.style.layoutPosition; - CGSize autoMaxSize = { - constrainedSize.max.width - layoutPosition.x, - constrainedSize.max.height - layoutPosition.y - }; - - const ASSizeRange childConstraint = ASLayoutElementSizeResolveAutoSize(child.style.size, size, {{0,0}, autoMaxSize}); - - ASLayout *sublayout = [child layoutThatFits:childConstraint parentSize:size]; - sublayout.position = layoutPosition; - rawSublayouts[i++] = sublayout; - } - const auto sublayouts = [NSArray arrayByTransferring:rawSublayouts count:i]; - - if (_sizing == ASAbsoluteLayoutSpecSizingSizeToFit || isnan(size.width)) { - size.width = constrainedSize.min.width; - for (ASLayout *sublayout in sublayouts) { - size.width = MAX(size.width, sublayout.position.x + sublayout.size.width); - } - } - - if (_sizing == ASAbsoluteLayoutSpecSizingSizeToFit || isnan(size.height)) { - size.height = constrainedSize.min.height; - for (ASLayout *sublayout in sublayouts) { - size.height = MAX(size.height, sublayout.position.y + sublayout.size.height); - } - } - - return [ASLayout layoutWithLayoutElement:self size:ASSizeRangeClamp(constrainedSize, size) sublayouts:sublayouts]; -} - -@end - diff --git a/submodules/AsyncDisplayKit/Source/ASAsciiArtBoxCreator.h b/submodules/AsyncDisplayKit/Source/ASAsciiArtBoxCreator.h deleted file mode 100644 index 83c7bec414..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASAsciiArtBoxCreator.h +++ /dev/null @@ -1,60 +0,0 @@ -// -// ASAsciiArtBoxCreator.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 - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASLayoutElementAsciiArtProtocol -/** - * Returns an ascii-art representation of this object and its children. - * For example, an ASInsetSpec may return something like this: - * - * --ASInsetLayoutSpec-- - * | ASTextNode | - * --------------------- - */ -- (NSString *)asciiArtString; - -/** - * returns the name of this object that will display in the ascii art. Usually this can - * simply be NSStringFromClass([self class]). - */ -- (NSString *)asciiArtName; - -@end - -/** - * A that takes a parent and its children and renders as ascii art box. - */ -@interface ASAsciiArtBoxCreator : NSObject - -/** - * Renders an ascii art box with the children aligned horizontally - * Example: - * ------------ASStackLayoutSpec----------- - * | ASTextNode ASTextNode ASTextNode | - * ---------------------------------------- - */ -+ (NSString *)horizontalBoxStringForChildren:(NSArray *)children parent:(NSString *)parent; - -/** - * Renders an ascii art box with the children aligned vertically. - * Example: - * --ASStackLayoutSpec-- - * | ASTextNode | - * | ASTextNode | - * | ASTextNode | - * --------------------- - */ -+ (NSString *)verticalBoxStringForChildren:(NSArray *)children parent:(NSString *)parent; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASAsciiArtBoxCreator.mm b/submodules/AsyncDisplayKit/Source/ASAsciiArtBoxCreator.mm deleted file mode 100644 index 78eb572ead..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASAsciiArtBoxCreator.mm +++ /dev/null @@ -1,186 +0,0 @@ -// -// ASAsciiArtBoxCreator.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 - -#import -#import - -static const NSUInteger kDebugBoxPadding = 2; - -typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation) -{ - PIDebugBoxPaddingLocationFront, - PIDebugBoxPaddingLocationEnd, - PIDebugBoxPaddingLocationBoth -}; - -@interface NSString(PIDebugBox) - -@end - -@implementation NSString(PIDebugBox) - -+ (instancetype)debugbox_stringWithString:(NSString *)stringToRepeat repeatedCount:(NSUInteger)repeatCount NS_RETURNS_RETAINED -{ - NSMutableString *string = [[NSMutableString alloc] initWithCapacity:[stringToRepeat length] * repeatCount]; - for (NSUInteger index = 0; index < repeatCount; index++) { - [string appendString:stringToRepeat]; - } - return [string copy]; -} - -- (NSString *)debugbox_stringByAddingPadding:(NSString *)padding count:(NSUInteger)count location:(PIDebugBoxPaddingLocation)location -{ - NSString *paddingString = [NSString debugbox_stringWithString:padding repeatedCount:count]; - switch (location) { - case PIDebugBoxPaddingLocationFront: - return [NSString stringWithFormat:@"%@%@", paddingString, self]; - case PIDebugBoxPaddingLocationEnd: - return [NSString stringWithFormat:@"%@%@", self, paddingString]; - case PIDebugBoxPaddingLocationBoth: - return [NSString stringWithFormat:@"%@%@%@", paddingString, self, paddingString]; - } - return [self copy]; -} - -@end - -@implementation ASAsciiArtBoxCreator - -+ (NSString *)horizontalBoxStringForChildren:(NSArray *)children parent:(NSString *)parent -{ - if ([children count] == 0) { - return parent; - } - - NSMutableArray *childrenLines = [NSMutableArray array]; - - // split the children into lines - NSUInteger lineCountPerChild = 0; - for (NSString *child in children) { - NSArray *lines = [child componentsSeparatedByString:@"\n"]; - lineCountPerChild = MAX(lineCountPerChild, [lines count]); - } - - for (NSString *child in children) { - NSMutableArray *lines = [[child componentsSeparatedByString:@"\n"] mutableCopy]; - NSUInteger topPadding = ceil((CGFloat)(lineCountPerChild - [lines count])/2.0); - NSUInteger bottomPadding = (lineCountPerChild - [lines count])/2.0; - NSUInteger lineLength = [lines[0] length]; - - for (NSUInteger index = 0; index < topPadding; index++) { - [lines insertObject:[NSString debugbox_stringWithString:@" " repeatedCount:lineLength] atIndex:0]; - } - for (NSUInteger index = 0; index < bottomPadding; index++) { - [lines addObject:[NSString debugbox_stringWithString:@" " repeatedCount:lineLength]]; - } - [childrenLines addObject:lines]; - } - - NSMutableArray *concatenatedLines = [NSMutableArray array]; - NSString *padding = [NSString debugbox_stringWithString:@" " repeatedCount:kDebugBoxPadding]; - for (NSUInteger index = 0; index < lineCountPerChild; index++) { - NSMutableString *line = [[NSMutableString alloc] init]; - [line appendFormat:@"|%@",padding]; - for (NSArray *childLines in childrenLines) { - [line appendFormat:@"%@%@", childLines[index], padding]; - } - [line appendString:@"|"]; - [concatenatedLines addObject:line]; - } - - // surround the lines in a box - NSUInteger totalLineLength = [concatenatedLines[0] length]; - if (totalLineLength < [parent length]) { - NSUInteger difference = [parent length] + (2 * kDebugBoxPadding) - totalLineLength; - NSUInteger leftPadding = ceil((CGFloat)difference/2.0); - NSUInteger rightPadding = difference/2; - - NSString *leftString = [@"|" debugbox_stringByAddingPadding:@" " count:leftPadding location:PIDebugBoxPaddingLocationEnd]; - NSString *rightString = [@"|" debugbox_stringByAddingPadding:@" " count:rightPadding location:PIDebugBoxPaddingLocationFront]; - - NSMutableArray *paddedLines = [NSMutableArray array]; - for (NSString *line in concatenatedLines) { - NSString *paddedLine = [line stringByReplacingOccurrencesOfString:@"|" withString:leftString options:NSCaseInsensitiveSearch range:NSMakeRange(0, 1)]; - paddedLine = [paddedLine stringByReplacingOccurrencesOfString:@"|" withString:rightString options:NSCaseInsensitiveSearch range:NSMakeRange([paddedLine length] - 1, 1)]; - [paddedLines addObject:paddedLine]; - } - concatenatedLines = paddedLines; - // totalLineLength += difference; - } - concatenatedLines = [self appendTopAndBottomToBoxString:concatenatedLines parent:parent]; - return [concatenatedLines componentsJoinedByString:@"\n"]; - -} - -+ (NSString *)verticalBoxStringForChildren:(NSArray *)children parent:(NSString *)parent -{ - if ([children count] == 0) { - return parent; - } - - NSMutableArray *childrenLines = [NSMutableArray array]; - - NSUInteger maxChildLength = 0; - for (NSString *child in children) { - NSArray *lines = [child componentsSeparatedByString:@"\n"]; - maxChildLength = MAX(maxChildLength, [lines[0] length]); - } - - NSUInteger rightPadding = 0; - NSUInteger leftPadding = 0; - - if (maxChildLength < [parent length]) { - NSUInteger difference = [parent length] + (2 * kDebugBoxPadding) - maxChildLength; - leftPadding = ceil((CGFloat)difference/2.0); - rightPadding = difference/2; - } - - NSString *rightPaddingString = [NSString debugbox_stringWithString:@" " repeatedCount:rightPadding + kDebugBoxPadding]; - NSString *leftPaddingString = [NSString debugbox_stringWithString:@" " repeatedCount:leftPadding + kDebugBoxPadding]; - - for (NSString *child in children) { - NSMutableArray *lines = [[child componentsSeparatedByString:@"\n"] mutableCopy]; - - NSUInteger leftLinePadding = ceil((CGFloat)(maxChildLength - [lines[0] length])/2.0); - NSUInteger rightLinePadding = (maxChildLength - [lines[0] length])/2.0; - - for (NSString *line in lines) { - NSString *rightLinePaddingString = [NSString debugbox_stringWithString:@" " repeatedCount:rightLinePadding]; - rightLinePaddingString = [NSString stringWithFormat:@"%@%@|", rightLinePaddingString, rightPaddingString]; - - NSString *leftLinePaddingString = [NSString debugbox_stringWithString:@" " repeatedCount:leftLinePadding]; - leftLinePaddingString = [NSString stringWithFormat:@"|%@%@", leftLinePaddingString, leftPaddingString]; - - NSString *paddingLine = [NSString stringWithFormat:@"%@%@%@", leftLinePaddingString, line, rightLinePaddingString]; - [childrenLines addObject:paddingLine]; - } - } - - childrenLines = [self appendTopAndBottomToBoxString:childrenLines parent:parent]; - return [childrenLines componentsJoinedByString:@"\n"]; -} - -+ (NSMutableArray *)appendTopAndBottomToBoxString:(NSMutableArray *)boxStrings parent:(NSString *)parent -{ - NSUInteger totalLineLength = [boxStrings[0] length]; - [boxStrings addObject:[NSString debugbox_stringWithString:@"-" repeatedCount:totalLineLength]]; - - NSUInteger leftPadding = ceil(((CGFloat)(totalLineLength - [parent length]))/2.0); - NSUInteger rightPadding = (totalLineLength - [parent length])/2; - - NSString *topLine = [parent debugbox_stringByAddingPadding:@"-" count:leftPadding location:PIDebugBoxPaddingLocationFront]; - topLine = [topLine debugbox_stringByAddingPadding:@"-" count:rightPadding location:PIDebugBoxPaddingLocationEnd]; - [boxStrings insertObject:topLine atIndex:0]; - - return boxStrings; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASAssert.m.orig b/submodules/AsyncDisplayKit/Source/ASAssert.m.orig deleted file mode 100644 index 45a3452180..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASAssert.m.orig +++ /dev/null @@ -1,72 +0,0 @@ -// -// ASAssert.m -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -<<<<<<< HEAD -#ifndef MINIMAL_ASDK -static _Thread_local int tls_mainThreadAssertionsDisabledCount; -#endif -======= -#if AS_TLS_AVAILABLE ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e - -static _Thread_local int tls_mainThreadAssertionsDisabledCount; -BOOL ASMainThreadAssertionsAreDisabled() { -#ifdef MINIMAL_ASDK - return false; -#else - return tls_mainThreadAssertionsDisabledCount > 0; -#endif -} - -void ASPushMainThreadAssertionsDisabled() { -#ifndef MINIMAL_ASDK - tls_mainThreadAssertionsDisabledCount += 1; -#endif -} - -void ASPopMainThreadAssertionsDisabled() { -#ifndef MINIMAL_ASDK - tls_mainThreadAssertionsDisabledCount -= 1; - ASDisplayNodeCAssert(tls_mainThreadAssertionsDisabledCount >= 0, @"Attempt to pop thread assertion-disabling without corresponding push."); -#endif -} - -#else - -#import - -static pthread_key_t ASMainThreadAssertionsDisabledKey() { - static pthread_key_t k; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - pthread_key_create(&k, NULL); - }); - return k; -} - -BOOL ASMainThreadAssertionsAreDisabled() { - return (pthread_getspecific(ASMainThreadAssertionsDisabledKey()) > 0); -} - -void ASPushMainThreadAssertionsDisabled() { - let key = ASMainThreadAssertionsDisabledKey(); - let oldVal = pthread_getspecific(key); - pthread_setspecific(key, oldVal + 1); -} - -void ASPopMainThreadAssertionsDisabled() { - let key = ASMainThreadAssertionsDisabledKey(); - let oldVal = pthread_getspecific(key); - pthread_setspecific(key, oldVal - 1); - ASDisplayNodeCAssert(oldVal > 0, @"Attempt to pop thread assertion-disabling without corresponding push."); -} - -#endif // AS_TLS_AVAILABLE diff --git a/submodules/AsyncDisplayKit/Source/ASBackgroundLayoutSpec.h b/submodules/AsyncDisplayKit/Source/ASBackgroundLayoutSpec.h deleted file mode 100644 index 032a240bbd..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASBackgroundLayoutSpec.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// ASBackgroundLayoutSpec.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 - -NS_ASSUME_NONNULL_BEGIN - -/** - Lays out a single layoutElement child, then lays out a background layoutElement instance behind it stretched to its size. - */ -@interface ASBackgroundLayoutSpec : ASLayoutSpec - -/** - * Background layoutElement for this layout spec - */ -@property (nonatomic) id background; - -/** - * Creates and returns an ASBackgroundLayoutSpec object - * - * @param child A child that is laid out to determine the size of this spec. - * @param background A layoutElement object that is laid out behind the child. - */ -+ (instancetype)backgroundLayoutSpecWithChild:(id)child background:(id)background NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASBackgroundLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/ASBackgroundLayoutSpec.mm deleted file mode 100644 index 31dd75487a..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASBackgroundLayoutSpec.mm +++ /dev/null @@ -1,92 +0,0 @@ -// -// ASBackgroundLayoutSpec.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 - -#import - -#import -#import - -static NSUInteger const kForegroundChildIndex = 0; -static NSUInteger const kBackgroundChildIndex = 1; - -@implementation ASBackgroundLayoutSpec - -#pragma mark - Class - -+ (instancetype)backgroundLayoutSpecWithChild:(id)child background:(id)background NS_RETURNS_RETAINED -{ - return [[self alloc] initWithChild:child background:background]; -} - -#pragma mark - Lifecycle - -- (instancetype)initWithChild:(id)child background:(id)background -{ - if (!(self = [super init])) { - return nil; - } - self.child = child; - self.background = background; - return self; -} - -#pragma mark - ASLayoutSpec - -/** - * First layout the contents, then fit the background image. - */ -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize - restrictedToSize:(ASLayoutElementSize)size - relativeToParentSize:(CGSize)parentSize -{ - ASLayout *contentsLayout = [self.child layoutThatFits:constrainedSize parentSize:parentSize]; - - ASLayout *rawSublayouts[2]; - int i = 0; - if (self.background) { - // Size background to exactly the same size. - ASLayout *backgroundLayout = [self.background layoutThatFits:ASSizeRangeMake(contentsLayout.size) - parentSize:parentSize]; - backgroundLayout.position = CGPointZero; - rawSublayouts[i++] = backgroundLayout; - } - contentsLayout.position = CGPointZero; - rawSublayouts[i++] = contentsLayout; - - const auto sublayouts = [NSArray arrayByTransferring:rawSublayouts count:i]; - return [ASLayout layoutWithLayoutElement:self size:contentsLayout.size sublayouts:sublayouts]; -} - -#pragma mark - Background - -- (void)setChild:(id)child -{ - ASDisplayNodeAssertNotNil(child, @"Child cannot be nil"); - [super setChild:child atIndex:kForegroundChildIndex]; -} - -- (id)child -{ - return [super childAtIndex:kForegroundChildIndex]; -} - -- (void)setBackground:(id)background -{ - ASDisplayNodeAssertNotNil(background, @"Background cannot be nil"); - [super setChild:background atIndex:kBackgroundChildIndex]; -} - -- (id)background -{ - return [super childAtIndex:kBackgroundChildIndex]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASBasicImageDownloader.h b/submodules/AsyncDisplayKit/Source/ASBasicImageDownloader.h deleted file mode 100644 index d5f8169745..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASBasicImageDownloader.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// ASBasicImageDownloader.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 - -NS_ASSUME_NONNULL_BEGIN - -/** - * @abstract Simple NSURLSession-based image downloader. - */ -@interface ASBasicImageDownloader : NSObject - -/** - * A shared image downloader which can be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes. - * The userInfo provided by this downloader is `nil`. - * - * This is a very basic image downloader. It does not support caching, progressive downloading and likely - * isn't something you should use in production. If you'd like something production ready, see @c ASPINRemoteImageDownloader - * - * @note It is strongly recommended you include PINRemoteImage and use @c ASPINRemoteImageDownloader instead. - */ -@property (class, readonly) ASBasicImageDownloader *sharedImageDownloader; -+ (ASBasicImageDownloader *)sharedImageDownloader NS_RETURNS_RETAINED; - -+ (instancetype)new __attribute__((unavailable("+[ASBasicImageDownloader sharedImageDownloader] must be used."))); -- (instancetype)init __attribute__((unavailable("+[ASBasicImageDownloader sharedImageDownloader] must be used."))); - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASBasicImageDownloader.mm b/submodules/AsyncDisplayKit/Source/ASBasicImageDownloader.mm deleted file mode 100644 index 17c035a41c..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASBasicImageDownloader.mm +++ /dev/null @@ -1,355 +0,0 @@ -// -// ASBasicImageDownloader.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 - -#import -#import - -#import -#import -#import - -using AS::MutexLocker; - -#pragma mark - -/** - * Collection of properties associated with a download request. - */ - -NSString * const kASBasicImageDownloaderContextCallbackQueue = @"kASBasicImageDownloaderContextCallbackQueue"; -NSString * const kASBasicImageDownloaderContextProgressBlock = @"kASBasicImageDownloaderContextProgressBlock"; -NSString * const kASBasicImageDownloaderContextCompletionBlock = @"kASBasicImageDownloaderContextCompletionBlock"; - -static inline float NSURLSessionTaskPriorityWithImageDownloaderPriority(ASImageDownloaderPriority priority) { - switch (priority) { - case ASImageDownloaderPriorityPreload: - return NSURLSessionTaskPriorityLow; - - case ASImageDownloaderPriorityImminent: - return NSURLSessionTaskPriorityDefault; - - case ASImageDownloaderPriorityVisible: - return NSURLSessionTaskPriorityHigh; - } -} - -@interface ASBasicImageDownloaderContext () -{ - BOOL _invalid; - AS::RecursiveMutex __instanceLock__; -} - -@property (nonatomic) NSMutableArray *callbackDatas; - -@end - -@implementation ASBasicImageDownloaderContext - -static NSMutableDictionary *currentRequests = nil; - -+ (AS::Mutex *)currentRequestLock -{ - static dispatch_once_t onceToken; - static AS::Mutex *currentRequestsLock; - dispatch_once(&onceToken, ^{ - currentRequestsLock = new AS::Mutex(); - }); - return currentRequestsLock; -} - -+ (ASBasicImageDownloaderContext *)contextForURL:(NSURL *)URL -{ - MutexLocker l(*self.currentRequestLock); - if (!currentRequests) { - currentRequests = [[NSMutableDictionary alloc] init]; - } - ASBasicImageDownloaderContext *context = currentRequests[URL]; - if (!context) { - context = [[ASBasicImageDownloaderContext alloc] initWithURL:URL]; - currentRequests[URL] = context; - } - return context; -} - -+ (void)cancelContextWithURL:(NSURL *)URL -{ - MutexLocker l(*self.currentRequestLock); - if (currentRequests) { - [currentRequests removeObjectForKey:URL]; - } -} - -- (instancetype)initWithURL:(NSURL *)URL -{ - if (self = [super init]) { - _URL = URL; - _callbackDatas = [NSMutableArray array]; - } - return self; -} - -- (void)cancel -{ - MutexLocker l(__instanceLock__); - - NSURLSessionTask *sessionTask = self.sessionTask; - if (sessionTask) { - [sessionTask cancel]; - self.sessionTask = nil; - } - - _invalid = YES; - [self.class cancelContextWithURL:self.URL]; -} - -- (BOOL)isCancelled -{ - MutexLocker l(__instanceLock__); - return _invalid; -} - -- (void)addCallbackData:(NSDictionary *)callbackData -{ - MutexLocker l(__instanceLock__); - [self.callbackDatas addObject:callbackData]; -} - -- (void)performProgressBlocks:(CGFloat)progress -{ - MutexLocker l(__instanceLock__); - for (NSDictionary *callbackData in self.callbackDatas) { - ASImageDownloaderProgress progressBlock = callbackData[kASBasicImageDownloaderContextProgressBlock]; - dispatch_queue_t callbackQueue = callbackData[kASBasicImageDownloaderContextCallbackQueue]; - - if (progressBlock) { - dispatch_async(callbackQueue, ^{ - progressBlock(progress); - }); - } - } -} - -- (void)completeWithImage:(UIImage *)image error:(NSError *)error -{ - MutexLocker l(__instanceLock__); - for (NSDictionary *callbackData in self.callbackDatas) { - ASImageDownloaderCompletion completionBlock = callbackData[kASBasicImageDownloaderContextCompletionBlock]; - dispatch_queue_t callbackQueue = callbackData[kASBasicImageDownloaderContextCallbackQueue]; - - if (completionBlock) { - dispatch_async(callbackQueue, ^{ - completionBlock(image, error, nil, nil); - }); - } - } - - self.sessionTask = nil; - [self.callbackDatas removeAllObjects]; -} - -- (NSURLSessionTask *)createSessionTaskIfNecessaryWithBlock:(NSURLSessionTask *(^)())creationBlock { - { - MutexLocker l(__instanceLock__); - - if (self.isCancelled) { - return nil; - } - - if (self.sessionTask && (self.sessionTask.state == NSURLSessionTaskStateRunning)) { - return nil; - } - } - - NSURLSessionTask *newTask = creationBlock(); - - { - MutexLocker l(__instanceLock__); - - if (self.isCancelled) { - return nil; - } - - if (self.sessionTask && (self.sessionTask.state == NSURLSessionTaskStateRunning)) { - return nil; - } - - self.sessionTask = newTask; - - return self.sessionTask; - } -} - -@end - - -#pragma mark - -/** - * NSURLSessionDownloadTask lacks a `userInfo` property, so add this association ourselves. - */ -@interface NSURLRequest (ASBasicImageDownloader) -@property (nonatomic) ASBasicImageDownloaderContext *asyncdisplaykit_context; -@end - -@implementation NSURLRequest (ASBasicImageDownloader) - -static const void *ContextKey() { - return @selector(asyncdisplaykit_context); -} - -- (void)setAsyncdisplaykit_context:(ASBasicImageDownloaderContext *)asyncdisplaykit_context -{ - objc_setAssociatedObject(self, ContextKey(), asyncdisplaykit_context, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} -- (ASBasicImageDownloader *)asyncdisplaykit_context -{ - return objc_getAssociatedObject(self, ContextKey()); -} -@end - - -#pragma mark - -@interface ASBasicImageDownloader () -{ - NSOperationQueue *_sessionDelegateQueue; - NSURLSession *_session; -} - -@end - -@implementation ASBasicImageDownloader - -+ (ASBasicImageDownloader *)sharedImageDownloader -{ - static ASBasicImageDownloader *sharedImageDownloader = nil; - static dispatch_once_t once = 0; - dispatch_once(&once, ^{ - sharedImageDownloader = [[ASBasicImageDownloader alloc] _init]; - }); - return sharedImageDownloader; -} - -#pragma mark Lifecycle. - -- (instancetype)_init -{ - if (!(self = [super init])) - return nil; - - _sessionDelegateQueue = [[NSOperationQueue alloc] init]; - _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] - delegate:self - delegateQueue:_sessionDelegateQueue]; - - return self; -} - - -#pragma mark ASImageDownloaderProtocol. - -- (nullable id)downloadImageWithURL:(NSURL *)URL - callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress - completion:(ASImageDownloaderCompletion)completion -{ - return [self downloadImageWithURL:URL - priority:ASImageDownloaderPriorityImminent // maps to default priority - callbackQueue:callbackQueue - downloadProgress:downloadProgress - completion:completion]; -} - -- (nullable id)downloadImageWithURL:(NSURL *)URL - priority:(ASImageDownloaderPriority)priority - callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(ASImageDownloaderProgress)downloadProgress - completion:(ASImageDownloaderCompletion)completion -{ - ASBasicImageDownloaderContext *context = [ASBasicImageDownloaderContext contextForURL:URL]; - - // NSURLSessionDownloadTask will do file I/O to create a temp directory. If called on the main thread this will - // cause significant performance issues. - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - // associate metadata with it - const auto callbackData = [[NSMutableDictionary alloc] init]; - callbackData[kASBasicImageDownloaderContextCallbackQueue] = callbackQueue ? : dispatch_get_main_queue(); - - if (downloadProgress) { - callbackData[kASBasicImageDownloaderContextProgressBlock] = [downloadProgress copy]; - } - - if (completion) { - callbackData[kASBasicImageDownloaderContextCompletionBlock] = [completion copy]; - } - - [context addCallbackData:[[NSDictionary alloc] initWithDictionary:callbackData]]; - - // Create new task if necessary - NSURLSessionDownloadTask *task = (NSURLSessionDownloadTask *)[context createSessionTaskIfNecessaryWithBlock:^(){return [_session downloadTaskWithURL:URL];}]; - - if (task) { - task.priority = NSURLSessionTaskPriorityWithImageDownloaderPriority(priority); - task.originalRequest.asyncdisplaykit_context = context; - - // start downloading - [task resume]; - } - }); - - return context; -} - -- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier -{ - ASDisplayNodeAssert([downloadIdentifier isKindOfClass:ASBasicImageDownloaderContext.class], @"unexpected downloadIdentifier"); - ASBasicImageDownloaderContext *context = (ASBasicImageDownloaderContext *)downloadIdentifier; - - [context cancel]; -} - - -#pragma mark NSURLSessionDownloadDelegate. - -- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask - didWriteData:(int64_t)bytesWritten - totalBytesWritten:(int64_t)totalBytesWritten - totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite -{ - ASBasicImageDownloaderContext *context = downloadTask.originalRequest.asyncdisplaykit_context; - [context performProgressBlocks:(CGFloat)totalBytesWritten / (CGFloat)totalBytesExpectedToWrite]; -} - -// invoked if the download succeeded with no error -- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask - didFinishDownloadingToURL:(NSURL *)location -{ - ASBasicImageDownloaderContext *context = downloadTask.originalRequest.asyncdisplaykit_context; - if ([context isCancelled]) { - return; - } - - if (context) { - UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]]; - [context completeWithImage:image error:nil]; - } -} - -// invoked unconditionally -- (void)URLSession:(NSURLSession *)session task:(NSURLSessionDownloadTask *)task - didCompleteWithError:(NSError *)error -{ - ASBasicImageDownloaderContext *context = task.originalRequest.asyncdisplaykit_context; - if (context && error) { - [context completeWithImage:nil error:error]; - } -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASBasicImageDownloaderInternal.h b/submodules/AsyncDisplayKit/Source/ASBasicImageDownloaderInternal.h deleted file mode 100644 index ba06e0bc81..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASBasicImageDownloaderInternal.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// ASBasicImageDownloaderInternal.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 -// - -@interface ASBasicImageDownloaderContext : NSObject - -+ (ASBasicImageDownloaderContext *)contextForURL:(NSURL *)URL; - -@property (nonatomic, readonly) NSURL *URL; -@property (nonatomic, weak) NSURLSessionTask *sessionTask; - -- (BOOL)isCancelled; -- (void)cancel; - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASBatchContext.h b/submodules/AsyncDisplayKit/Source/ASBatchContext.h deleted file mode 100644 index bbe92b8c34..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASBatchContext.h +++ /dev/null @@ -1,69 +0,0 @@ -// -// ASBatchContext.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 - -NS_ASSUME_NONNULL_BEGIN - -/** - * @abstract A context object to notify when batch fetches are finished or cancelled. - */ -@interface ASBatchContext : NSObject - -/** - * Retrieve the state of the current batch process. - * - * @return A boolean reflecting if the owner of the context object is fetching another batch. - */ -- (BOOL)isFetching; - -/** - * Let the context object know that a batch fetch was completed. - * - * @param didComplete A boolean that states whether or not the batch fetch completed. - * - * @discussion Only by passing YES will the owner of the context know to attempt another batch update when necessary. - * For instance, when a table has reached the end of its data, a batch fetch will be attempted unless the context - * object thinks that it is still fetching. - */ -- (void)completeBatchFetching:(BOOL)didComplete; - -/** - * Ask the context object if the batch fetching process was cancelled by the context owner. - * - * @discussion If an error occurs in the context owner, the batch fetching may become out of sync and need to be - * cancelled. For best practices, pass the return value of -batchWasCancelled to -completeBatchFetch:. - * - * @return A boolean reflecting if the context object owner had to cancel the batch process. - */ -- (BOOL)batchFetchingWasCancelled; - -/** - * Notify the context object that something has interrupted the batch fetching process. - * - * @discussion Call this method only when something has corrupted the batch fetching process. Calling this method should - * be left to the owner of the batch process unless there is a specific purpose. - */ -- (void)cancelBatchFetching; - -/** - * Notify the context object that fetching has started. - * - * @discussion Call this method only when you are beginning a fetch process. This should really only be called by the - * context object's owner. Calling this method should be paired with -completeBatchFetching:. - */ -- (void)beginBatchFetching; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASBatchContext.mm b/submodules/AsyncDisplayKit/Source/ASBatchContext.mm deleted file mode 100644 index ee4ec173a5..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASBatchContext.mm +++ /dev/null @@ -1,64 +0,0 @@ -// -// ASBatchContext.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 - -#import -#import - -typedef NS_ENUM(NSInteger, ASBatchContextState) { - ASBatchContextStateFetching, - ASBatchContextStateCancelled, - ASBatchContextStateCompleted -}; - -@implementation ASBatchContext { - atomic_int _state; -} - -- (instancetype)init -{ - if (self = [super init]) { - _state = ATOMIC_VAR_INIT(ASBatchContextStateCompleted); - } - return self; -} - -- (BOOL)isFetching -{ - return atomic_load(&_state) == ASBatchContextStateFetching; -} - -- (BOOL)batchFetchingWasCancelled -{ - return atomic_load(&_state) == ASBatchContextStateCancelled; -} - -- (void)beginBatchFetching -{ - atomic_store(&_state, ASBatchContextStateFetching); -} - -- (void)completeBatchFetching:(BOOL)didComplete -{ - if (didComplete) { - as_log_debug(ASCollectionLog(), "Completed batch fetch with context %@", self); - atomic_store(&_state, ASBatchContextStateCompleted); - } -} - -- (void)cancelBatchFetching -{ - atomic_store(&_state, ASBatchContextStateCancelled); -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASBatchFetching.h b/submodules/AsyncDisplayKit/Source/ASBatchFetching.h deleted file mode 100644 index 0358bf12cc..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASBatchFetching.h +++ /dev/null @@ -1,77 +0,0 @@ -// -// ASBatchFetching.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 - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASBatchContext; -@protocol ASBatchFetchingDelegate; - -@protocol ASBatchFetchingScrollView - -- (BOOL)canBatchFetch; -- (ASBatchContext *)batchContext; -- (CGFloat)leadingScreensForBatching; -- (nullable id)batchFetchingDelegate; - -@end - -/** - @abstract Determine if batch fetching should begin based on the state of the parameters. - @discussion This method is broken into a category for unit testing purposes and should be used with the ASTableView and - * ASCollectionView batch fetching API. - @param scrollView The scroll view that in-flight fetches are happening. - @param scrollDirection The current scrolling direction of the scroll view. - @param scrollableDirections The possible scrolling directions of the scroll view. - @param contentOffset The offset that the scrollview will scroll to. - @param velocity The velocity of the scroll view (in points) at the moment the touch was released. - @return Whether or not the current state should proceed with batch fetching. - */ -AS_EXTERN BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView *scrollView, - ASScrollDirection scrollDirection, - ASScrollDirection scrollableDirections, - CGPoint contentOffset, - CGPoint velocity); - - -/** - @abstract Determine if batch fetching should begin based on the state of the parameters. - @param context The batch fetching context that contains knowledge about in-flight fetches. - @param scrollDirection The current scrolling direction of the scroll view. - @param scrollableDirections The possible scrolling directions of the scroll view. - @param bounds The bounds of the scrollview. - @param contentSize The content size of the scrollview. - @param targetOffset The offset that the scrollview will scroll to. - @param leadingScreens How many screens in the remaining distance will trigger batch fetching. - @param visible Whether the view is visible or not. - @param velocity The velocity of the scroll view (in points) at the moment the touch was released. - @param delegate The delegate to be consulted if needed. - @return Whether or not the current state should proceed with batch fetching. - @discussion This method is broken into a category for unit testing purposes and should be used with the ASTableView and - * ASCollectionView batch fetching API. - */ -AS_EXTERN BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, - ASScrollDirection scrollDirection, - ASScrollDirection scrollableDirections, - CGRect bounds, - CGSize contentSize, - CGPoint targetOffset, - CGFloat leadingScreens, - BOOL visible, - CGPoint velocity, - _Nullable id delegate); - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASBatchFetching.mm b/submodules/AsyncDisplayKit/Source/ASBatchFetching.mm deleted file mode 100644 index 44b31ed77d..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASBatchFetching.mm +++ /dev/null @@ -1,102 +0,0 @@ -// -// ASBatchFetching.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 -#import -#import - -BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView *scrollView, - ASScrollDirection scrollDirection, - ASScrollDirection scrollableDirections, - CGPoint contentOffset, - CGPoint velocity) -{ - // Don't fetch if the scroll view does not allow - if (![scrollView canBatchFetch]) { - return NO; - } - - // Check if we should batch fetch - ASBatchContext *context = scrollView.batchContext; - CGRect bounds = scrollView.bounds; - CGSize contentSize = scrollView.contentSize; - CGFloat leadingScreens = scrollView.leadingScreensForBatching; - id delegate = scrollView.batchFetchingDelegate; - BOOL visible = (scrollView.window != nil); - return ASDisplayShouldFetchBatchForContext(context, scrollDirection, scrollableDirections, bounds, contentSize, contentOffset, leadingScreens, visible, velocity, delegate); -} - -BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, - ASScrollDirection scrollDirection, - ASScrollDirection scrollableDirections, - CGRect bounds, - CGSize contentSize, - CGPoint targetOffset, - CGFloat leadingScreens, - BOOL visible, - CGPoint velocity, - id delegate) -{ - // Do not allow fetching if a batch is already in-flight and hasn't been completed or cancelled - if ([context isFetching]) { - return NO; - } - - // No fetching for null states - if (leadingScreens <= 0.0 || CGRectIsEmpty(bounds)) { - return NO; - } - - - CGFloat viewLength, offset, contentLength, velocityLength; - if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { - viewLength = bounds.size.height; - offset = targetOffset.y; - contentLength = contentSize.height; - velocityLength = velocity.y; - } else { // horizontal / right - viewLength = bounds.size.width; - offset = targetOffset.x; - contentLength = contentSize.width; - velocityLength = velocity.x; - } - - BOOL hasSmallContent = contentLength < viewLength; - if (hasSmallContent) { - return YES; - } - - // If we are not visible, but we do have enough content to fill visible area, - // don't batch fetch. - if (visible == NO) { - return NO; - } - - // If they are scrolling toward the head of content, don't batch fetch. - BOOL isScrollingTowardHead = (ASScrollDirectionContainsUp(scrollDirection) || ASScrollDirectionContainsLeft(scrollDirection)); - if (isScrollingTowardHead) { - return NO; - } - - CGFloat triggerDistance = viewLength * leadingScreens; - CGFloat remainingDistance = contentLength - viewLength - offset; - BOOL result = remainingDistance <= triggerDistance; - - if (delegate != nil && velocityLength > 0.0) { - // Don't need to get absolute value of remaining time - // because both remainingDistance and velocityLength are positive when scrolling toward tail - NSTimeInterval remainingTime = remainingDistance / (velocityLength * 1000); - result = [delegate shouldFetchBatchWithRemainingTime:remainingTime hint:result]; - } - - return result; -} - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASBatchFetchingDelegate.h b/submodules/AsyncDisplayKit/Source/ASBatchFetchingDelegate.h deleted file mode 100644 index 3b35115026..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASBatchFetchingDelegate.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// ASBatchFetchingDelegate.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@protocol ASBatchFetchingDelegate - -/** - * @abstract Determine if batch fetching should begin based on the remaining time. - * If the delegate doesn't have enough information to confidently decide, it can take the given hint. - * - * @param remainingTime The amount of time left for user to reach the end of the scroll view's content. - * - * @param hint A hint for the delegate to fallback to. - */ -- (BOOL)shouldFetchBatchWithRemainingTime:(NSTimeInterval)remainingTime hint:(BOOL)hint; - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASButtonNode+Private.h b/submodules/AsyncDisplayKit/Source/ASButtonNode+Private.h deleted file mode 100644 index 2ecc894463..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASButtonNode+Private.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// 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 -#import -#import -#import - -@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 diff --git a/submodules/AsyncDisplayKit/Source/ASButtonNode+Yoga.h b/submodules/AsyncDisplayKit/Source/ASButtonNode+Yoga.h deleted file mode 100644 index 4fddc9df3a..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASButtonNode+Yoga.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// 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 -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ASButtonNode (Yoga) - -- (void)updateYogaLayoutIfNeeded; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASButtonNode+Yoga.mm b/submodules/AsyncDisplayKit/Source/ASButtonNode+Yoga.mm deleted file mode 100644 index c6ed69ab26..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASButtonNode+Yoga.mm +++ /dev/null @@ -1,109 +0,0 @@ -// -// ASButtonNode+Yoga.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#if YOGA - -#import "AsyncDisplayKit/ASAvailability.h" -#import "ASButtonNode+Yoga.h" -#import -#import -#import -#import - -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 *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 - -#import "ASButtonNode+Private.h" - -@implementation ASButtonNode (Yoga) - -- (void)updateYogaLayoutIfNeeded {} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASButtonNode.h b/submodules/AsyncDisplayKit/Source/ASButtonNode.h deleted file mode 100644 index f43a544df1..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASButtonNode.h +++ /dev/null @@ -1,130 +0,0 @@ -// -// 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 -#import - -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 diff --git a/submodules/AsyncDisplayKit/Source/ASButtonNode.mm b/submodules/AsyncDisplayKit/Source/ASButtonNode.mm deleted file mode 100644 index 8a28095abf..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASButtonNode.mm +++ /dev/null @@ -1,561 +0,0 @@ -// -// 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 -#import -#import -#import -#import -#import -#import -#import -#import "Private/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.displayWithoutProcessing = true; - _imageNode.displaysAsynchronously = false; - [_imageNode setLayerBacked:YES]; - } - return _imageNode; -} - -- (ASImageNode *)backgroundImageNode -{ - ASLockScopeSelf(); - if (!_backgroundImageNode) { - _backgroundImageNode = [[ASImageNode alloc] init]; - _backgroundImageNode.displayWithoutProcessing = true; - _backgroundImageNode.displaysAsynchronously = false; - [_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 diff --git a/submodules/AsyncDisplayKit/Source/ASCellNode+Internal.h b/submodules/AsyncDisplayKit/Source/ASCellNode+Internal.h deleted file mode 100644 index 4c3e63d652..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCellNode+Internal.h +++ /dev/null @@ -1,79 +0,0 @@ -// -// ASCellNode+Internal.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 - -NS_ASSUME_NONNULL_BEGIN - -@class ASCollectionElement; - -@protocol ASCellNodeInteractionDelegate - -/** - * Notifies the delegate that a specified cell node invalidates it's size what could result into a size change. - * - * @param node A node informing the delegate about the relayout. - */ -- (void)nodeDidInvalidateSize:(ASCellNode *)node; - -/* - * Methods to be called whenever the selection or highlight state changes - * on ASCellNode. UIKit internally stores these values to update reusable cells. - */ - -- (void)nodeSelectedStateDidChange:(ASCellNode *)node; -- (void)nodeHighlightedStateDidChange:(ASCellNode *)node; - -@end - -@interface ASCellNode () - -@property (nonatomic, weak) id interactionDelegate; - -/* - * Back-pointer to the containing scrollView instance, set only for visible cells. Used for Cell Visibility Event callbacks. - */ -@property (nonatomic, weak) UIScrollView *scrollView; - -- (void)__setSelectedFromUIKit:(BOOL)selected; -- (void)__setHighlightedFromUIKit:(BOOL)highlighted; - -/** - * @note This could be declared @c copy, but since this is only settable internally, we can ensure - * that it's always safe simply to retain it, and copy if needed. Since @c UICollectionViewLayoutAttributes - * is always mutable, @c copy is never "free" like it is for e.g. NSString. - */ -@property (nullable, nonatomic) UICollectionViewLayoutAttributes *layoutAttributes; - -@property (weak, nullable) ASCollectionElement *collectionElement; - -@property (weak, nullable) id owningNode; - -@property (nonatomic, readonly) BOOL shouldUseUIKitCell; - -@end - -@class ASWrapperCellNode; - -typedef CGSize (^ASSizeForItemBlock)(ASWrapperCellNode *node, CGSize collectionSize); -typedef UICollectionViewCell * _Nonnull(^ASCellForItemBlock)(ASWrapperCellNode *node); -typedef UICollectionReusableView * _Nonnull(^ASViewForSupplementaryBlock)(ASWrapperCellNode *node); - -@interface ASWrapperCellNode : ASCellNode - -@property (nonatomic, readonly) ASSizeForItemBlock sizeForItemBlock; -@property (nonatomic, readonly) ASCellForItemBlock cellForItemBlock; -@property (nonatomic, readonly) ASViewForSupplementaryBlock viewForSupplementaryBlock; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCellNode.h b/submodules/AsyncDisplayKit/Source/ASCellNode.h deleted file mode 100644 index 35d836ce5f..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCellNode.h +++ /dev/null @@ -1,263 +0,0 @@ -// -// 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 - -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 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 *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER; -- (void)touchesMoved:(NSSet *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER; -- (void)touchesEnded:(NSSet *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER; -- (void)touchesCancelled:(nullable NSSet *)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 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 *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 diff --git a/submodules/AsyncDisplayKit/Source/ASCellNode.mm b/submodules/AsyncDisplayKit/Source/ASCellNode.mm deleted file mode 100644 index 91f561a129..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCellNode.mm +++ /dev/null @@ -1,488 +0,0 @@ -// -// 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 - -#import -#import "Private/ASInternalHelpers.h" -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#import -#import -#import - -#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 *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 *)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 *_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 diff --git a/submodules/AsyncDisplayKit/Source/ASCenterLayoutSpec.h b/submodules/AsyncDisplayKit/Source/ASCenterLayoutSpec.h deleted file mode 100644 index dd1f99aa5a..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCenterLayoutSpec.h +++ /dev/null @@ -1,70 +0,0 @@ -// -// ASCenterLayoutSpec.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 - -/** - * How the child is centered within the spec. - * - * The default option will position the child at {0,0} relatively to the layout bound. - * Swift: use [] for the default behavior. - */ -typedef NS_OPTIONS(NSUInteger, ASCenterLayoutSpecCenteringOptions) { - /** The child is positioned in {0,0} relatively to the layout bounds */ - ASCenterLayoutSpecCenteringNone = 0, - /** The child is centered along the X axis */ - ASCenterLayoutSpecCenteringX = 1 << 0, - /** The child is centered along the Y axis */ - ASCenterLayoutSpecCenteringY = 1 << 1, - /** Convenience option to center both along the X and Y axis */ - ASCenterLayoutSpecCenteringXY = ASCenterLayoutSpecCenteringX | ASCenterLayoutSpecCenteringY -}; - -/** - * How much space the spec will take up. - * - * The default option will allow the spec to take up the maximum size possible. - * Swift: use [] for the default behavior. - */ -typedef NS_OPTIONS(NSUInteger, ASCenterLayoutSpecSizingOptions) { - /** The spec will take up the maximum size possible */ - ASCenterLayoutSpecSizingOptionDefault = ASRelativeLayoutSpecSizingOptionDefault, - /** The spec will take up the minimum size possible along the X axis */ - ASCenterLayoutSpecSizingOptionMinimumX = ASRelativeLayoutSpecSizingOptionMinimumWidth, - /** The spec will take up the minimum size possible along the Y axis */ - ASCenterLayoutSpecSizingOptionMinimumY = ASRelativeLayoutSpecSizingOptionMinimumHeight, - /** Convenience option to take up the minimum size along both the X and Y axis */ - ASCenterLayoutSpecSizingOptionMinimumXY = ASRelativeLayoutSpecSizingOptionMinimumSize -}; - -NS_ASSUME_NONNULL_BEGIN - -/** Lays out a single layoutElement child and position it so that it is centered into the layout bounds. - * NOTE: ASRelativeLayoutSpec offers all of the capabilities of Center, and more. - * Check it out if you would like to be able to position the child at any corner or the middle of an edge. - */ -@interface ASCenterLayoutSpec : ASRelativeLayoutSpec - -@property (nonatomic) ASCenterLayoutSpecCenteringOptions centeringOptions; -@property (nonatomic) ASCenterLayoutSpecSizingOptions sizingOptions; - -/** - * Initializer. - * - * @param centeringOptions How the child is centered. - * @param sizingOptions How much space will be taken up. - * @param child The child to center. - */ -+ (instancetype)centerLayoutSpecWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions - sizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions - child:(id)child NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASCenterLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/ASCenterLayoutSpec.mm deleted file mode 100644 index 107f317c0c..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCenterLayoutSpec.mm +++ /dev/null @@ -1,76 +0,0 @@ -// -// ASCenterLayoutSpec.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 - -#import - -@implementation ASCenterLayoutSpec -{ - ASCenterLayoutSpecCenteringOptions _centeringOptions; - ASCenterLayoutSpecSizingOptions _sizingOptions; -} - -- (instancetype)initWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions - sizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions - child:(id)child; -{ - ASRelativeLayoutSpecPosition verticalPosition = [self verticalPositionFromCenteringOptions:centeringOptions]; - ASRelativeLayoutSpecPosition horizontalPosition = [self horizontalPositionFromCenteringOptions:centeringOptions]; - - if (!(self = [super initWithHorizontalPosition:horizontalPosition verticalPosition:verticalPosition sizingOption:sizingOptions child:child])) { - return nil; - } - _centeringOptions = centeringOptions; - _sizingOptions = sizingOptions; - return self; -} - -+ (instancetype)centerLayoutSpecWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions - sizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions - child:(id)child NS_RETURNS_RETAINED -{ - return [[self alloc] initWithCenteringOptions:centeringOptions sizingOptions:sizingOptions child:child]; -} - -- (void)setCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _centeringOptions = centeringOptions; - - [self setHorizontalPosition:[self horizontalPositionFromCenteringOptions:centeringOptions]]; - [self setVerticalPosition:[self verticalPositionFromCenteringOptions:centeringOptions]]; -} - -- (void)setSizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _sizingOptions = sizingOptions; - [self setSizingOption:sizingOptions]; -} - -- (ASRelativeLayoutSpecPosition)horizontalPositionFromCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions -{ - if ((centeringOptions & ASCenterLayoutSpecCenteringX) != 0) { - return ASRelativeLayoutSpecPositionCenter; - } else { - return ASRelativeLayoutSpecPositionNone; - } -} - -- (ASRelativeLayoutSpecPosition)verticalPositionFromCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions -{ - if ((centeringOptions & ASCenterLayoutSpecCenteringY) != 0) { - return ASRelativeLayoutSpecPositionCenter; - } else { - return ASRelativeLayoutSpecPositionNone; - } -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionElement.h b/submodules/AsyncDisplayKit/Source/ASCollectionElement.h deleted file mode 100644 index ea8fc697c4..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionElement.h +++ /dev/null @@ -1,51 +0,0 @@ -// -// ASCollectionElement.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 -#import - -@class ASDisplayNode; -@protocol ASRangeManagingNode; - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASCollectionElement : NSObject - -@property (nullable, nonatomic, copy, readonly) NSString *supplementaryElementKind; -@property (nonatomic) ASSizeRange constrainedSize; -@property (nonatomic, weak, readonly) id owningNode; -@property (nonatomic) ASPrimitiveTraitCollection traitCollection; -@property (nullable, nonatomic, readonly) id nodeModel; - -- (instancetype)initWithNodeModel:(nullable id)nodeModel - nodeBlock:(ASCellNodeBlock)nodeBlock - supplementaryElementKind:(nullable NSString *)supplementaryElementKind - constrainedSize:(ASSizeRange)constrainedSize - owningNode:(id)owningNode - traitCollection:(ASPrimitiveTraitCollection)traitCollection; - -/** - * @return The node, running the node block if necessary. The node block will be discarded - * after the first time it is run. - */ -@property (readonly) ASCellNode *node; - -/** - * @return The node, if the node block has been run already. - */ -@property (nullable, readonly) ASCellNode *nodeIfAllocated; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionElement.mm b/submodules/AsyncDisplayKit/Source/ASCollectionElement.mm deleted file mode 100644 index 5b7b33862b..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionElement.mm +++ /dev/null @@ -1,91 +0,0 @@ -// -// ASCollectionElement.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 -#import -#import - -@interface ASCollectionElement () - -/// Required node block used to allocate a cell node. Nil after the first execution. -@property (nonatomic) ASCellNodeBlock nodeBlock; - -@end - -@implementation ASCollectionElement { - std::mutex _lock; - ASCellNode *_node; -} - -- (instancetype)initWithNodeModel:(id)nodeModel - nodeBlock:(ASCellNodeBlock)nodeBlock - supplementaryElementKind:(NSString *)supplementaryElementKind - constrainedSize:(ASSizeRange)constrainedSize - owningNode:(id)owningNode - traitCollection:(ASPrimitiveTraitCollection)traitCollection -{ - NSAssert(nodeBlock != nil, @"Node block must not be nil"); - self = [super init]; - if (self) { - _nodeModel = nodeModel; - _nodeBlock = nodeBlock; - _supplementaryElementKind = [supplementaryElementKind copy]; - _constrainedSize = constrainedSize; - _owningNode = owningNode; - _traitCollection = traitCollection; - } - return self; -} - -- (ASCellNode *)node -{ - std::lock_guard l(_lock); - if (_nodeBlock != nil) { - ASCellNode *node = _nodeBlock(); - _nodeBlock = nil; - if (node == nil) { - ASDisplayNodeFailAssert(@"Node block returned nil node!"); - node = [[ASCellNode alloc] init]; - } - node.owningNode = _owningNode; - node.collectionElement = self; - ASTraitCollectionPropagateDown(node, _traitCollection); - node.nodeModel = _nodeModel; - _node = node; - } - return _node; -} - -- (ASCellNode *)nodeIfAllocated -{ - std::lock_guard l(_lock); - return _node; -} - -- (void)setTraitCollection:(ASPrimitiveTraitCollection)traitCollection -{ - ASCellNode *nodeIfNeedsPropagation; - - { - std::lock_guard l(_lock); - if (! ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(_traitCollection, traitCollection)) { - _traitCollection = traitCollection; - nodeIfNeedsPropagation = _node; - } - } - - if (nodeIfNeedsPropagation != nil) { - ASTraitCollectionPropagateDown(nodeIfNeedsPropagation, traitCollection); - } -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionFlowLayoutDelegate.h b/submodules/AsyncDisplayKit/Source/ASCollectionFlowLayoutDelegate.h deleted file mode 100644 index ba6e5b48ed..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionFlowLayoutDelegate.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// ASCollectionFlowLayoutDelegate.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 - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED - -/** - * A thread-safe, high performant layout delegate that arranges items into a flow layout. - * It uses a concurrent and multi-line ASStackLayoutSpec under the hood. Thus, per-child flex properties (i.e alignSelf, - * flexShrink, flexGrow, etc - see @ASStackLayoutElement) can be set directly on cell nodes to be used - * to calculate the final collection layout. - */ -@interface ASCollectionFlowLayoutDelegate : NSObject - -- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionFlowLayoutDelegate.mm b/submodules/AsyncDisplayKit/Source/ASCollectionFlowLayoutDelegate.mm deleted file mode 100644 index 37d311d24e..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionFlowLayoutDelegate.mm +++ /dev/null @@ -1,82 +0,0 @@ -// -// ASCollectionFlowLayoutDelegate.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 - -#import -#import -#import -#import -#import -#import -#import -#import -#import - -@implementation ASCollectionFlowLayoutDelegate { - ASScrollDirection _scrollableDirections; -} - -- (instancetype)init -{ - return [self initWithScrollableDirections:ASScrollDirectionVerticalDirections]; -} - -- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections -{ - self = [super init]; - if (self) { - _scrollableDirections = scrollableDirections; - } - return self; -} - -- (ASScrollDirection)scrollableDirections -{ - ASDisplayNodeAssertMainThread(); - return _scrollableDirections; -} - -- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements -{ - ASDisplayNodeAssertMainThread(); - return nil; -} - -+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context -{ - ASElementMap *elements = context.elements; - NSArray *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node); - if (children.count == 0) { - return [[ASCollectionLayoutState alloc] initWithContext:context]; - } - - ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal - spacing:0 - justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsStart - flexWrap:ASStackLayoutFlexWrapWrap - alignContent:ASStackLayoutAlignContentStart - children:children]; - stackSpec.concurrent = YES; - - ASSizeRange sizeRange = ASSizeRangeForCollectionLayoutThatFitsViewportSize(context.viewportSize, context.scrollableDirections); - ASLayout *layout = [stackSpec layoutThatFits:sizeRange]; - - return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout getElementBlock:^ASCollectionElement * _Nullable(ASLayout * _Nonnull sublayout) { - ASCellNode *node = ASDynamicCast(sublayout.layoutElement, ASCellNode); - return node ? node.collectionElement : nil; - }]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionGalleryLayoutDelegate.h b/submodules/AsyncDisplayKit/Source/ASCollectionGalleryLayoutDelegate.h deleted file mode 100644 index c6d1df2cb7..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionGalleryLayoutDelegate.h +++ /dev/null @@ -1,110 +0,0 @@ -// -// ASCollectionGalleryLayoutDelegate.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import - -@class ASElementMap; -@class ASCollectionGalleryLayoutDelegate; - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASCollectionGalleryLayoutPropertiesProviding - -/** - * Returns the fixed size of each and every element. - * - * @discussion This method will only be called on main thread. - * - * @param delegate The calling object. - * - * @param elements All elements to be sized. - * - * @return The elements' size - */ -- (CGSize)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate sizeForElements:(ASElementMap *)elements; - -@optional - -/** - * Returns the minumum spacing to use between lines of items. - * - * @discussion This method will only be called on main thread. - * - * @discussion For a vertically scrolling layout, this value represents the minimum spacing between rows. - * For a horizontally scrolling one, it represents the minimum spacing between columns. - * It is not applied between the first line and the header, or between the last line and the footer. - * This is the same behavior as UICollectionViewFlowLayout's minimumLineSpacing. - * - * @param delegate The calling object. - * - * @param elements All elements in the layout. - * - * @return The interitem spacing - */ -- (CGFloat)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate minimumLineSpacingForElements:(ASElementMap *)elements; - -/** - * Returns the minumum spacing to use between items in the same row or column, depending on the scroll directions. - * - * @discussion This method will only be called on main thread. - * - * @discussion For a vertically scrolling layout, this value represents the minimum spacing between items in the same row. - * For a horizontally scrolling one, it represents the minimum spacing between items in the same column. - * It is considered while fitting items into lines, but the actual final spacing between some items might be larger. - * This is the same behavior as UICollectionViewFlowLayout's minimumInteritemSpacing. - * - * @param delegate The calling object. - * - * @param elements All elements in the layout. - * - * @return The interitem spacing - */ -- (CGFloat)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate minimumInteritemSpacingForElements:(ASElementMap *)elements; - -/** - * Returns the margins of each section. - * - * @discussion This method will only be called on main thread. - * - * @param delegate The calling object. - * - * @param elements All elements in the layout. - * - * @return The margins used to layout content in a section - */ -- (UIEdgeInsets)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate sectionInsetForElements:(ASElementMap *)elements; - -@end - -/** - * A thread-safe layout delegate that arranges items with the same size into a flow layout. - * - * @note Supplemenraty elements are not supported. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASCollectionGalleryLayoutDelegate : NSObject - -@property (nonatomic, weak) id propertiesProvider; - -/** - * Designated initializer. - * - * @param scrollableDirections The scrollable directions of this layout. Must be either vertical or horizontal directions. - */ -- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections NS_DESIGNATED_INITIALIZER; - -- (instancetype)init __unavailable; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionGalleryLayoutDelegate.m b/submodules/AsyncDisplayKit/Source/ASCollectionGalleryLayoutDelegate.m deleted file mode 100644 index 99e65f8343..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionGalleryLayoutDelegate.m +++ /dev/null @@ -1,97 +0,0 @@ -// -// ASCollectionGalleryLayoutDelegate.m -// Texture -// -// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. -// 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 -// - -#ifndef MINIMAL_ASDK - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#pragma mark - ASCollectionGalleryLayoutDelegate - -@implementation ASCollectionGalleryLayoutDelegate { - ASScrollDirection _scrollableDirections; - CGSize _itemSize; -} - -- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections -{ - self = [super init]; - if (self) { - _scrollableDirections = scrollableDirections; - } - return self; -} - -- (ASScrollDirection)scrollableDirections -{ - ASDisplayNodeAssertMainThread(); - return _scrollableDirections; -} - -- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements -{ - ASDisplayNodeAssertMainThread(); - if (_sizeProvider == nil) { - return nil; - } - - return [NSValue valueWithCGSize:[_sizeProvider sizeForElements:elements]]; -} - -+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context -{ - ASElementMap *elements = context.elements; - CGSize pageSize = context.viewportSize; - ASScrollDirection scrollableDirections = context.scrollableDirections; - - CGSize itemSize = context.additionalInfo ? ((NSValue *)context.additionalInfo).CGSizeValue : CGSizeZero; - if (CGSizeEqualToSize(CGSizeZero, itemSize)) { - return [[ASCollectionLayoutState alloc] initWithContext:context]; - } - - NSMutableArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements, - ASCollectionElement *element, - [[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]); - if (children.count == 0) { - return [[ASCollectionLayoutState alloc] initWithContext:context]; - } - - // Use a stack spec to calculate layout content size and frames of all elements without actually measuring each element - ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal - spacing:0 - justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsStart - flexWrap:ASStackLayoutFlexWrapWrap - alignContent:ASStackLayoutAlignContentStart - children:children]; - stackSpec.concurrent = YES; - ASLayout *layout = [stackSpec layoutThatFits:ASSizeRangeForCollectionLayoutThatFitsViewportSize(pageSize, scrollableDirections)]; - - return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout getElementBlock:^ASCollectionElement *(ASLayout *sublayout) { - return ((_ASGalleryLayoutItem *)sublayout.layoutElement).collectionElement; - }]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionGalleryLayoutDelegate.mm b/submodules/AsyncDisplayKit/Source/ASCollectionGalleryLayoutDelegate.mm deleted file mode 100644 index 94d1f0de36..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionGalleryLayoutDelegate.mm +++ /dev/null @@ -1,141 +0,0 @@ -// -// ASCollectionGalleryLayoutDelegate.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#pragma mark - ASCollectionGalleryLayoutDelegate - -@implementation ASCollectionGalleryLayoutDelegate { - ASScrollDirection _scrollableDirections; - - struct { - unsigned int minimumLineSpacingForElements:1; - unsigned int minimumInteritemSpacingForElements:1; - unsigned int sectionInsetForElements:1; - } _propertiesProviderFlags; -} - -- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections -{ - self = [super init]; - if (self) { - // Scrollable directions must be either vertical or horizontal, but not both - ASDisplayNodeAssertTrue(ASScrollDirectionContainsVerticalDirection(scrollableDirections) - || ASScrollDirectionContainsHorizontalDirection(scrollableDirections)); - ASDisplayNodeAssertFalse(ASScrollDirectionContainsVerticalDirection(scrollableDirections) - && ASScrollDirectionContainsHorizontalDirection(scrollableDirections)); - _scrollableDirections = scrollableDirections; - } - return self; -} - -- (ASScrollDirection)scrollableDirections -{ - ASDisplayNodeAssertMainThread(); - return _scrollableDirections; -} - -- (void)setPropertiesProvider:(id)propertiesProvider -{ - ASDisplayNodeAssertMainThread(); - if (propertiesProvider == nil) { - _propertiesProvider = nil; - _propertiesProviderFlags = {}; - } else { - _propertiesProvider = propertiesProvider; - _propertiesProviderFlags.minimumLineSpacingForElements = [_propertiesProvider respondsToSelector:@selector(galleryLayoutDelegate:minimumLineSpacingForElements:)]; - _propertiesProviderFlags.minimumInteritemSpacingForElements = [_propertiesProvider respondsToSelector:@selector(galleryLayoutDelegate:minimumInteritemSpacingForElements:)]; - _propertiesProviderFlags.sectionInsetForElements = [_propertiesProvider respondsToSelector:@selector(galleryLayoutDelegate:sectionInsetForElements:)]; - } -} - -- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements -{ - ASDisplayNodeAssertMainThread(); - id propertiesProvider = _propertiesProvider; - if (propertiesProvider == nil) { - return nil; - } - - CGSize itemSize = [propertiesProvider galleryLayoutDelegate:self sizeForElements:elements]; - UIEdgeInsets sectionInset = _propertiesProviderFlags.sectionInsetForElements ? [propertiesProvider galleryLayoutDelegate:self sectionInsetForElements:elements] : UIEdgeInsetsZero; - CGFloat lineSpacing = _propertiesProviderFlags.minimumLineSpacingForElements ? [propertiesProvider galleryLayoutDelegate:self minimumLineSpacingForElements:elements] : 0.0; - CGFloat interitemSpacing = _propertiesProviderFlags.minimumInteritemSpacingForElements ? [propertiesProvider galleryLayoutDelegate:self minimumInteritemSpacingForElements:elements] : 0.0; - return [[_ASCollectionGalleryLayoutInfo alloc] initWithItemSize:itemSize - minimumLineSpacing:lineSpacing - minimumInteritemSpacing:interitemSpacing - sectionInset:sectionInset]; -} - -+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context -{ - ASElementMap *elements = context.elements; - CGSize pageSize = context.viewportSize; - ASScrollDirection scrollableDirections = context.scrollableDirections; - - _ASCollectionGalleryLayoutInfo *info = ASDynamicCast(context.additionalInfo, _ASCollectionGalleryLayoutInfo); - CGSize itemSize = info.itemSize; - if (info == nil || CGSizeEqualToSize(CGSizeZero, itemSize)) { - return [[ASCollectionLayoutState alloc] initWithContext:context]; - } - - NSArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements, - ASCollectionElement *element, - [[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]); - if (children.count == 0) { - return [[ASCollectionLayoutState alloc] initWithContext:context]; - } - - // Use a stack spec to calculate layout content size and frames of all elements without actually measuring each element - ASStackLayoutDirection stackDirection = ASScrollDirectionContainsVerticalDirection(scrollableDirections) - ? ASStackLayoutDirectionHorizontal - : ASStackLayoutDirectionVertical; - ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:stackDirection - spacing:info.minimumInteritemSpacing - justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsStart - flexWrap:ASStackLayoutFlexWrapWrap - alignContent:ASStackLayoutAlignContentStart - lineSpacing:info.minimumLineSpacing - children:children]; - stackSpec.concurrent = YES; - - ASLayoutSpec *finalSpec = stackSpec; - UIEdgeInsets sectionInset = info.sectionInset; - if (UIEdgeInsetsEqualToEdgeInsets(sectionInset, UIEdgeInsetsZero) == NO) { - finalSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:sectionInset child:stackSpec]; - } - - ASLayout *layout = [finalSpec layoutThatFits:ASSizeRangeForCollectionLayoutThatFitsViewportSize(pageSize, scrollableDirections)]; - - return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout getElementBlock:^ASCollectionElement * _Nullable(ASLayout * _Nonnull sublayout) { - _ASGalleryLayoutItem *item = ASDynamicCast(sublayout.layoutElement, _ASGalleryLayoutItem); - return item ? item.collectionElement : nil; - }]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionInternal.h b/submodules/AsyncDisplayKit/Source/ASCollectionInternal.h deleted file mode 100644 index 925b19f970..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionInternal.h +++ /dev/null @@ -1,68 +0,0 @@ -// -// ASCollectionInternal.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 - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASCollectionViewLayoutFacilitatorProtocol; -@class ASCollectionNode; -@class ASDataController; -@class ASRangeController; - -@interface ASCollectionView () -- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id)layoutFacilitator owningNode:(nullable ASCollectionNode *)owningNode eventLog:(nullable ASEventLog *)eventLog; - -@property (nonatomic, weak) ASCollectionNode *collectionNode; -@property (nonatomic, readonly) ASDataController *dataController; -@property (nonatomic, readonly) ASRangeController *rangeController; - -/** - * The change set that we're currently building, if any. - */ -@property (nonatomic, nullable, readonly) _ASHierarchyChangeSet *changeSet; - -/** - * @see ASCollectionNode+Beta.h for full documentation. - */ -@property (nonatomic) ASCellLayoutMode cellLayoutMode; - -/** - * Attempt to get the view-layer index path for the item with the given index path. - * - * @param indexPath The index path of the item. - * @param wait If the item hasn't reached the view yet, this attempts to wait for updates to commit. - */ -- (nullable NSIndexPath *)convertIndexPathFromCollectionNode:(NSIndexPath *)indexPath waitingIfNeeded:(BOOL)wait; - -/** - * Attempt to get the node index path given the view-layer index path. - * - * @param indexPath The index path of the row. - */ -- (nullable NSIndexPath *)convertIndexPathToCollectionNode:(NSIndexPath *)indexPath; - -/** - * Attempt to get the node index paths given the view-layer index paths. - * - * @param indexPaths An array of index paths in the view space - */ -- (nullable NSArray *)convertIndexPathsToCollectionNode:(nullable NSArray *)indexPaths; - -- (void)beginUpdates; - -- (void)endUpdatesAnimated:(BOOL)animated completion:(nullable void (^)(BOOL))completion; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionLayout.h b/submodules/AsyncDisplayKit/Source/ASCollectionLayout.h deleted file mode 100644 index 6ba15e96eb..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionLayout.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// ASCollectionLayout.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import - -@protocol ASCollectionLayoutDelegate; -@class ASElementMap, ASCollectionLayout, ASCollectionNode; - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASCollectionLayout : UICollectionViewLayout - -/** - * The collection node object currently using this layout object. - * - * @discussion The collection node object sets the value of this property when a new layout object is assigned to it. - * - * @discussion To get the truth on the current state of the collection, call methods on the collection node or the data source rather than the collection view because: - * 1. The view might not yet be allocated. - * 2. The collection node and data source are thread-safe. - */ -@property (nonatomic, weak) ASCollectionNode *collectionNode; - -@property (nonatomic, readonly) id layoutDelegate; - -/** - * Initializes with a layout delegate. - * - * @discussion For developers' convenience, the delegate is retained by this layout object, similar to UICollectionView retains its UICollectionViewLayout object. - * - * @discussion For simplicity, the delegate is read-only. If a new layout delegate is needed, construct a new layout object with that delegate and notify ASCollectionView about it. - * This ensures the underlying UICollectionView purges its cache and properly loads the new layout. - */ -- (instancetype)initWithLayoutDelegate:(id)layoutDelegate NS_DESIGNATED_INITIALIZER; - -- (instancetype)init __unavailable; - -- (instancetype)initWithCoder:(NSCoder *)aDecoder __unavailable; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionLayout.mm b/submodules/AsyncDisplayKit/Source/ASCollectionLayout.mm deleted file mode 100644 index 59697f0820..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionLayout.mm +++ /dev/null @@ -1,390 +0,0 @@ -// -// ASCollectionLayout.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 - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -static const ASRangeTuningParameters kASDefaultMeasureRangeTuningParameters = { - .leadingBufferScreenfuls = 2.0, - .trailingBufferScreenfuls = 2.0 -}; - -static const ASScrollDirection kASStaticScrollDirection = (ASScrollDirectionRight | ASScrollDirectionDown); - -@interface ASCollectionLayout () { - ASCollectionLayoutCache *_layoutCache; - ASCollectionLayoutState *_layout; // Main thread only. - - struct { - unsigned int implementsAdditionalInfoForLayoutWithElements:1; - } _layoutDelegateFlags; -} - -@end - -@implementation ASCollectionLayout - -- (instancetype)initWithLayoutDelegate:(id)layoutDelegate -{ - self = [super init]; - if (self) { - ASDisplayNodeAssertNotNil(layoutDelegate, @"Collection layout delegate cannot be nil"); - _layoutDelegate = layoutDelegate; - _layoutDelegateFlags.implementsAdditionalInfoForLayoutWithElements = [layoutDelegate respondsToSelector:@selector(additionalInfoForLayoutWithElements:)]; - _layoutCache = [[ASCollectionLayoutCache alloc] init]; - } - return self; -} - -#pragma mark - ASDataControllerLayoutDelegate - -- (ASCollectionLayoutContext *)layoutContextWithElements:(ASElementMap *)elements -{ - ASDisplayNodeAssertMainThread(); - - Class layoutDelegateClass = [_layoutDelegate class]; - ASCollectionLayoutCache *layoutCache = _layoutCache; - ASCollectionNode *collectionNode = _collectionNode; - if (collectionNode == nil) { - return [[ASCollectionLayoutContext alloc] initWithViewportSize:CGSizeZero - initialContentOffset:CGPointZero - scrollableDirections:ASScrollDirectionNone - elements:[[ASElementMap alloc] init] - layoutDelegateClass:layoutDelegateClass - layoutCache:layoutCache - additionalInfo:nil]; - } - - ASScrollDirection scrollableDirections = [_layoutDelegate scrollableDirections]; - CGSize viewportSize = [ASCollectionLayout _viewportSizeForCollectionNode:collectionNode scrollableDirections:scrollableDirections]; - CGPoint contentOffset = collectionNode.contentOffset; - - id additionalInfo = nil; - if (_layoutDelegateFlags.implementsAdditionalInfoForLayoutWithElements) { - additionalInfo = [_layoutDelegate additionalInfoForLayoutWithElements:elements]; - } - - return [[ASCollectionLayoutContext alloc] initWithViewportSize:viewportSize - initialContentOffset:contentOffset - scrollableDirections:scrollableDirections - elements:elements - layoutDelegateClass:layoutDelegateClass - layoutCache:layoutCache - additionalInfo:additionalInfo]; -} - -+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context -{ - if (context.elements == nil) { - return [[ASCollectionLayoutState alloc] initWithContext:context]; - } - - ASCollectionLayoutState *layout = [context.layoutDelegateClass calculateLayoutWithContext:context]; - [context.layoutCache setLayout:layout forContext:context]; - - // Measure elements in the measure range ahead of time - CGSize viewportSize = context.viewportSize; - CGPoint contentOffset = context.initialContentOffset; - CGRect initialRect = CGRectMake(contentOffset.x, contentOffset.y, viewportSize.width, viewportSize.height); - CGRect measureRect = CGRectExpandToRangeWithScrollableDirections(initialRect, - kASDefaultMeasureRangeTuningParameters, - context.scrollableDirections, - kASStaticScrollDirection); - // The first call to -layoutAttributesForElementsInRect: will be with a rect that is way bigger than initialRect here. - // If we only block on initialRect, a few elements that are outside of initialRect but inside measureRect - // may not be available by the time -layoutAttributesForElementsInRect: is called. - // Since this method is usually run off main, let's spawn more threads to measure and block on all elements in measureRect. - [self _measureElementsInRect:measureRect blockingRect:measureRect layout:layout]; - - return layout; -} - -#pragma mark - UICollectionViewLayout overrides - -- (void)prepareLayout -{ - ASDisplayNodeAssertMainThread(); - [super prepareLayout]; - - ASCollectionLayoutContext *context = [self layoutContextWithElements:_collectionNode.visibleElements]; - if (_layout != nil && ASObjectIsEqual(_layout.context, context)) { - // The existing layout is still valid. No-op - return; - } - - if (ASCollectionLayoutState *cachedLayout = [_layoutCache layoutForContext:context]) { - _layout = cachedLayout; - } else { - // A new layout is needed now. Calculate and apply it immediately - _layout = [ASCollectionLayout calculateLayoutWithContext:context]; - } -} - -- (void)invalidateLayout -{ - ASDisplayNodeAssertMainThread(); - [super invalidateLayout]; - if (_layout != nil) { - [_layoutCache removeLayoutForContext:_layout.context]; - _layout = nil; - } -} - -/** - * NOTE: It is suggested practice on the Web to override invalidationContextForInteractivelyMovingItems… and call out to the - * data source to move the item (so that if e.g. the item size depends on the data, you get the data you expect). However, as of iOS 11 this - * doesn't work, because UICV machinery will also call out to the data source to move the item after the interaction is done. The result is - * that your data source state will be incorrect due to this last move call. Plus it's just an API violation. - * - * Things tried: - * - Doing the speculative data source moves, and then UNDOING the last one in invalidationContextForEndingInteractiveMovementOfItems… - * but this does not work because the UICV machinery informs its data source before it calls that method on us, so we are too late. - * - * The correct practice is to use the UIDataSourceTranslating API introduced in iOS 11. Currently Texture does not support this API but we can - * build it if there is demand. We could add an id field onto the layout context object, and the layout client can - * use data source index paths when it reads nodes or other data source data. - */ - -- (CGSize)collectionViewContentSize -{ - ASDisplayNodeAssertMainThread(); - // The content size can be queried right after a layout invalidation (https://github.com/TextureGroup/Texture/pull/509). - // In that case, return zero. - return _layout ? _layout.contentSize : CGSizeZero; -} - -- (NSArray *)layoutAttributesForElementsInRect:(CGRect)blockingRect -{ - ASDisplayNodeAssertMainThread(); - if (CGRectIsEmpty(blockingRect)) { - return nil; - } - - // Measure elements in the measure range, block on the requested rect - CGRect measureRect = CGRectExpandToRangeWithScrollableDirections(blockingRect, - kASDefaultMeasureRangeTuningParameters, - _layout.context.scrollableDirections, - kASStaticScrollDirection); - [ASCollectionLayout _measureElementsInRect:measureRect blockingRect:blockingRect layout:_layout]; - - NSArray *result = [_layout layoutAttributesForElementsInRect:blockingRect]; - - ASElementMap *elements = _layout.context.elements; - for (UICollectionViewLayoutAttributes *attrs in result) { - ASCollectionElement *element = [elements elementForLayoutAttributes:attrs]; - ASCollectionLayoutSetSizeToElement(attrs.frame.size, element); - } - - return result; -} - -- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - - ASCollectionElement *element = [_layout.context.elements elementForItemAtIndexPath:indexPath]; - UICollectionViewLayoutAttributes *attrs = [_layout layoutAttributesForElement:element]; - - ASCellNode *node = element.node; - CGSize elementSize = attrs.frame.size; - if (! CGSizeEqualToSize(elementSize, node.calculatedSize)) { - [node layoutThatFits:ASCollectionLayoutElementSizeRangeFromSize(elementSize)]; - } - - ASCollectionLayoutSetSizeToElement(attrs.frame.size, element); - return attrs; -} - -- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath -{ - ASCollectionElement *element = [_layout.context.elements supplementaryElementOfKind:elementKind atIndexPath:indexPath]; - UICollectionViewLayoutAttributes *attrs = [_layout layoutAttributesForElement:element]; - - ASCellNode *node = element.node; - CGSize elementSize = attrs.frame.size; - if (! CGSizeEqualToSize(elementSize, node.calculatedSize)) { - [node layoutThatFits:ASCollectionLayoutElementSizeRangeFromSize(elementSize)]; - } - - ASCollectionLayoutSetSizeToElement(attrs.frame.size, element); - return attrs; -} - -- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds -{ - return (! CGSizeEqualToSize([ASCollectionLayout _boundsForCollectionNode:_collectionNode], newBounds.size)); -} - -#pragma mark - Private methods - -+ (CGSize)_boundsForCollectionNode:(nonnull ASCollectionNode *)collectionNode -{ - if (collectionNode == nil) { - return CGSizeZero; - } - - if (!collectionNode.isNodeLoaded) { - // TODO consider calculatedSize as well - return collectionNode.threadSafeBounds.size; - } - - ASDisplayNodeAssertMainThread(); - return collectionNode.view.bounds.size; -} - -+ (CGSize)_viewportSizeForCollectionNode:(nonnull ASCollectionNode *)collectionNode scrollableDirections:(ASScrollDirection)scrollableDirections -{ - if (collectionNode == nil) { - return CGSizeZero; - } - - CGSize result = [ASCollectionLayout _boundsForCollectionNode:collectionNode]; - // TODO: Consider using adjustedContentInset on iOS 11 and later, to include the safe area of the scroll view - UIEdgeInsets contentInset = collectionNode.contentInset; - if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { - result.height -= (contentInset.top + contentInset.bottom); - } else { - result.width -= (contentInset.left + contentInset.right); - } - return result; -} - -/** - * Measures all elements in the specified rect and blocks the calling thread while measuring those in the blocking rect. - */ -+ (void)_measureElementsInRect:(CGRect)rect blockingRect:(CGRect)blockingRect layout:(ASCollectionLayoutState *)layout -{ - if (CGRectIsEmpty(rect) || layout.context.elements == nil) { - return; - } - BOOL hasBlockingRect = !CGRectIsEmpty(blockingRect); - if (hasBlockingRect && CGRectContainsRect(rect, blockingRect) == NO) { - ASDisplayNodeCAssert(NO, @"Blocking rect, if specified, must be within the other (outer) rect"); - return; - } - - // Step 1: Clamp the specified rects between the bounds of content rect - CGSize contentSize = layout.contentSize; - CGRect contentRect = CGRectMake(0, 0, contentSize.width, contentSize.height); - rect = CGRectIntersection(contentRect, rect); - if (CGRectIsNull(rect)) { - return; - } - if (hasBlockingRect) { - blockingRect = CGRectIntersection(contentRect, blockingRect); - hasBlockingRect = !CGRectIsNull(blockingRect); - } - - // Step 2: Get layout attributes of all elements within the specified outer rect - ASPageToLayoutAttributesTable *attrsTable = [layout getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:rect]; - if (attrsTable.count == 0) { - // No elements in this rect! Bail early - return; - } - - // Step 3: Split all those attributes into blocking and non-blocking buckets - // Use ordered sets here because some items may span multiple pages, and the sets will be accessed by indexes later on. - ASCollectionLayoutContext *context = layout.context; - CGSize pageSize = context.viewportSize; - NSMutableOrderedSet *blockingAttrs = hasBlockingRect ? [NSMutableOrderedSet orderedSet] : nil; - NSMutableOrderedSet *nonBlockingAttrs = [NSMutableOrderedSet orderedSet]; - for (id pagePtr in attrsTable) { - ASPageCoordinate page = (ASPageCoordinate)pagePtr; - NSArray *attrsInPage = [attrsTable objectForPage:page]; - // Calculate the page's rect but only if it's going to be used. - CGRect pageRect = hasBlockingRect ? ASPageCoordinateGetPageRect(page, pageSize) : CGRectZero; - - if (hasBlockingRect && CGRectContainsRect(blockingRect, pageRect)) { - // The page fits well within the blocking rect. All attributes in this page are blocking. - [blockingAttrs addObjectsFromArray:attrsInPage]; - } else if (hasBlockingRect && CGRectIntersectsRect(blockingRect, pageRect)) { - // The page intersects the blocking rect. Some elements in this page are blocking, some are not. - for (UICollectionViewLayoutAttributes *attrs in attrsInPage) { - if (CGRectIntersectsRect(blockingRect, attrs.frame)) { - [blockingAttrs addObject:attrs]; - } else { - [nonBlockingAttrs addObject:attrs]; - } - } - } else { - // The page doesn't intersect the blocking rect. All elements in this page are non-blocking. - [nonBlockingAttrs addObjectsFromArray:attrsInPage]; - } - } - - // Step 4: Allocate and measure blocking elements' node - ASElementMap *elements = context.elements; - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - if (NSUInteger count = blockingAttrs.count) { - ASDispatchApply(count, queue, 0, ^(size_t i) { - UICollectionViewLayoutAttributes *attrs = blockingAttrs[i]; - ASCellNode *node = [elements elementForItemAtIndexPath:attrs.indexPath].node; - CGSize expectedSize = attrs.frame.size; - if (! CGSizeEqualToSize(expectedSize, node.calculatedSize)) { - [node layoutThatFits:ASCollectionLayoutElementSizeRangeFromSize(expectedSize)]; - } - }); - } - - // Step 5: Allocate and measure non-blocking ones - if (NSUInteger count = nonBlockingAttrs.count) { - __weak ASElementMap *weakElements = elements; - ASDispatchAsync(count, queue, 0, ^(size_t i) { - __strong ASElementMap *strongElements = weakElements; - if (strongElements) { - UICollectionViewLayoutAttributes *attrs = nonBlockingAttrs[i]; - ASCellNode *node = [elements elementForItemAtIndexPath:attrs.indexPath].node; - CGSize expectedSize = attrs.frame.size; - if (! CGSizeEqualToSize(expectedSize, node.calculatedSize)) { - [node layoutThatFits:ASCollectionLayoutElementSizeRangeFromSize(expectedSize)]; - } - } - }); - } -} - -# pragma mark - Convenient inline functions - -ASDISPLAYNODE_INLINE ASSizeRange ASCollectionLayoutElementSizeRangeFromSize(CGSize size) -{ - // The layout delegate consulted us that this element must fit within this size, - // and the only way to achieve that without asking it again is to use an exact size range here. - return ASSizeRangeMake(size); -} - -ASDISPLAYNODE_INLINE void ASCollectionLayoutSetSizeToElement(CGSize size, ASCollectionElement *element) -{ - if (ASCellNode *node = element.node) { - if (! CGSizeEqualToSize(size, node.frame.size)) { - CGRect frame = CGRectZero; - frame.size = size; - node.frame = frame; - } - } -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutCache.h b/submodules/AsyncDisplayKit/Source/ASCollectionLayoutCache.h deleted file mode 100644 index 857e43683f..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutCache.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// ASCollectionLayoutCache.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASCollectionLayoutContext, ASCollectionLayoutState; - -/// A thread-safe cache for ASCollectionLayoutContext-ASCollectionLayoutState pairs -AS_SUBCLASSING_RESTRICTED -@interface ASCollectionLayoutCache : NSObject - -- (nullable ASCollectionLayoutState *)layoutForContext:(ASCollectionLayoutContext *)context; - -- (void)setLayout:(ASCollectionLayoutState *)layout forContext:(ASCollectionLayoutContext *)context; - -- (void)removeLayoutForContext:(ASCollectionLayoutContext *)context; - -- (void)removeAllLayouts; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutCache.mm b/submodules/AsyncDisplayKit/Source/ASCollectionLayoutCache.mm deleted file mode 100644 index b5aecd78b6..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutCache.mm +++ /dev/null @@ -1,92 +0,0 @@ -// -// ASCollectionLayoutCache.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#import -#import -#import -#import - -using AS::MutexLocker; - -@implementation ASCollectionLayoutCache { - AS::Mutex __instanceLock__; - - /** - * The underlying data structure of this cache. - * - * The outer map table is a weak to strong table. That is because ASCollectionLayoutContext doesn't (and shouldn't) - * hold a strong reference on its element map. As a result, this cache should handle the case in which - * an element map no longer exists and all contexts and layouts associated with it should be cleared. - * - * The inner map table is a standard strong to strong map. - * Since different ASCollectionLayoutContext objects with the same content are considered equal, - * "object pointer personality" can't be used as a key option. - */ - NSMapTable *> *_map; -} - -- (instancetype)init -{ - self = [super init]; - if (self) { - _map = [NSMapTable mapTableWithKeyOptions:(NSMapTableWeakMemory | NSMapTableObjectPointerPersonality) valueOptions:NSMapTableStrongMemory]; - } - return self; -} - -- (ASCollectionLayoutState *)layoutForContext:(ASCollectionLayoutContext *)context -{ - ASElementMap *elements = context.elements; - if (elements == nil) { - return nil; - } - - MutexLocker l(__instanceLock__); - return [[_map objectForKey:elements] objectForKey:context]; -} - -- (void)setLayout:(ASCollectionLayoutState *)layout forContext:(ASCollectionLayoutContext *)context -{ - ASElementMap *elements = context.elements; - if (layout == nil || elements == nil) { - return; - } - - MutexLocker l(__instanceLock__); - auto innerMap = [_map objectForKey:elements]; - if (innerMap == nil) { - innerMap = [NSMapTable strongToStrongObjectsMapTable]; - [_map setObject:innerMap forKey:elements]; - } - [innerMap setObject:layout forKey:context]; -} - -- (void)removeLayoutForContext:(ASCollectionLayoutContext *)context -{ - ASElementMap *elements = context.elements; - if (elements == nil) { - return; - } - - MutexLocker l(__instanceLock__); - [[_map objectForKey:elements] removeObjectForKey:context]; -} - -- (void)removeAllLayouts -{ - MutexLocker l(__instanceLock__); - [_map removeAllObjects]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutContext+Private.h b/submodules/AsyncDisplayKit/Source/ASCollectionLayoutContext+Private.h deleted file mode 100644 index 2ff68e17bc..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutContext+Private.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// ASCollectionLayoutContext+Private.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 - -@class ASCollectionLayoutCache; -@protocol ASCollectionLayoutDelegate; - -NS_ASSUME_NONNULL_BEGIN - -@interface ASCollectionLayoutContext (Private) - -@property (nonatomic, readonly) Class layoutDelegateClass; -@property (nonatomic, weak, readonly) ASCollectionLayoutCache *layoutCache; - -- (instancetype)initWithViewportSize:(CGSize)viewportSize - initialContentOffset:(CGPoint)initialContentOffset - scrollableDirections:(ASScrollDirection)scrollableDirections - elements:(ASElementMap *)elements - layoutDelegateClass:(Class)layoutDelegateClass - layoutCache:(ASCollectionLayoutCache *)layoutCache - additionalInfo:(nullable id)additionalInfo; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutContext.h b/submodules/AsyncDisplayKit/Source/ASCollectionLayoutContext.h deleted file mode 100644 index f5e875f561..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutContext.h +++ /dev/null @@ -1,35 +0,0 @@ -// -// ASCollectionLayoutContext.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 -#import -#import - -@class ASElementMap; - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASCollectionLayoutContext : NSObject - -@property (nonatomic, readonly) CGSize viewportSize; -@property (nonatomic, readonly) CGPoint initialContentOffset; -@property (nonatomic, readonly) ASScrollDirection scrollableDirections; -@property (nonatomic, weak, readonly) ASElementMap *elements; -@property (nullable, nonatomic, readonly) id additionalInfo; - -- (instancetype)init NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutContext.mm b/submodules/AsyncDisplayKit/Source/ASCollectionLayoutContext.mm deleted file mode 100644 index 5f82fd76fa..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutContext.mm +++ /dev/null @@ -1,107 +0,0 @@ -// -// ASCollectionLayoutContext.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import - -#import -#import -#import -#import -#import -#import - -@implementation ASCollectionLayoutContext { - Class _layoutDelegateClass; - __weak ASCollectionLayoutCache *_layoutCache; -} - -- (instancetype)initWithViewportSize:(CGSize)viewportSize - initialContentOffset:(CGPoint)initialContentOffset - scrollableDirections:(ASScrollDirection)scrollableDirections - elements:(ASElementMap *)elements - layoutDelegateClass:(Class)layoutDelegateClass - layoutCache:(ASCollectionLayoutCache *)layoutCache - additionalInfo:(id)additionalInfo -{ - self = [super init]; - if (self) { - _viewportSize = viewportSize; - _initialContentOffset = initialContentOffset; - _scrollableDirections = scrollableDirections; - _elements = elements; - _layoutDelegateClass = layoutDelegateClass; - _layoutCache = layoutCache; - _additionalInfo = additionalInfo; - } - return self; -} - -- (Class)layoutDelegateClass -{ - return _layoutDelegateClass; -} - -- (ASCollectionLayoutCache *)layoutCache -{ - return _layoutCache; -} - -// NOTE: Some properties, like initialContentOffset and layoutCache are ignored in -isEqualToContext: and -hash. -// That is because contexts can be equal regardless of the content offsets or layout caches. -- (BOOL)isEqualToContext:(ASCollectionLayoutContext *)context -{ - if (context == nil) { - return NO; - } - - // NOTE: ASObjectIsEqual returns YES when both objects are nil. - // So don't use ASObjectIsEqual on _elements. - // It is a weak property and 2 layouts generated from different sets of elements - // should never be considered the same even if they are nil now. - return CGSizeEqualToSize(_viewportSize, context.viewportSize) - && _scrollableDirections == context.scrollableDirections - && [_elements isEqual:context.elements] - && _layoutDelegateClass == context.layoutDelegateClass - && ASObjectIsEqual(_additionalInfo, context.additionalInfo); -} - -- (BOOL)isEqual:(id)other -{ - if (self == other) { - return YES; - } - if (! [other isKindOfClass:[ASCollectionLayoutContext class]]) { - return NO; - } - return [self isEqualToContext:other]; -} - -- (NSUInteger)hash -{ - struct { - CGSize viewportSize; - ASScrollDirection scrollableDirections; - NSUInteger elementsHash; - NSUInteger layoutDelegateClassHash; - NSUInteger additionalInfoHash; - } data = { - _viewportSize, - _scrollableDirections, - _elements.hash, - _layoutDelegateClass.hash, - [_additionalInfo hash] - }; - return ASHashBytes(&data, sizeof(data)); -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutDefines.h b/submodules/AsyncDisplayKit/Source/ASCollectionLayoutDefines.h deleted file mode 100644 index 61a8ac4e88..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutDefines.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ASCollectionLayoutDefines.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -AS_EXTERN ASSizeRange ASSizeRangeForCollectionLayoutThatFitsViewportSize(CGSize viewportSize, ASScrollDirection scrollableDirections) AS_WARN_UNUSED_RESULT; - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutDefines.mm b/submodules/AsyncDisplayKit/Source/ASCollectionLayoutDefines.mm deleted file mode 100644 index 2071041edd..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutDefines.mm +++ /dev/null @@ -1,23 +0,0 @@ -// -// ASCollectionLayoutDefines.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASCollectionLayoutDefines.h" - -ASSizeRange ASSizeRangeForCollectionLayoutThatFitsViewportSize(CGSize viewportSize, ASScrollDirection scrollableDirections) -{ - ASSizeRange sizeRange = ASSizeRangeUnconstrained; - if (ASScrollDirectionContainsVerticalDirection(scrollableDirections) == NO) { - sizeRange.min.height = viewportSize.height; - sizeRange.max.height = viewportSize.height; - } - if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections) == NO) { - sizeRange.min.width = viewportSize.width; - sizeRange.max.width = viewportSize.width; - } - return sizeRange; -} diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutDelegate.h b/submodules/AsyncDisplayKit/Source/ASCollectionLayoutDelegate.h deleted file mode 100644 index d92a8c0ab7..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutDelegate.h +++ /dev/null @@ -1,63 +0,0 @@ -// -// ASCollectionLayoutDelegate.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 -#import -#import - -@class ASElementMap, ASCollectionLayoutContext, ASCollectionLayoutState; - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASCollectionLayoutDelegate - -/** - * @abstract Returns the scrollable directions of the coming layout (@see @c -calculateLayoutWithContext:). - * It will be available in the context parameter in +calculateLayoutWithContext: - * - * @return The scrollable directions. - * - * @discusstion This method will be called on main thread. - */ -- (ASScrollDirection)scrollableDirections; - -/** - * @abstract Returns any additional information needed for a coming layout pass (@see @c -calculateLayoutWithContext:) with the given elements. - * - * @param elements The elements to be laid out later. - * - * @discussion The returned object must support equality and hashing (i.e `-isEqual:` and `-hash` must be properly implemented). - * It should contain all the information needed for the layout pass to perform. It will be available in the context parameter in +calculateLayoutWithContext: - * - * This method will be called on main thread. - */ -- (nullable id)additionalInfoForLayoutWithElements:(ASElementMap *)elements; - -/** - * @abstract Prepares and returns a new layout for given context. - * - * @param context A context that contains all elements to be laid out and any additional information needed. - * - * @return The new layout calculated for the given context. - * - * @discussion This method is called ahead of time, i.e before the underlying collection/table view is aware of the provided elements. - * As a result, clients must solely rely on the given context and should not reach out to other objects for information not available in the context. - * - * This method can be called on background theads. It must be thread-safe and should not change any internal state of this delegate. - * It must block the calling thread but can dispatch to other theads to reduce total blocking time. - */ -+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutState+Private.h b/submodules/AsyncDisplayKit/Source/ASCollectionLayoutState+Private.h deleted file mode 100644 index c4d6013332..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutState+Private.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// ASCollectionLayoutState+Private.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ASCollectionLayoutState (Private) - -/** - * Remove and returns layout attributes for unmeasured elements that intersect the specified rect - * - * @discussion This method is atomic and thread-safe - */ -- (nullable ASPageToLayoutAttributesTable *)getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:(CGRect)rect; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutState.h b/submodules/AsyncDisplayKit/Source/ASCollectionLayoutState.h deleted file mode 100644 index 461f4b1296..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutState.h +++ /dev/null @@ -1,115 +0,0 @@ -// -// ASCollectionLayoutState.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 -#import -#import - -@class ASCollectionLayoutContext, ASLayout, ASCollectionElement; - -NS_ASSUME_NONNULL_BEGIN - -typedef ASCollectionElement * _Nullable (^ASCollectionLayoutStateGetElementBlock)(ASLayout *); - -@interface NSMapTable (ASCollectionLayoutConvenience) - -+ (NSMapTable *)elementToLayoutAttributesTable; - -@end - -AS_SUBCLASSING_RESTRICTED - -/// An immutable state of the collection layout -@interface ASCollectionLayoutState : NSObject - -/// The context used to calculate this object -@property (readonly) ASCollectionLayoutContext *context; - -/// The final content size of the collection's layout -@property (readonly) CGSize contentSize; - -- (instancetype)init NS_UNAVAILABLE; - -/** - * Designated initializer. - * - * @param context The context used to calculate this object - * - * @param contentSize The content size of the collection's layout - * - * @param table A map between elements to their layout attributes. It must contain all elements. - * It should have NSMapTableObjectPointerPersonality and NSMapTableWeakMemory as key options. - */ -- (instancetype)initWithContext:(ASCollectionLayoutContext *)context - contentSize:(CGSize)contentSize - elementToLayoutAttributesTable:(NSMapTable *)table NS_DESIGNATED_INITIALIZER; - -/** - * Convenience initializer. Returns an object with zero content size and an empty table. - * - * @param context The context used to calculate this object - */ -- (instancetype)initWithContext:(ASCollectionLayoutContext *)context; - -/** - * Convenience initializer. - * - * @param context The context used to calculate this object - * - * @param layout The layout describes size and position of all elements. - * - * @param getElementBlock A block that can retrieve the collection element from a sublayout of the root layout. - */ -- (instancetype)initWithContext:(ASCollectionLayoutContext *)context - layout:(ASLayout *)layout - getElementBlock:(ASCollectionLayoutStateGetElementBlock)getElementBlock; - -/** - * Returns all layout attributes present in this object. - */ -- (NSArray *)allLayoutAttributes; - -/** - * Returns layout attributes of elements in the specified rect. - * - * @param rect The rect containing the target elements. - */ -- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect; - -/** - * Returns layout attributes of the element at the specified index path. - * - * @param indexPath The index path of the item. - */ -- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Returns layout attributes of the specified supplementary element. - * - * @param kind A string that identifies the type of the supplementary element. - * - * @param indexPath The index path of the element. - */ -- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryElementOfKind:(NSString *)kind - atIndexPath:(NSIndexPath *)indexPath; - -/** - * Returns layout attributes of the specified element. - * - * @element The element. - */ -- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForElement:(ASCollectionElement *)element; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutState.mm b/submodules/AsyncDisplayKit/Source/ASCollectionLayoutState.mm deleted file mode 100644 index 2c09202c1b..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionLayoutState.mm +++ /dev/null @@ -1,259 +0,0 @@ -// -// ASCollectionLayoutState.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#import - -@implementation NSMapTable (ASCollectionLayoutConvenience) - -+ (NSMapTable *)elementToLayoutAttributesTable -{ - return [NSMapTable mapTableWithKeyOptions:(NSMapTableWeakMemory | NSMapTableObjectPointerPersonality) valueOptions:NSMapTableStrongMemory]; -} - -@end - -@implementation ASCollectionLayoutState { - AS::Mutex __instanceLock__; - CGSize _contentSize; - ASCollectionLayoutContext *_context; - NSMapTable *_elementToLayoutAttributesTable; - ASPageToLayoutAttributesTable *_pageToLayoutAttributesTable; - ASPageToLayoutAttributesTable *_unmeasuredPageToLayoutAttributesTable; -} - -- (instancetype)initWithContext:(ASCollectionLayoutContext *)context -{ - return [self initWithContext:context - contentSize:CGSizeZero -elementToLayoutAttributesTable:[NSMapTable elementToLayoutAttributesTable]]; -} - -- (instancetype)initWithContext:(ASCollectionLayoutContext *)context - layout:(ASLayout *)layout - getElementBlock:(ASCollectionLayoutStateGetElementBlock)getElementBlock -{ - ASElementMap *elements = context.elements; - NSMapTable *table = [NSMapTable elementToLayoutAttributesTable]; - - // Traverse the given layout tree in breadth first fashion. Generate layout attributes for all included elements along the way. - struct Context { - ASLayout *layout; - CGPoint absolutePosition; - }; - - std::queue queue; - queue.push({layout, CGPointZero}); - - while (!queue.empty()) { - Context context = queue.front(); - queue.pop(); - - ASLayout *layout = context.layout; - const CGPoint absolutePosition = context.absolutePosition; - - ASCollectionElement *element = getElementBlock(layout); - if (element != nil) { - NSIndexPath *indexPath = [elements indexPathForElement:element]; - NSString *supplementaryElementKind = element.supplementaryElementKind; - - UICollectionViewLayoutAttributes *attrs; - if (supplementaryElementKind == nil) { - attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; - } else { - attrs = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:supplementaryElementKind withIndexPath:indexPath]; - } - - CGRect frame = layout.frame; - frame.origin = absolutePosition; - attrs.frame = frame; - [table setObject:attrs forKey:element]; - } - - // Add all sublayouts to process in next step - for (ASLayout *sublayout in layout.sublayouts) { - queue.push({sublayout, absolutePosition + sublayout.position}); - } - } - - return [self initWithContext:context contentSize:layout.size elementToLayoutAttributesTable:table]; -} - -- (instancetype)initWithContext:(ASCollectionLayoutContext *)context - contentSize:(CGSize)contentSize - elementToLayoutAttributesTable:(NSMapTable *)table -{ - self = [super init]; - if (self) { - _context = context; - _contentSize = contentSize; - _elementToLayoutAttributesTable = [table copy]; // Copy the given table to make sure clients can't mutate it after this point. - CGSize pageSize = context.viewportSize; - _pageToLayoutAttributesTable = [ASPageTable pageTableWithLayoutAttributes:table.objectEnumerator contentSize:contentSize pageSize:pageSize]; - _unmeasuredPageToLayoutAttributesTable = [ASCollectionLayoutState _unmeasuredLayoutAttributesTableFromTable:table contentSize:contentSize pageSize:pageSize]; - } - return self; -} - -- (ASCollectionLayoutContext *)context -{ - return _context; -} - -- (CGSize)contentSize -{ - return _contentSize; -} - -- (NSArray *)allLayoutAttributes -{ - return [_elementToLayoutAttributesTable.objectEnumerator allObjects]; -} - -- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASCollectionElement *element = [_context.elements elementForItemAtIndexPath:indexPath]; - return [_elementToLayoutAttributesTable objectForKey:element]; -} - -- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryElementOfKind:(NSString *)elementKind - atIndexPath:(NSIndexPath *)indexPath -{ - ASCollectionElement *element = [_context.elements supplementaryElementOfKind:elementKind atIndexPath:indexPath]; - return [_elementToLayoutAttributesTable objectForKey:element]; -} - -- (UICollectionViewLayoutAttributes *)layoutAttributesForElement:(ASCollectionElement *)element -{ - return [_elementToLayoutAttributesTable objectForKey:element]; -} - -- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect -{ - CGSize pageSize = _context.viewportSize; - NSPointerArray *pages = ASPageCoordinatesForPagesThatIntersectRect(rect, _contentSize, pageSize); - if (pages.count == 0) { - return @[]; - } - - // Use a set here because some items may span multiple pages - const auto result = [[NSMutableSet alloc] init]; - for (id pagePtr in pages) { - ASPageCoordinate page = (ASPageCoordinate)pagePtr; - NSArray *allAttrs = [_pageToLayoutAttributesTable objectForPage:page]; - if (allAttrs.count > 0) { - CGRect pageRect = ASPageCoordinateGetPageRect(page, pageSize); - - if (CGRectContainsRect(rect, pageRect)) { - [result addObjectsFromArray:allAttrs]; - } else { - for (UICollectionViewLayoutAttributes *attrs in allAttrs) { - if (CGRectIntersectsRect(rect, attrs.frame)) { - [result addObject:attrs]; - } - } - } - } - } - - return [result allObjects]; -} - -- (ASPageToLayoutAttributesTable *)getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:(CGRect)rect -{ - CGSize pageSize = _context.viewportSize; - CGSize contentSize = _contentSize; - - AS::MutexLocker l(__instanceLock__); - if (_unmeasuredPageToLayoutAttributesTable.count == 0 || CGRectIsNull(rect) || CGRectIsEmpty(rect) || CGSizeEqualToSize(CGSizeZero, contentSize) || CGSizeEqualToSize(CGSizeZero, pageSize)) { - return nil; - } - - // Step 1: Determine all the pages that intersect the specified rect - NSPointerArray *pagesInRect = ASPageCoordinatesForPagesThatIntersectRect(rect, contentSize, pageSize); - if (pagesInRect.count == 0) { - return nil; - } - - // Step 2: Filter out attributes in these pages that intersect the specified rect. - ASPageToLayoutAttributesTable *result = nil; - for (id pagePtr in pagesInRect) { - ASPageCoordinate page = (ASPageCoordinate)pagePtr; - NSMutableArray *attrsInPage = [_unmeasuredPageToLayoutAttributesTable objectForPage:page]; - if (attrsInPage.count == 0) { - continue; - } - - NSMutableArray *intersectingAttrsInPage = nil; - CGRect pageRect = ASPageCoordinateGetPageRect(page, pageSize); - if (CGRectContainsRect(rect, pageRect)) { - // This page fits well within the specified rect. Simply return all of its attributes. - intersectingAttrsInPage = attrsInPage; - } else { - // The page intersects the specified rect. Some attributes in this page are returned, some are not. - for (UICollectionViewLayoutAttributes *attrs in attrsInPage) { - if (CGRectIntersectsRect(rect, attrs.frame)) { - if (intersectingAttrsInPage == nil) { - intersectingAttrsInPage = [[NSMutableArray alloc] init]; - } - [intersectingAttrsInPage addObject:attrs]; - } - } - } - - if (intersectingAttrsInPage.count > 0) { - if (attrsInPage.count == intersectingAttrsInPage.count) { - [_unmeasuredPageToLayoutAttributesTable removeObjectForPage:page]; - } else { - [attrsInPage removeObjectsInArray:intersectingAttrsInPage]; - } - if (result == nil) { - result = [ASPageTable pageTableForStrongObjectPointers]; - } - [result setObject:intersectingAttrsInPage forPage:page]; - } - } - - return result; -} - -#pragma mark - Private methods - -+ (ASPageToLayoutAttributesTable *)_unmeasuredLayoutAttributesTableFromTable:(NSMapTable *)table - contentSize:(CGSize)contentSize - pageSize:(CGSize)pageSize -{ - NSMutableArray *unmeasuredAttrs = [[NSMutableArray alloc] init]; - for (ASCollectionElement *element in table) { - UICollectionViewLayoutAttributes *attrs = [table objectForKey:element]; - if (element.nodeIfAllocated == nil || CGSizeEqualToSize(element.nodeIfAllocated.calculatedSize, attrs.frame.size) == NO) { - [unmeasuredAttrs addObject:attrs]; - } - } - - return [ASPageTable pageTableWithLayoutAttributes:unmeasuredAttrs contentSize:contentSize pageSize:pageSize]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionNode+Beta.h b/submodules/AsyncDisplayKit/Source/ASCollectionNode+Beta.h deleted file mode 100644 index 5aefbb8a76..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionNode+Beta.h +++ /dev/null @@ -1,82 +0,0 @@ -// -// 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 - -@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 layoutDelegate; - -@property (nullable, nonatomic, weak) id 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)layoutFacilitator; - -- (instancetype)initWithLayoutDelegate:(id)layoutDelegate layoutFacilitator:(nullable id)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 diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionNode.h b/submodules/AsyncDisplayKit/Source/ASCollectionNode.h deleted file mode 100644 index bbcd8befef..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionNode.h +++ /dev/null @@ -1,954 +0,0 @@ -// -// 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 -#import -#import -#import -#import -#import - -@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 - -- (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 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 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 node’s 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 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 node’s origin to the collection node’s 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 *)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 *)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 *)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 *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 *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)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 - -@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)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 *)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 - -@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 - -/** - * 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 - -@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 diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionNode.mm b/submodules/AsyncDisplayKit/Source/ASCollectionNode.mm deleted file mode 100644 index 60e39d57e5..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionNode.mm +++ /dev/null @@ -1,1056 +0,0 @@ -// -// ASCollectionNode.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 -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import "Private/ASInternalHelpers.h" -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#pragma mark - _ASCollectionPendingState - -@interface _ASCollectionPendingState : NSObject { -@public - std::vector> _tuningParameters; -} -@property (nonatomic, weak) id delegate; -@property (nonatomic, weak) id dataSource; -@property (nonatomic) UICollectionViewLayout *collectionViewLayout; -@property (nonatomic) ASLayoutRangeMode rangeMode; -@property (nonatomic) BOOL allowsSelection; // default is YES -@property (nonatomic) BOOL allowsMultipleSelection; // default is NO -@property (nonatomic) BOOL inverted; //default is NO -@property (nonatomic) ASCellLayoutMode cellLayoutMode; -@property (nonatomic) CGFloat leadingScreensForBatching; -@property (nonatomic, weak) id layoutInspector; -@property (nonatomic) BOOL alwaysBounceVertical; -@property (nonatomic) BOOL alwaysBounceHorizontal; -@property (nonatomic) UIEdgeInsets contentInset; -@property (nonatomic) CGPoint contentOffset; -@property (nonatomic) BOOL animatesContentOffset; -@property (nonatomic) BOOL showsVerticalScrollIndicator; -@property (nonatomic) BOOL showsHorizontalScrollIndicator; -@end - -@implementation _ASCollectionPendingState - -#pragma mark Lifecycle - -- (instancetype)init -{ - self = [super init]; - if (self) { - _rangeMode = ASLayoutRangeModeUnspecified; - _tuningParameters = [ASAbstractLayoutController defaultTuningParameters]; - _allowsSelection = YES; - _allowsMultipleSelection = NO; - _inverted = NO; - _contentInset = UIEdgeInsetsZero; - _contentOffset = CGPointZero; - _animatesContentOffset = NO; - _showsVerticalScrollIndicator = YES; - _showsHorizontalScrollIndicator = YES; - } - return self; -} - -#pragma mark Tuning Parameters - -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType -{ - return [self tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType -{ - return [self setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Requesting a range that is OOB for the configured tuning parameters"); - return _tuningParameters[rangeMode][rangeType]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Setting a range that is OOB for the configured tuning parameters"); - _tuningParameters[rangeMode][rangeType] = tuningParameters; -} - -@end - -#pragma mark - ASCollectionNode - -@interface ASCollectionNode () -{ - AS::RecursiveMutex _environmentStateLock; - Class _collectionViewClass; - id _batchFetchingDelegate; -} -@property (nonatomic) _ASCollectionPendingState *pendingState; -@property (nonatomic, weak) ASRangeController *rangeController; -@end - -@implementation ASCollectionNode - -#pragma mark Lifecycle - -- (Class)collectionViewClass -{ - return _collectionViewClass ? : [ASCollectionView class]; -} - -- (void)setCollectionViewClass:(Class)collectionViewClass -{ - if (_collectionViewClass != collectionViewClass) { - ASDisplayNodeAssert([collectionViewClass isSubclassOfClass:[ASCollectionView class]] || collectionViewClass == Nil, @"ASCollectionNode requires that .collectionViewClass is an ASCollectionView subclass"); - ASDisplayNodeAssert([self isNodeLoaded] == NO, @"ASCollectionNode's .collectionViewClass cannot be changed after the view is loaded"); - _collectionViewClass = collectionViewClass; - } -} - -- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout -{ - return [self initWithFrame:CGRectZero collectionViewLayout:layout layoutFacilitator:nil]; -} - -- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout -{ - return [self initWithFrame:frame collectionViewLayout:layout layoutFacilitator:nil]; -} - -- (instancetype)initWithLayoutDelegate:(id)layoutDelegate layoutFacilitator:(id)layoutFacilitator -{ - return [self initWithFrame:CGRectZero collectionViewLayout:[[ASCollectionLayout alloc] initWithLayoutDelegate:layoutDelegate] layoutFacilitator:layoutFacilitator]; -} - -- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id)layoutFacilitator -{ - if (self = [super init]) { - // Must call the setter here to make sure pendingState is created and the layout is configured. - [self setCollectionViewLayout:layout]; - - __weak __typeof__(self) weakSelf = self; - [self setViewBlock:^{ - __typeof__(self) strongSelf = weakSelf; - return [[[strongSelf collectionViewClass] alloc] _initWithFrame:frame collectionViewLayout:strongSelf->_pendingState.collectionViewLayout layoutFacilitator:layoutFacilitator owningNode:strongSelf eventLog:ASDisplayNodeGetEventLog(strongSelf)]; - }]; - } - return self; -} - -#if ASDISPLAYNODE_ASSERTIONS_ENABLED -- (void)dealloc -{ - if (self.nodeLoaded) { - __weak UIView *view = self.view; - ASPerformBlockOnMainThread(^{ - ASDisplayNodeCAssertNil(view.superview, @"Node's view should be removed from hierarchy."); - }); - } -} -#endif - -#pragma mark ASDisplayNode - -- (void)didLoad -{ - [super didLoad]; - - ASCollectionView *view = self.view; - view.collectionNode = self; - - _rangeController = view.rangeController; - - if (_pendingState) { - _ASCollectionPendingState *pendingState = _pendingState; - self.pendingState = nil; - view.asyncDelegate = pendingState.delegate; - view.asyncDataSource = pendingState.dataSource; - view.inverted = pendingState.inverted; - view.allowsSelection = pendingState.allowsSelection; - view.allowsMultipleSelection = pendingState.allowsMultipleSelection; - view.cellLayoutMode = pendingState.cellLayoutMode; - view.layoutInspector = pendingState.layoutInspector; - view.showsVerticalScrollIndicator = pendingState.showsVerticalScrollIndicator; - view.showsHorizontalScrollIndicator = pendingState.showsHorizontalScrollIndicator; - - // Only apply these flags if they're enabled; the view might come with them turned on. - if (pendingState.alwaysBounceVertical) { - view.alwaysBounceVertical = YES; - } - if (pendingState.alwaysBounceHorizontal) { - view.alwaysBounceHorizontal = YES; - } - - UIEdgeInsets contentInset = pendingState.contentInset; - if (!UIEdgeInsetsEqualToEdgeInsets(contentInset, UIEdgeInsetsZero)) { - view.contentInset = contentInset; - } - - CGPoint contentOffset = pendingState.contentOffset; - if (!CGPointEqualToPoint(contentOffset, CGPointZero)) { - [view setContentOffset:contentOffset animated:pendingState.animatesContentOffset]; - } - - const auto tuningParametersVector = pendingState->_tuningParameters; - const auto tuningParametersVectorSize = tuningParametersVector.size(); - for (NSInteger rangeMode = 0; rangeMode < tuningParametersVectorSize; rangeMode++) { - const auto tuningparametersRangeModeVector = tuningParametersVector[rangeMode]; - const auto tuningParametersVectorRangeModeSize = tuningparametersRangeModeVector.size(); - for (NSInteger rangeType = 0; rangeType < tuningParametersVectorRangeModeSize; rangeType++) { - ASRangeTuningParameters tuningParameters = tuningparametersRangeModeVector[rangeType]; - [_rangeController setTuningParameters:tuningParameters - forRangeMode:(ASLayoutRangeMode)rangeMode - rangeType:(ASLayoutRangeType)rangeType]; - } - } - - if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) { - [_rangeController updateCurrentRangeWithMode:pendingState.rangeMode]; - } - - // Don't need to set collectionViewLayout to the view as the layout was already used to init the view in view block. - } -} - -- (ASCollectionView *)view -{ - return (ASCollectionView *)[super view]; -} - -- (void)clearContents -{ - [super clearContents]; - [self.rangeController clearContents]; -} - -- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState -{ - [super interfaceStateDidChange:newState fromState:oldState]; - [ASRangeController layoutDebugOverlayIfNeeded]; -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - // ASCollectionNode is often nested inside of other collections. In this case, ASHierarchyState's RangeManaged bit will be set. - // Intentionally allocate the view here and trigger a layout pass on it, which in turn will trigger the intial data load. - // We can get rid of this call later when ASDataController, ASRangeController and ASCollectionLayout can operate without the view. - // TODO (ASCL) If this node supports async layout, kick off the initial data load without allocating the view - if (ASHierarchyStateIncludesRangeManaged(self.hierarchyState) && CGRectEqualToRect(self.bounds, CGRectZero) == NO) { - [self.view layoutIfNeeded]; - } -} - -#if ASRangeControllerLoggingEnabled -- (void)didEnterVisibleState -{ - [super didEnterVisibleState]; - NSLog(@"%@ - visible: YES", self); -} - -- (void)didExitVisibleState -{ - [super didExitVisibleState]; - NSLog(@"%@ - visible: NO", self); -} -#endif - -- (void)didExitPreloadState -{ - [super didExitPreloadState]; - [self.rangeController clearPreloadedData]; -} - -#pragma mark Setter / Getter - -// TODO: Implement this without the view. Then revisit ASLayoutElementCollectionTableSetTraitCollection -- (ASDataController *)dataController -{ - return self.view.dataController; -} - -- (_ASCollectionPendingState *)pendingState -{ - if (!_pendingState && ![self isNodeLoaded]) { - self.pendingState = [[_ASCollectionPendingState alloc] init]; - } - ASDisplayNodeAssert(![self isNodeLoaded] || !_pendingState, @"ASCollectionNode should not have a pendingState once it is loaded"); - return _pendingState; -} - -- (void)setInverted:(BOOL)inverted -{ - self.transform = inverted ? CATransform3DMakeScale(1, -1, 1) : CATransform3DIdentity; - if ([self pendingState]) { - _pendingState.inverted = inverted; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.inverted = inverted; - } -} - -- (BOOL)inverted -{ - if ([self pendingState]) { - return _pendingState.inverted; - } else { - return self.view.inverted; - } -} - -- (void)setLayoutInspector:(id)layoutInspector -{ - if ([self pendingState]) { - _pendingState.layoutInspector = layoutInspector; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.layoutInspector = layoutInspector; - } -} - -- (id)layoutInspector -{ - if ([self pendingState]) { - return _pendingState.layoutInspector; - } else { - return self.view.layoutInspector; - } -} - -- (void)setLeadingScreensForBatching:(CGFloat)leadingScreensForBatching -{ - if ([self pendingState]) { - _pendingState.leadingScreensForBatching = leadingScreensForBatching; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.leadingScreensForBatching = leadingScreensForBatching; - } -} - -- (CGFloat)leadingScreensForBatching -{ - if ([self pendingState]) { - return _pendingState.leadingScreensForBatching; - } else { - return self.view.leadingScreensForBatching; - } -} - -- (void)setDelegate:(id )delegate -{ - if ([self pendingState]) { - _pendingState.delegate = delegate; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - - // Manually trampoline to the main thread. The view requires this be called on main - // and asserting here isn't an option – it is a common pattern for users to clear - // the delegate/dataSource in dealloc, which may be running on a background thread. - // It is important that we avoid retaining self in this block, so that this method is dealloc-safe. - ASCollectionView *view = self.view; - ASPerformBlockOnMainThread(^{ - view.asyncDelegate = delegate; - }); - } -} - -- (id )delegate -{ - if ([self pendingState]) { - return _pendingState.delegate; - } else { - return self.view.asyncDelegate; - } -} - -- (void)setDataSource:(id )dataSource -{ - if ([self pendingState]) { - _pendingState.dataSource = dataSource; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - // Manually trampoline to the main thread. The view requires this be called on main - // and asserting here isn't an option – it is a common pattern for users to clear - // the delegate/dataSource in dealloc, which may be running on a background thread. - // It is important that we avoid retaining self in this block, so that this method is dealloc-safe. - ASCollectionView *view = self.view; - ASPerformBlockOnMainThread(^{ - view.asyncDataSource = dataSource; - }); - } -} - -- (id )dataSource -{ - if ([self pendingState]) { - return _pendingState.dataSource; - } else { - return self.view.asyncDataSource; - } -} - -- (void)setAllowsSelection:(BOOL)allowsSelection -{ - if ([self pendingState]) { - _pendingState.allowsSelection = allowsSelection; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.allowsSelection = allowsSelection; - } -} - -- (BOOL)allowsSelection -{ - if ([self pendingState]) { - return _pendingState.allowsSelection; - } else { - return self.view.allowsSelection; - } -} - -- (void)setAllowsMultipleSelection:(BOOL)allowsMultipleSelection -{ - if ([self pendingState]) { - _pendingState.allowsMultipleSelection = allowsMultipleSelection; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.allowsMultipleSelection = allowsMultipleSelection; - } -} - -- (BOOL)allowsMultipleSelection -{ - if ([self pendingState]) { - return _pendingState.allowsMultipleSelection; - } else { - return self.view.allowsMultipleSelection; - } -} - -- (void)setAlwaysBounceVertical:(BOOL)alwaysBounceVertical -{ - if ([self pendingState]) { - _pendingState.alwaysBounceVertical = alwaysBounceVertical; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.alwaysBounceVertical = alwaysBounceVertical; - } -} - -- (BOOL)alwaysBounceVertical -{ - if ([self pendingState]) { - return _pendingState.alwaysBounceVertical; - } else { - return self.view.alwaysBounceVertical; - } -} - -- (void)setAlwaysBounceHorizontal:(BOOL)alwaysBounceHorizontal -{ - if ([self pendingState]) { - _pendingState.alwaysBounceHorizontal = alwaysBounceHorizontal; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.alwaysBounceHorizontal = alwaysBounceHorizontal; - } -} - -- (BOOL)alwaysBounceHorizontal -{ - if ([self pendingState]) { - return _pendingState.alwaysBounceHorizontal; - } else { - return self.view.alwaysBounceHorizontal; - } -} - -- (void)setShowsVerticalScrollIndicator:(BOOL)showsVerticalScrollIndicator -{ - if ([self pendingState]) { - _pendingState.showsVerticalScrollIndicator = showsVerticalScrollIndicator; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.showsVerticalScrollIndicator = showsVerticalScrollIndicator; - } -} - -- (BOOL)showsVerticalScrollIndicator -{ - if ([self pendingState]) { - return _pendingState.showsVerticalScrollIndicator; - } else { - return self.view.showsVerticalScrollIndicator; - } -} - -- (void)setShowsHorizontalScrollIndicator:(BOOL)showsHorizontalScrollIndicator -{ - if ([self pendingState]) { - _pendingState.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator; - } -} - -- (BOOL)showsHorizontalScrollIndicator -{ - if ([self pendingState]) { - return _pendingState.showsHorizontalScrollIndicator; - } else { - return self.view.showsHorizontalScrollIndicator; - } -} - -- (void)setCollectionViewLayout:(UICollectionViewLayout *)layout -{ - if ([self pendingState]) { - [self _configureCollectionViewLayout:layout]; - _pendingState.collectionViewLayout = layout; - } else { - [self _configureCollectionViewLayout:layout]; - self.view.collectionViewLayout = layout; - } -} - -- (UICollectionViewLayout *)collectionViewLayout -{ - if ([self pendingState]) { - return _pendingState.collectionViewLayout; - } else { - return self.view.collectionViewLayout; - } -} - -- (void)setContentInset:(UIEdgeInsets)contentInset -{ - if ([self pendingState]) { - _pendingState.contentInset = contentInset; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - self.view.contentInset = contentInset; - } -} - -- (UIEdgeInsets)contentInset -{ - if ([self pendingState]) { - return _pendingState.contentInset; - } else { - return self.view.contentInset; - } -} - -- (void)setContentOffset:(CGPoint)contentOffset -{ - [self setContentOffset:contentOffset animated:NO]; -} - -- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated -{ - if ([self pendingState]) { - _pendingState.contentOffset = contentOffset; - _pendingState.animatesContentOffset = animated; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); - [self.view setContentOffset:contentOffset animated:animated]; - } -} - -- (CGPoint)contentOffset -{ - if ([self pendingState]) { - return _pendingState.contentOffset; - } else { - return self.view.contentOffset; - } -} - -- (ASScrollDirection)scrollDirection -{ - return [self isNodeLoaded] ? self.view.scrollDirection : ASScrollDirectionNone; -} - -- (ASScrollDirection)scrollableDirections -{ - return [self isNodeLoaded] ? self.view.scrollableDirections : ASScrollDirectionNone; -} - -- (ASElementMap *)visibleElements -{ - ASDisplayNodeAssertMainThread(); - // TODO Own the data controller when view is not yet loaded - return self.dataController.visibleMap; -} - -- (id)layoutDelegate -{ - UICollectionViewLayout *layout = self.collectionViewLayout; - if ([layout isKindOfClass:[ASCollectionLayout class]]) { - return ((ASCollectionLayout *)layout).layoutDelegate; - } - return nil; -} - -- (void)setBatchFetchingDelegate:(id)batchFetchingDelegate -{ - _batchFetchingDelegate = batchFetchingDelegate; -} - -- (id)batchFetchingDelegate -{ - return _batchFetchingDelegate; -} - -- (ASCellLayoutMode)cellLayoutMode -{ - if ([self pendingState]) { - return _pendingState.cellLayoutMode; - } else { - return self.view.cellLayoutMode; - } -} - -- (void)setCellLayoutMode:(ASCellLayoutMode)cellLayoutMode -{ - if ([self pendingState]) { - _pendingState.cellLayoutMode = cellLayoutMode; - } else { - self.view.cellLayoutMode = cellLayoutMode; - } -} - -#pragma mark - Range Tuning - -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType -{ - return [self tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType -{ - [self setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - if ([self pendingState]) { - return [_pendingState tuningParametersForRangeMode:rangeMode rangeType:rangeType]; - } else { - return [self.rangeController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; - } -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - if ([self pendingState]) { - [_pendingState setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; - } else { - return [self.rangeController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; - } -} - -#pragma mark - Selection - -- (NSArray *)indexPathsForSelectedItems -{ - ASDisplayNodeAssertMainThread(); - ASCollectionView *view = self.view; - return [view convertIndexPathsToCollectionNode:view.indexPathsForSelectedItems]; -} - -- (void)selectItemAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition -{ - ASDisplayNodeAssertMainThread(); - ASCollectionView *collectionView = self.view; - - indexPath = [collectionView convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:YES]; - - if (indexPath != nil) { - [collectionView selectItemAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition]; - } else { - NSLog(@"Failed to select item at index path %@ because the item never reached the view.", indexPath); - } -} - -- (void)deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated -{ - ASDisplayNodeAssertMainThread(); - ASCollectionView *collectionView = self.view; - - indexPath = [collectionView convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:YES]; - - if (indexPath != nil) { - [collectionView deselectItemAtIndexPath:indexPath animated:animated]; - } else { - NSLog(@"Failed to deselect item at index path %@ because the item never reached the view.", indexPath); - } -} - -- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated -{ - ASDisplayNodeAssertMainThread(); - ASCollectionView *collectionView = self.view; - - indexPath = [collectionView convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:YES]; - - if (indexPath != nil) { - [collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated]; - } else { - NSLog(@"Failed to scroll to item at index path %@ because the item never reached the view.", indexPath); - } -} - -#pragma mark - Querying Data - -- (void)reloadDataInitiallyIfNeeded -{ - if (!self.dataController.initialReloadDataHasBeenCalled) { - [self reloadData]; - } -} - -- (NSInteger)numberOfItemsInSection:(NSInteger)section -{ - [self reloadDataInitiallyIfNeeded]; - return [self.dataController.pendingMap numberOfItemsInSection:section]; -} - -- (NSInteger)numberOfSections -{ - [self reloadDataInitiallyIfNeeded]; - return self.dataController.pendingMap.numberOfSections; -} - -- (NSArray<__kindof ASCellNode *> *)visibleNodes -{ - ASDisplayNodeAssertMainThread(); - return self.isNodeLoaded ? [self.view visibleNodes] : @[]; -} - -- (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath -{ - [self reloadDataInitiallyIfNeeded]; - return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].node; -} - -- (id)nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath -{ - [self reloadDataInitiallyIfNeeded]; - return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].nodeModel; -} - -- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode -{ - return [self.dataController.pendingMap indexPathForElement:cellNode.collectionElement]; -} - -- (NSArray *)indexPathsForVisibleItems -{ - ASDisplayNodeAssertMainThread(); - NSMutableArray *indexPathsArray = [NSMutableArray new]; - for (ASCellNode *cell in [self visibleNodes]) { - NSIndexPath *indexPath = [self indexPathForNode:cell]; - if (indexPath) { - [indexPathsArray addObject:indexPath]; - } - } - return indexPathsArray; -} - -- (nullable NSIndexPath *)indexPathForItemAtPoint:(CGPoint)point -{ - ASDisplayNodeAssertMainThread(); - ASCollectionView *collectionView = self.view; - - NSIndexPath *indexPath = [collectionView indexPathForItemAtPoint:point]; - if (indexPath != nil) { - return [collectionView convertIndexPathToCollectionNode:indexPath]; - } - return indexPath; -} - -- (nullable UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - ASCollectionView *collectionView = self.view; - - indexPath = [collectionView convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:YES]; - if (indexPath == nil) { - return nil; - } - return [collectionView cellForItemAtIndexPath:indexPath]; -} - -- (id)contextForSection:(NSInteger)section -{ - ASDisplayNodeAssertMainThread(); - return [self.dataController.pendingMap contextForSection:section]; -} - -#pragma mark - Editing - -- (void)registerSupplementaryNodeOfKind:(NSString *)elementKind -{ - [self.view registerSupplementaryNodeOfKind:elementKind]; -} - -- (void)performBatchAnimated:(BOOL)animated updates:(NS_NOESCAPE void (^)())updates completion:(void (^)(BOOL))completion -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view performBatchAnimated:animated updates:updates completion:completion]; - } else { - if (updates) { - updates(); - } - if (completion) { - completion(YES); - } - } -} - -- (void)performBatchUpdates:(NS_NOESCAPE void (^)())updates completion:(void (^)(BOOL))completion -{ - [self performBatchAnimated:UIView.areAnimationsEnabled updates:updates completion:completion]; -} - -- (BOOL)isProcessingUpdates -{ - return (self.nodeLoaded ? [self.view isProcessingUpdates] : NO); -} - -- (void)onDidFinishProcessingUpdates:(void (^)())completion -{ - if (!completion) { - return; - } - if (!self.nodeLoaded) { - completion(); - } else { - [self.view onDidFinishProcessingUpdates:completion]; - } -} - -- (BOOL)isSynchronized -{ - return (self.nodeLoaded ? [self.view isSynchronized] : YES); -} - -- (void)onDidFinishSynchronizing:(void (^)())completion -{ - if (!completion) { - return; - } - if (!self.nodeLoaded) { - completion(); - } else { - [self.view onDidFinishSynchronizing:completion]; - } -} - -- (void)waitUntilAllUpdatesAreProcessed -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view waitUntilAllUpdatesAreCommitted]; - } -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)waitUntilAllUpdatesAreCommitted -{ - [self waitUntilAllUpdatesAreProcessed]; -} -#pragma clang diagnostic pop - -- (void)reloadDataWithCompletion:(void (^)())completion -{ - ASDisplayNodeAssertMainThread(); - if (!self.nodeLoaded) { - return; - } - - [self performBatchUpdates:^{ - [self.view.changeSet reloadData]; - } completion:^(BOOL finished){ - if (completion) { - completion(); - } - }]; -} - -- (void)reloadData -{ - [self reloadDataWithCompletion:nil]; -} - -- (void)relayoutItems -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view relayoutItems]; - } -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)beginUpdates -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view beginUpdates]; - } -} - -- (void)endUpdatesAnimated:(BOOL)animated -{ - [self endUpdatesAnimated:animated completion:nil]; -} - -- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view endUpdatesAnimated:animated completion:completion]; - } -} -#pragma clang diagnostic pop - -- (void)invalidateFlowLayoutDelegateMetrics { - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view invalidateFlowLayoutDelegateMetrics]; - } -} - -- (void)insertSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view insertSections:sections]; - } -} - -- (void)deleteSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view deleteSections:sections]; - } -} - -- (void)reloadSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view reloadSections:sections]; - } -} - -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view moveSection:section toSection:newSection]; - } -} - -- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view insertItemsAtIndexPaths:indexPaths]; - } -} - -- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view deleteItemsAtIndexPaths:indexPaths]; - } -} - -- (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view reloadItemsAtIndexPaths:indexPaths]; - } -} - -- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view moveItemAtIndexPath:indexPath toIndexPath:newIndexPath]; - } -} - -#pragma mark - ASRangeControllerUpdateRangeProtocol - -- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; -{ - if ([self pendingState]) { - _pendingState.rangeMode = rangeMode; - } else { - [self.rangeController updateCurrentRangeWithMode:rangeMode]; - } -} - -#pragma mark - ASPrimitiveTraitCollection - -ASLayoutElementCollectionTableSetTraitCollection(_environmentStateLock) - -#pragma mark - Debugging (Private) - -- (NSMutableArray *)propertiesForDebugDescription -{ - NSMutableArray *result = [super propertiesForDebugDescription]; - [result addObject:@{ @"dataSource" : ASObjectDescriptionMakeTiny(self.dataSource) }]; - [result addObject:@{ @"delegate" : ASObjectDescriptionMakeTiny(self.delegate) }]; - return result; -} - -#pragma mark - Private methods - -- (void)_configureCollectionViewLayout:(UICollectionViewLayout *)layout -{ - if ([layout isKindOfClass:[ASCollectionLayout class]]) { - ASCollectionLayout *collectionLayout = (ASCollectionLayout *)layout; - collectionLayout.collectionNode = self; - } -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionView+Undeprecated.h b/submodules/AsyncDisplayKit/Source/ASCollectionView+Undeprecated.h deleted file mode 100644 index 050e029313..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionView+Undeprecated.h +++ /dev/null @@ -1,303 +0,0 @@ -// -// ASCollectionView+Undeprecated.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 - -NS_ASSUME_NONNULL_BEGIN - -/** - * Currently our public collection API is on @c ASCollectionNode and the @c ASCollectionView - * API is deprecated, but the implementations still live in the view. - * - * This category lets us avoid deprecation warnings everywhere internally. - * In the future, the @c ASCollectionView public API will be eliminated and so will this file. - */ -@interface ASCollectionView (Undeprecated) - -/** - * 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 asyncDelegate; - -/** - * 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 asyncDataSource; - -/** - * 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; - -/** - * 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; - -@property (nonatomic) CGFloat leadingScreensForBatching; - -@property (nonatomic) BOOL inverted; - -@property (nonatomic, readonly) ASScrollDirection scrollDirection; - -@property (nonatomic, readonly) ASScrollDirection scrollableDirections; - -@property (nonatomic, weak) id layoutInspector; - -@property (nonatomic) UIEdgeInsets contentInset; - -@property (nonatomic) CGPoint contentOffset; - -/** - * 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; - -- (nullable __kindof UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath; - -@property (nonatomic, readonly) NSArray *indexPathsForVisibleItems; - -@property (nonatomic, readonly, nullable) NSArray *indexPathsForSelectedItems; - -/** - * Scrolls the collection to the given item. - * - * @param indexPath The index path of the item. - * @param scrollPosition Where the row should end up after the scroll. - * @param animated Whether the scroll should be animated or not. - */ -- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated; - -- (void)selectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition; - -/** - * 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; - -/** - * 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; - -/** - * Triggers a relayout of all nodes. - * - * @discussion This method invalidates and lays out every cell node in the collection. - */ -- (void)relayoutItems; - -/** - * Blocks execution of the main thread until all section and row updates are committed. This method must be called from the main thread. - */ -- (void)waitUntilAllUpdatesAreCommitted; - -/** - * 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; - -/** - * 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; - -/** - * 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; - -/** - * 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; - -/** - * 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; - -/** - * 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 *)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 asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)deleteItemsAtIndexPaths:(NSArray *)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 asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)reloadItemsAtIndexPaths:(NSArray *)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 asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath; - -/** - * Similar to -visibleCells. - * - * @return an array containing the nodes being displayed on screen. - */ -- (NSArray<__kindof ASCellNode *> *)visibleNodes 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; - -/** - * Invalidates and recalculates the cached sizes stored for pass-through cells used in interop mode. - */ -- (void)invalidateFlowLayoutDelegateMetrics; - -- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionView.h b/submodules/AsyncDisplayKit/Source/ASCollectionView.h deleted file mode 100644 index 740efc41d9..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionView.h +++ /dev/null @@ -1,496 +0,0 @@ -// -// 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 - -#import -#import -#import -#import -#import -#import - -@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)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 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 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 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 *indexPathsForVisibleItems ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead."); - -@property (nullable, nonatomic, copy, readonly) NSArray *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 *)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 *)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 *)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 -@end - -ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDelegate.") -@protocol ASCollectionViewDelegate -@end - -/** - * Defines methods that let you coordinate a `UICollectionViewFlowLayout` in combination with an `ASCollectionNode`. - */ -@protocol ASCollectionDelegateFlowLayout - -@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 -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionView.mm b/submodules/AsyncDisplayKit/Source/ASCollectionView.mm deleted file mode 100644 index 6883b8dd3d..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionView.mm +++ /dev/null @@ -1,2521 +0,0 @@ -// -// ASCollectionView.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 -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import "Private/ASInternalHelpers.h" -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -/** - * A macro to get self.collectionNode and assign it to a local variable, or return - * the given value if nil. - * - * Previously we would set ASCollectionNode's dataSource & delegate to nil - * during dealloc. However, our asyncDelegate & asyncDataSource must be set on the - * main thread, so if the node is deallocated off-main, we won't learn about the change - * until later on. Since our @c collectionNode parameter to delegate methods (e.g. - * collectionNode:didEndDisplayingItemWithNode:) is nonnull, it's important that we never - * unintentionally pass nil (this will crash in Swift, in production). So we can use - * this macro to ensure that our node is still alive before calling out to the user - * on its behalf. - */ -#define GET_COLLECTIONNODE_OR_RETURN(__var, __val) \ - ASCollectionNode *__var = self.collectionNode; \ - if (__var == nil) { \ - return __val; \ - } - -#define ASFlowLayoutDefault(layout, property, default) \ -({ \ - UICollectionViewFlowLayout *flowLayout = ASDynamicCast(layout, UICollectionViewFlowLayout); \ - flowLayout ? flowLayout.property : default; \ -}) - -// ASCellLayoutMode is an NSUInteger-based NS_OPTIONS field. Be careful with BOOL handling on the -// 32-bit Objective-C runtime, and pattern after ASInterfaceStateIncludesVisible() & friends. -#define ASCellLayoutModeIncludes(layoutMode) ((_cellLayoutMode & layoutMode) == layoutMode) - -/// What, if any, invalidation should we perform during the next -layoutSubviews. -typedef NS_ENUM(NSUInteger, ASCollectionViewInvalidationStyle) { - /// Perform no invalidation. - ASCollectionViewInvalidationStyleNone, - /// Perform invalidation with animation (use an empty batch update). - ASCollectionViewInvalidationStyleWithoutAnimation, - /// Perform invalidation without animation (use -invalidateLayout). - ASCollectionViewInvalidationStyleWithAnimation, -}; - -static const NSUInteger kASCollectionViewAnimationNone = UITableViewRowAnimationNone; - -/// Used for all cells and supplementaries. UICV keys by supp-kind+reuseID so this is plenty. -static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; - -#pragma mark - -#pragma mark ASCollectionView. - -@interface ASCollectionView () { - ASCollectionViewProxy *_proxyDataSource; - ASCollectionViewProxy *_proxyDelegate; - - ASDataController *_dataController; - ASRangeController *_rangeController; - ASCollectionViewLayoutController *_layoutController; - id _defaultLayoutInspector; - __weak id _layoutInspector; - NSHashTable<_ASCollectionViewCell *> *_cellsForVisibilityUpdates; - NSHashTable *_cellsForLayoutUpdates; - id _layoutFacilitator; - CGFloat _leadingScreensForBatching; - - // When we update our data controller in response to an interactive move, - // we don't want to tell the collection view about the change (it knows!) - BOOL _updatingInResponseToInteractiveMove; - BOOL _inverted; - - NSUInteger _superBatchUpdateCount; - BOOL _isDeallocating; - - ASBatchContext *_batchContext; - - CGSize _lastBoundsSizeUsedForMeasuringNodes; - - NSMutableSet *_registeredSupplementaryKinds; - - // CountedSet because UIKit may display the same element in multiple cells e.g. during animations. - NSCountedSet *_visibleElements; - - CGPoint _deceleratingVelocity; - - BOOL _zeroContentInsets; - - ASCollectionViewInvalidationStyle _nextLayoutInvalidationStyle; - - /** - * If YES, the `UICollectionView` will reload its data on next layout pass so we should not forward any updates to it. - - * Rationale: - * In `reloadData`, a collection view invalidates its data and marks itself as needing reload, and waits until `layoutSubviews` to requery its data source. - * This can lead to data inconsistency problems. - * Say you have an empty collection view. You call `reloadData`, then immediately insert an item into your data source and call `insertItemsAtIndexPaths:[0,0]`. - * You will get an assertion failure saying `Invalid number of items in section 0. - * The number of items after the update (1) must be equal to the number of items before the update (1) plus or minus the items added and removed (1 added, 0 removed).` - * The collection view never queried your data source before the update to see that it actually had 0 items. - */ - BOOL _superIsPendingDataLoad; - - /** - * It's important that we always check for batch fetching at least once, but also - * that we do not check for batch fetching for empty updates (as that may cause an infinite - * loop of batch fetching, where the batch completes and performBatchUpdates: is called without - * actually making any changes.) So to handle the case where a collection is completely empty - * (0 sections) we always check at least once after each update (initial reload is the first update.) - */ - BOOL _hasEverCheckedForBatchFetchingDueToUpdate; - - /** - * Set during beginInteractiveMovementForItemAtIndexPath and UIGestureRecognizerStateEnded - * (or UIGestureRecognizerStateFailed, UIGestureRecognizerStateCancelled. - */ - BOOL _reordering; - - /** - * Counter used to keep track of nested batch updates. - */ - NSInteger _batchUpdateCount; - - /** - * Keep a strong reference to node till view is ready to release. - */ - ASCollectionNode *_keepalive_node; - - struct { - unsigned int scrollViewDidScroll:1; - unsigned int scrollViewWillBeginDragging:1; - unsigned int scrollViewDidEndDragging:1; - unsigned int scrollViewWillEndDragging:1; - unsigned int scrollViewDidEndDecelerating:1; - unsigned int collectionViewWillDisplayNodeForItem:1; - unsigned int collectionViewWillDisplayNodeForItemDeprecated:1; - unsigned int collectionViewDidEndDisplayingNodeForItem:1; - unsigned int collectionViewShouldSelectItem:1; - unsigned int collectionViewDidSelectItem:1; - unsigned int collectionViewShouldDeselectItem:1; - unsigned int collectionViewDidDeselectItem:1; - unsigned int collectionViewShouldHighlightItem:1; - unsigned int collectionViewDidHighlightItem:1; - unsigned int collectionViewDidUnhighlightItem:1; - unsigned int collectionViewShouldShowMenuForItem:1; - unsigned int collectionViewCanPerformActionForItem:1; - unsigned int collectionViewPerformActionForItem:1; - unsigned int collectionViewWillBeginBatchFetch:1; - unsigned int shouldBatchFetchForCollectionView:1; - unsigned int collectionNodeWillDisplayItem:1; - unsigned int collectionNodeDidEndDisplayingItem:1; - unsigned int collectionNodeShouldSelectItem:1; - unsigned int collectionNodeDidSelectItem:1; - unsigned int collectionNodeShouldDeselectItem:1; - unsigned int collectionNodeDidDeselectItem:1; - unsigned int collectionNodeShouldHighlightItem:1; - unsigned int collectionNodeDidHighlightItem:1; - unsigned int collectionNodeDidUnhighlightItem:1; - unsigned int collectionNodeShouldShowMenuForItem:1; - unsigned int collectionNodeCanPerformActionForItem:1; - unsigned int collectionNodePerformActionForItem:1; - unsigned int collectionNodeWillBeginBatchFetch:1; - unsigned int collectionNodeWillDisplaySupplementaryElement:1; - unsigned int collectionNodeDidEndDisplayingSupplementaryElement:1; - unsigned int shouldBatchFetchForCollectionNode:1; - - // Interop flags - unsigned int interop:1; - unsigned int interopWillDisplayCell:1; - unsigned int interopDidEndDisplayingCell:1; - unsigned int interopWillDisplaySupplementaryView:1; - unsigned int interopdidEndDisplayingSupplementaryView:1; - } _asyncDelegateFlags; - - struct { - unsigned int collectionViewNodeForItem:1; - unsigned int collectionViewNodeBlockForItem:1; - unsigned int collectionViewNodeForSupplementaryElement:1; - unsigned int numberOfSectionsInCollectionView:1; - unsigned int collectionViewNumberOfItemsInSection:1; - unsigned int collectionNodeNodeForItem:1; - unsigned int collectionNodeNodeBlockForItem:1; - unsigned int nodeModelForItem:1; - unsigned int collectionNodeNodeForSupplementaryElement:1; - unsigned int collectionNodeNodeBlockForSupplementaryElement:1; - unsigned int collectionNodeSupplementaryElementKindsInSection:1; - unsigned int numberOfSectionsInCollectionNode:1; - unsigned int collectionNodeNumberOfItemsInSection:1; - unsigned int collectionNodeContextForSection:1; - unsigned int collectionNodeCanMoveItem:1; - unsigned int collectionNodeMoveItem:1; - - // Whether this data source conforms to ASCollectionDataSourceInterop - unsigned int interop:1; - // Whether this interop data source returns YES from +dequeuesCellsForNodeBackedItems - unsigned int interopAlwaysDequeue:1; - // Whether this interop data source implements viewForSupplementaryElementOfKind: - unsigned int interopViewForSupplementaryElement:1; - unsigned int modelIdentifierMethods:1; // if both modelIdentifierForElementAtIndexPath and indexPathForElementWithModelIdentifier are implemented - } _asyncDataSourceFlags; - - struct { - unsigned int constrainedSizeForSupplementaryNodeOfKindAtIndexPath:1; - unsigned int supplementaryNodesOfKindInSection:1; - unsigned int didChangeCollectionViewDataSource:1; - unsigned int didChangeCollectionViewDelegate:1; - } _layoutInspectorFlags; - - BOOL _hasDataControllerLayoutDelegate; -} - -@end - -@implementation ASCollectionView -{ - __weak id _asyncDelegate; - __weak id _asyncDataSource; -} - -// Using _ASDisplayLayer ensures things like -layout are properly forwarded to ASCollectionNode. -+ (Class)layerClass -{ - return [_ASDisplayLayer class]; -} - -#pragma mark - -#pragma mark Lifecycle. - -- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout -{ - return [self initWithFrame:CGRectZero collectionViewLayout:layout]; -} - -- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout -{ - return [self _initWithFrame:frame collectionViewLayout:layout layoutFacilitator:nil owningNode:nil eventLog:nil]; -} - -- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id)layoutFacilitator owningNode:(ASCollectionNode *)owningNode eventLog:(ASEventLog *)eventLog -{ - if (!(self = [super initWithFrame:frame collectionViewLayout:layout])) - return nil; - - // Disable UICollectionView prefetching. Use super, because self disables this method. - // Experiments done by Instagram show that this option being YES (default) - // when unused causes a significant hit to scroll performance. - // https://github.com/Instagram/IGListKit/issues/318 - if (AS_AVAILABLE_IOS_TVOS(10, 10)) { - super.prefetchingEnabled = NO; - } - - _layoutController = [[ASCollectionViewLayoutController alloc] initWithCollectionView:self]; - - _rangeController = [[ASRangeController alloc] init]; - _rangeController.dataSource = self; - _rangeController.delegate = self; - _rangeController.layoutController = _layoutController; - - _dataController = [[ASDataController alloc] initWithDataSource:self node:owningNode eventLog:eventLog]; - _dataController.delegate = _rangeController; - - _batchContext = [[ASBatchContext alloc] init]; - - _leadingScreensForBatching = 2.0; - - _lastBoundsSizeUsedForMeasuringNodes = self.bounds.size; - - _layoutFacilitator = layoutFacilitator; - - _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; - super.delegate = (id)_proxyDelegate; - - _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; - super.dataSource = (id)_proxyDataSource; - - _registeredSupplementaryKinds = [[NSMutableSet alloc] init]; - _visibleElements = [[NSCountedSet alloc] init]; - - _cellsForVisibilityUpdates = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; - _cellsForLayoutUpdates = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; - self.backgroundColor = [UIColor whiteColor]; - - [self registerClass:[_ASCollectionViewCell class] forCellWithReuseIdentifier:kReuseIdentifier]; - - [self _configureCollectionViewLayout:layout]; - - return self; -} - -- (void)dealloc -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeCAssert(_batchUpdateCount == 0, @"ASCollectionView deallocated in the middle of a batch update."); - - // Sometimes the UIKit classes can call back to their delegate even during deallocation, due to animation completion blocks etc. - _isDeallocating = YES; - if (!ASActivateExperimentalFeature(ASExperimentalCollectionTeardown)) { - [self setAsyncDelegate:nil]; - [self setAsyncDataSource:nil]; - } - - // Data controller & range controller may own a ton of nodes, let's deallocate those off-main. - ASPerformBackgroundDeallocation(&_dataController); - ASPerformBackgroundDeallocation(&_rangeController); -} - -#pragma mark - -#pragma mark Overrides. - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -/** - * This method is not available to be called by the public i.e. - * it should only be called by UICollectionView itself. UICollectionView - * does this e.g. during the first layout pass, or if you call -numberOfSections - * before its content is loaded. - */ -- (void)reloadData -{ - [self _superReloadData:nil completion:nil]; - - // UICollectionView calls -reloadData during first layoutSubviews and when the data source changes. - // This fires off the first load of cell nodes. - if (_asyncDataSource != nil && !self.dataController.initialReloadDataHasBeenCalled) { - [self performBatchUpdates:^{ - [_changeSet reloadData]; - } completion:nil]; - } -} -#pragma clang diagnostic pop - -- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated -{ - if ([self validateIndexPath:indexPath]) { - [super scrollToItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated]; - } -} - -- (void)relayoutItems -{ - [_dataController relayoutAllNodesWithInvalidationBlock:^{ - [self.collectionViewLayout invalidateLayout]; - [self invalidateFlowLayoutDelegateMetrics]; - }]; -} - -- (BOOL)isProcessingUpdates -{ - return [_dataController isProcessingUpdates]; -} - -- (void)onDidFinishProcessingUpdates:(void (^)())completion -{ - [_dataController onDidFinishProcessingUpdates:completion]; -} - -- (void)waitUntilAllUpdatesAreCommitted -{ - ASDisplayNodeAssertMainThread(); - if (_batchUpdateCount > 0) { - // This assertion will be enabled soon. - // ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd)); - return; - } - - [_dataController waitUntilAllUpdatesAreProcessed]; -} - -- (BOOL)isSynchronized -{ - return [_dataController isSynchronized]; -} - -- (void)onDidFinishSynchronizing:(void (^)())completion -{ - [_dataController onDidFinishSynchronizing:completion]; -} - -- (void)setDataSource:(id)dataSource -{ - // UIKit can internally generate a call to this method upon changing the asyncDataSource; only assert for non-nil. We also allow this when we're doing interop. - ASDisplayNodeAssert(_asyncDelegateFlags.interop || dataSource == nil, @"ASCollectionView uses asyncDataSource, not UICollectionView's dataSource property."); -} - -- (void)setDelegate:(id)delegate -{ - // Our UIScrollView superclass sets its delegate to nil on dealloc. Only assert if we get a non-nil value here. We also allow this when we're doing interop. - ASDisplayNodeAssert(_asyncDelegateFlags.interop || delegate == nil, @"ASCollectionView uses asyncDelegate, not UICollectionView's delegate property."); -} - -- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy -{ - if (proxy == _proxyDelegate) { - [self setAsyncDelegate:nil]; - } else if (proxy == _proxyDataSource) { - [self setAsyncDataSource:nil]; - } -} - -- (id)asyncDataSource -{ - return _asyncDataSource; -} - -- (void)setAsyncDataSource:(id)asyncDataSource -{ - // Changing super.dataSource will trigger a setNeedsLayout, so this must happen on the main thread. - ASDisplayNodeAssertMainThread(); - - // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle - // the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource - // will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to hold a strong - // reference to the old dataSource in this case because calls to ASCollectionViewProxy will start failing and cause crashes. - NS_VALID_UNTIL_END_OF_SCOPE id oldDataSource = super.dataSource; - - if (asyncDataSource == nil) { - _asyncDataSource = nil; - _proxyDataSource = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; - _asyncDataSourceFlags = {}; - - } else { - _asyncDataSource = asyncDataSource; - _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; - - _asyncDataSourceFlags.collectionViewNodeForItem = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)]; - _asyncDataSourceFlags.collectionViewNodeBlockForItem = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)]; - _asyncDataSourceFlags.numberOfSectionsInCollectionView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]; - _asyncDataSourceFlags.collectionViewNumberOfItemsInSection = [_asyncDataSource respondsToSelector:@selector(collectionView:numberOfItemsInSection:)]; - _asyncDataSourceFlags.collectionViewNodeForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForSupplementaryElementOfKind:atIndexPath:)]; - - _asyncDataSourceFlags.collectionNodeNodeForItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeForItemAtIndexPath:)]; - _asyncDataSourceFlags.collectionNodeNodeBlockForItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeBlockForItemAtIndexPath:)]; - _asyncDataSourceFlags.numberOfSectionsInCollectionNode = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionNode:)]; - _asyncDataSourceFlags.collectionNodeNumberOfItemsInSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:numberOfItemsInSection:)]; - _asyncDataSourceFlags.collectionNodeContextForSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:contextForSection:)]; - _asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeForSupplementaryElementOfKind:atIndexPath:)]; - _asyncDataSourceFlags.collectionNodeNodeBlockForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeBlockForSupplementaryElementOfKind:atIndexPath:)]; - _asyncDataSourceFlags.collectionNodeSupplementaryElementKindsInSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:supplementaryElementKindsInSection:)]; - _asyncDataSourceFlags.nodeModelForItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeModelForItemAtIndexPath:)]; - _asyncDataSourceFlags.collectionNodeCanMoveItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:canMoveItemWithNode:)]; - _asyncDataSourceFlags.collectionNodeMoveItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:moveItemAtIndexPath:toIndexPath:)]; - - _asyncDataSourceFlags.interop = [_asyncDataSource conformsToProtocol:@protocol(ASCollectionDataSourceInterop)]; - if (_asyncDataSourceFlags.interop) { - id interopDataSource = (id)_asyncDataSource; - _asyncDataSourceFlags.interopAlwaysDequeue = [[interopDataSource class] respondsToSelector:@selector(dequeuesCellsForNodeBackedItems)] && [[interopDataSource class] dequeuesCellsForNodeBackedItems]; - _asyncDataSourceFlags.interopViewForSupplementaryElement = [interopDataSource respondsToSelector:@selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:)]; - } - - _asyncDataSourceFlags.modelIdentifierMethods = [_asyncDataSource respondsToSelector:@selector(modelIdentifierForElementAtIndexPath:inNode:)] && [_asyncDataSource respondsToSelector:@selector(indexPathForElementWithModelIdentifier:inNode:)]; - - - ASDisplayNodeAssert(_asyncDataSourceFlags.collectionNodeNumberOfItemsInSection || _asyncDataSourceFlags.collectionViewNumberOfItemsInSection, @"Data source must implement collectionNode:numberOfItemsInSection:"); - ASDisplayNodeAssert(_asyncDataSourceFlags.collectionNodeNodeBlockForItem - || _asyncDataSourceFlags.collectionNodeNodeForItem - || _asyncDataSourceFlags.collectionViewNodeBlockForItem - || _asyncDataSourceFlags.collectionViewNodeForItem, @"Data source must implement collectionNode:nodeBlockForItemAtIndexPath: or collectionNode:nodeForItemAtIndexPath:"); - } - - _dataController.validationErrorSource = asyncDataSource; - super.dataSource = (id)_proxyDataSource; - - //Cache results of layoutInspector to ensure flags are up to date if getter lazily loads a new one. - id layoutInspector = self.layoutInspector; - if (_layoutInspectorFlags.didChangeCollectionViewDataSource) { - [layoutInspector didChangeCollectionViewDataSource:asyncDataSource]; - } - [self _asyncDelegateOrDataSourceDidChange]; -} - -- (id)asyncDelegate -{ - return _asyncDelegate; -} - -- (void)setAsyncDelegate:(id)asyncDelegate -{ - // Changing super.delegate will trigger a setNeedsLayout, so this must happen on the main thread. - ASDisplayNodeAssertMainThread(); - - // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle - // the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate - // will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to hold a strong - // reference to the old delegate in this case because calls to ASCollectionViewProxy will start failing and cause crashes. - NS_VALID_UNTIL_END_OF_SCOPE id oldDelegate = super.delegate; - - if (asyncDelegate == nil) { - _asyncDelegate = nil; - _proxyDelegate = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; - _asyncDelegateFlags = {}; - } else { - _asyncDelegate = asyncDelegate; - _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; - - _asyncDelegateFlags.scrollViewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)]; - _asyncDelegateFlags.scrollViewWillEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]; - _asyncDelegateFlags.scrollViewDidEndDecelerating = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]; - _asyncDelegateFlags.scrollViewWillBeginDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]; - _asyncDelegateFlags.scrollViewDidEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]; - _asyncDelegateFlags.collectionViewWillDisplayNodeForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNode:forItemAtIndexPath:)]; - if (_asyncDelegateFlags.collectionViewWillDisplayNodeForItem == NO) { - _asyncDelegateFlags.collectionViewWillDisplayNodeForItemDeprecated = [_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]; - } - _asyncDelegateFlags.collectionViewDidEndDisplayingNodeForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNode:forItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]; - _asyncDelegateFlags.shouldBatchFetchForCollectionView = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionView:)]; - _asyncDelegateFlags.collectionViewShouldSelectItem = [_asyncDelegate respondsToSelector:@selector(collectionView:shouldSelectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewDidSelectItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didSelectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewShouldDeselectItem = [_asyncDelegate respondsToSelector:@selector(collectionView:shouldDeselectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewDidDeselectItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didDeselectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewShouldHighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionView:shouldHighlightItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewDidHighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didHighlightItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewDidUnhighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didUnhighlightItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewShouldShowMenuForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:shouldShowMenuForItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewCanPerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:canPerformAction:forItemAtIndexPath:withSender:)]; - _asyncDelegateFlags.collectionViewPerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:performAction:forItemAtIndexPath:withSender:)]; - _asyncDelegateFlags.collectionNodeWillDisplayItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:willDisplayItemWithNode:)]; - _asyncDelegateFlags.collectionNodeDidEndDisplayingItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:didEndDisplayingItemWithNode:)]; - _asyncDelegateFlags.collectionNodeWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(collectionNode:willBeginBatchFetchWithContext:)]; - _asyncDelegateFlags.shouldBatchFetchForCollectionNode = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionNode:)]; - _asyncDelegateFlags.collectionNodeShouldSelectItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:shouldSelectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionNodeDidSelectItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:didSelectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionNodeShouldDeselectItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:shouldDeselectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionNodeDidDeselectItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:didDeselectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionNodeShouldHighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:shouldHighlightItemAtIndexPath:)]; - _asyncDelegateFlags.collectionNodeDidHighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:didHighlightItemAtIndexPath:)]; - _asyncDelegateFlags.collectionNodeDidUnhighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:didUnhighlightItemAtIndexPath:)]; - _asyncDelegateFlags.collectionNodeShouldShowMenuForItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:shouldShowMenuForItemAtIndexPath:)]; - _asyncDelegateFlags.collectionNodeCanPerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:canPerformAction:forItemAtIndexPath:sender:)]; - _asyncDelegateFlags.collectionNodePerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:performAction:forItemAtIndexPath:sender:)]; - _asyncDelegateFlags.collectionNodeWillDisplaySupplementaryElement = [_asyncDelegate respondsToSelector:@selector(collectionNode:willDisplaySupplementaryElementWithNode:)]; - _asyncDelegateFlags.collectionNodeDidEndDisplayingSupplementaryElement = [_asyncDelegate respondsToSelector:@selector(collectionNode:didEndDisplayingSupplementaryElementWithNode:)]; - _asyncDelegateFlags.interop = [_asyncDelegate conformsToProtocol:@protocol(ASCollectionDelegateInterop)]; - if (_asyncDelegateFlags.interop) { - id interopDelegate = (id)_asyncDelegate; - _asyncDelegateFlags.interopWillDisplayCell = [interopDelegate respondsToSelector:@selector(collectionView:willDisplayCell:forItemAtIndexPath:)]; - _asyncDelegateFlags.interopDidEndDisplayingCell = [interopDelegate respondsToSelector:@selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:)]; - _asyncDelegateFlags.interopWillDisplaySupplementaryView = [interopDelegate respondsToSelector:@selector(collectionView:willDisplaySupplementaryView:forElementKind:atIndexPath:)]; - _asyncDelegateFlags.interopdidEndDisplayingSupplementaryView = [interopDelegate respondsToSelector:@selector(collectionView:didEndDisplayingSupplementaryView:forElementOfKind:atIndexPath:)]; - } - } - - super.delegate = (id)_proxyDelegate; - - //Cache results of layoutInspector to ensure flags are up to date if getter lazily loads a new one. - id layoutInspector = self.layoutInspector; - if (_layoutInspectorFlags.didChangeCollectionViewDelegate) { - [layoutInspector didChangeCollectionViewDelegate:asyncDelegate]; - } - [self _asyncDelegateOrDataSourceDidChange]; -} - -- (void)_asyncDelegateOrDataSourceDidChange -{ - ASDisplayNodeAssertMainThread(); - - if (_asyncDataSource == nil && _asyncDelegate == nil && !ASActivateExperimentalFeature(ASExperimentalSkipClearData)) { - [_dataController clearData]; - } -} - -- (void)setCollectionViewLayout:(nonnull UICollectionViewLayout *)collectionViewLayout -{ - ASDisplayNodeAssertMainThread(); - [super setCollectionViewLayout:collectionViewLayout]; - - [self _configureCollectionViewLayout:collectionViewLayout]; - - // Trigger recreation of layout inspector with new collection view layout - if (_layoutInspector != nil) { - _layoutInspector = nil; - [self layoutInspector]; - } -} - -- (id)layoutInspector -{ - if (_layoutInspector == nil) { - UICollectionViewLayout *layout = self.collectionViewLayout; - if (layout == nil) { - // Layout hasn't been set yet, we're still init'ing - return nil; - } - - _defaultLayoutInspector = [layout asdk_layoutInspector]; - ASDisplayNodeAssertNotNil(_defaultLayoutInspector, @"You must not return nil from -asdk_layoutInspector. Return [super asdk_layoutInspector] if you have to! Layout: %@", layout); - - // Explicitly call the setter to wire up the _layoutInspectorFlags - self.layoutInspector = _defaultLayoutInspector; - } - - return _layoutInspector; -} - -- (void)setLayoutInspector:(id)layoutInspector -{ - _layoutInspector = layoutInspector; - - _layoutInspectorFlags.constrainedSizeForSupplementaryNodeOfKindAtIndexPath = [_layoutInspector respondsToSelector:@selector(collectionView:constrainedSizeForSupplementaryNodeOfKind:atIndexPath:)]; - _layoutInspectorFlags.supplementaryNodesOfKindInSection = [_layoutInspector respondsToSelector:@selector(collectionView:supplementaryNodesOfKind:inSection:)]; - _layoutInspectorFlags.didChangeCollectionViewDataSource = [_layoutInspector respondsToSelector:@selector(didChangeCollectionViewDataSource:)]; - _layoutInspectorFlags.didChangeCollectionViewDelegate = [_layoutInspector respondsToSelector:@selector(didChangeCollectionViewDelegate:)]; - - if (_layoutInspectorFlags.didChangeCollectionViewDataSource) { - [_layoutInspector didChangeCollectionViewDataSource:self.asyncDataSource]; - } - if (_layoutInspectorFlags.didChangeCollectionViewDelegate) { - [_layoutInspector didChangeCollectionViewDelegate:self.asyncDelegate]; - } -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType -{ - [_rangeController setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType -{ - return [_rangeController tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - [_rangeController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; -} - -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - return [_rangeController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)setZeroContentInsets:(BOOL)zeroContentInsets -{ - _zeroContentInsets = zeroContentInsets; -} - -- (BOOL)zeroContentInsets -{ - return _zeroContentInsets; -} -#pragma clang diagnostic pop - -/// Uses latest size range from data source and -layoutThatFits:. -- (CGSize)sizeForElement:(ASCollectionElement *)element -{ - ASDisplayNodeAssertMainThread(); - if (element == nil) { - return CGSizeZero; - } - - ASCellNode *node = element.node; - ASDisplayNodeAssertNotNil(node, @"Node must not be nil!"); - - BOOL useUIKitCell = node.shouldUseUIKitCell; - if (useUIKitCell) { - ASWrapperCellNode *wrapperNode = (ASWrapperCellNode *)node; - if (wrapperNode.sizeForItemBlock) { - return wrapperNode.sizeForItemBlock(wrapperNode, element.constrainedSize.max); - } else { - // In this case, it is possible the model indexPath for this element will be nil. Attempt to convert it, - // and call out to the delegate directly. If it has been deleted from the model, the size returned will be the layout's default. - NSIndexPath *indexPath = [_dataController.visibleMap indexPathForElement:element]; - return [self _sizeForUIKitCellWithKind:element.supplementaryElementKind atIndexPath:indexPath]; - } - } else { - return [node layoutThatFits:element.constrainedSize].size; - } -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - - ASCollectionElement *e = [_dataController.visibleMap elementForItemAtIndexPath:indexPath]; - return [self sizeForElement:e]; -} -#pragma clang diagnostic pop - -- (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath -{ - return [_dataController.visibleMap elementForItemAtIndexPath:indexPath].node; -} - -- (NSIndexPath *)convertIndexPathFromCollectionNode:(NSIndexPath *)indexPath waitingIfNeeded:(BOOL)wait -{ - if (indexPath == nil) { - return nil; - } - - NSIndexPath *viewIndexPath = [_dataController.visibleMap convertIndexPath:indexPath fromMap:_dataController.pendingMap]; - if (viewIndexPath == nil && wait) { - [self waitUntilAllUpdatesAreCommitted]; - return [self convertIndexPathFromCollectionNode:indexPath waitingIfNeeded:NO]; - } - return viewIndexPath; -} - -/** - * Asserts that the index path is a valid view-index-path, and returns it if so, nil otherwise. - */ -- (nullable NSIndexPath *)validateIndexPath:(nullable NSIndexPath *)indexPath -{ - if (indexPath == nil) { - return nil; - } - - NSInteger section = indexPath.section; - if (section >= self.numberOfSections) { - ASDisplayNodeFailAssert(@"Collection view index path has invalid section %lu, section count = %lu", (unsigned long)section, (unsigned long)self.numberOfSections); - return nil; - } - - NSInteger item = indexPath.item; - // item == NSNotFound means e.g. "scroll to this section" and is acceptable - if (item != NSNotFound && item >= [self numberOfItemsInSection:section]) { - ASDisplayNodeFailAssert(@"Collection view index path has invalid item %lu in section %lu, item count = %lu", (unsigned long)indexPath.item, (unsigned long)section, (unsigned long)[self numberOfItemsInSection:section]); - return nil; - } - - return indexPath; -} - -- (NSIndexPath *)convertIndexPathToCollectionNode:(NSIndexPath *)indexPath -{ - if ([self validateIndexPath:indexPath] == nil) { - return nil; - } - - return [_dataController.pendingMap convertIndexPath:indexPath fromMap:_dataController.visibleMap]; -} - -- (NSArray *)convertIndexPathsToCollectionNode:(NSArray *)indexPaths -{ - return ASArrayByFlatMapping(indexPaths, NSIndexPath *viewIndexPath, [self convertIndexPathToCollectionNode:viewIndexPath]); -} - -- (ASCellNode *)supplementaryNodeForElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath -{ - return [_dataController.visibleMap supplementaryElementOfKind:elementKind atIndexPath:indexPath].node; -} - -- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode -{ - return [_dataController.visibleMap indexPathForElement:cellNode.collectionElement]; -} - -- (NSArray *)visibleNodes -{ - NSArray *indexPaths = [self indexPathsForVisibleItems]; - NSMutableArray *visibleNodes = [[NSMutableArray alloc] init]; - - for (NSIndexPath *indexPath in indexPaths) { - ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; - if (node) { - // It is possible for UICollectionView to return indexPaths before the node is completed. - [visibleNodes addObject:node]; - } - } - - return visibleNodes; -} - -- (void)invalidateFlowLayoutDelegateMetrics -{ - // Subclass hook -} - -- (nullable NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)indexPath inView:(UIView *)view { - if (_asyncDataSourceFlags.modelIdentifierMethods) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, nil); - NSIndexPath *convertedPath = [self convertIndexPathToCollectionNode:indexPath]; - if (convertedPath == nil) { - return nil; - } else { - return [_asyncDataSource modelIdentifierForElementAtIndexPath:convertedPath inNode:collectionNode]; - } - } else { - return nil; - } -} - -- (nullable NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inView:(UIView *)view { - if (_asyncDataSourceFlags.modelIdentifierMethods) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, nil); - return [_asyncDataSource indexPathForElementWithModelIdentifier:identifier inNode:collectionNode]; - } else { - return nil; - } -} - -#pragma mark Internal - -- (void)_configureCollectionViewLayout:(nonnull UICollectionViewLayout *)layout -{ - _hasDataControllerLayoutDelegate = [layout conformsToProtocol:@protocol(ASDataControllerLayoutDelegate)]; - if (_hasDataControllerLayoutDelegate) { - _dataController.layoutDelegate = (id)layout; - } -} - -/** - This method is called only for UIKit Passthrough cells - either regular Items or Supplementary elements. - It checks if the delegate implements the UICollectionViewFlowLayout methods that provide sizes, and if not, - uses the default values set on the flow layout. If a flow layout is not in use, UICollectionView Passthrough - cells must be sized by logic in the Layout object, and Texture does not participate in these paths. -*/ -- (CGSize)_sizeForUIKitCellWithKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - CGSize size = CGSizeZero; - UICollectionViewLayout *l = self.collectionViewLayout; - - if (kind == nil) { - ASDisplayNodeAssert(_asyncDataSourceFlags.interop, @"This code should not be called except for UIKit passthrough compatibility"); - SEL sizeForItem = @selector(collectionView:layout:sizeForItemAtIndexPath:); - if (indexPath && [_asyncDelegate respondsToSelector:sizeForItem]) { - size = [(id)_asyncDelegate collectionView:self layout:l sizeForItemAtIndexPath:indexPath]; - } else { - size = ASFlowLayoutDefault(l, itemSize, CGSizeZero); - } - } else if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { - ASDisplayNodeAssert(_asyncDataSourceFlags.interopViewForSupplementaryElement, @"This code should not be called except for UIKit passthrough compatibility"); - SEL sizeForHeader = @selector(collectionView:layout:referenceSizeForHeaderInSection:); - if (indexPath && [_asyncDelegate respondsToSelector:sizeForHeader]) { - size = [(id)_asyncDelegate collectionView:self layout:l referenceSizeForHeaderInSection:indexPath.section]; - } else { - size = ASFlowLayoutDefault(l, headerReferenceSize, CGSizeZero); - } - } else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { - ASDisplayNodeAssert(_asyncDataSourceFlags.interopViewForSupplementaryElement, @"This code should not be called except for UIKit passthrough compatibility"); - SEL sizeForFooter = @selector(collectionView:layout:referenceSizeForFooterInSection:); - if (indexPath && [_asyncDelegate respondsToSelector:sizeForFooter]) { - size = [(id)_asyncDelegate collectionView:self layout:l referenceSizeForFooterInSection:indexPath.section]; - } else { - size = ASFlowLayoutDefault(l, footerReferenceSize, CGSizeZero); - } - } - - return size; -} - -- (void)_superReloadData:(void(^)())updates completion:(void(^)(BOOL finished))completion -{ - if (updates) { - updates(); - } - [super reloadData]; - if (completion) { - completion(YES); - } -} - -/** - * Performing nested batch updates with super (e.g. resizing a cell node & updating collection view - * during same frame) can cause super to throw data integrity exceptions because it checks the data - * source counts before the update is complete. - * - * Always call [self _superPerform:] rather than [super performBatch:] so that we can keep our - * `superPerformingBatchUpdates` flag updated. -*/ -- (void)_superPerformBatchUpdates:(void(^)())updates completion:(void(^)(BOOL finished))completion -{ - ASDisplayNodeAssertMainThread(); - - _superBatchUpdateCount++; - [super performBatchUpdates:updates completion:completion]; - _superBatchUpdateCount--; -} - -#pragma mark Assertions. - -- (ASDataController *)dataController -{ - return _dataController; -} - -- (void)beginUpdates -{ - ASDisplayNodeAssertMainThread(); - // _changeSet must be available during batch update - ASDisplayNodeAssertTrue((_batchUpdateCount > 0) == (_changeSet != nil)); - - if (_batchUpdateCount == 0) { - _changeSet = [[_ASHierarchyChangeSet alloc] initWithOldData:[_dataController itemCountsFromDataSource]]; - _changeSet.rootActivity = as_activity_create("Perform async collection update", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); - _changeSet.submitActivity = as_activity_create("Submit changes for collection update", _changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT); - } - _batchUpdateCount++; -} - -- (void)endUpdatesAnimated:(BOOL)animated completion:(nullable void (^)(BOOL))completion -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssertNotNil(_changeSet, @"_changeSet must be available when batch update ends"); - - _batchUpdateCount--; - // Prevent calling endUpdatesAnimated:completion: in an unbalanced way - NSAssert(_batchUpdateCount >= 0, @"endUpdatesAnimated:completion: called without having a balanced beginUpdates call"); - - [_changeSet addCompletionHandler:completion]; - - if (_batchUpdateCount == 0) { - _ASHierarchyChangeSet *changeSet = _changeSet; - - // Nil out _changeSet before forwarding to _dataController to allow the change set to cause subsequent batch updates on the same run loop - _changeSet = nil; - changeSet.animated = animated; - [_dataController updateWithChangeSet:changeSet]; - } -} - -- (void)performBatchAnimated:(BOOL)animated updates:(NS_NOESCAPE void (^)())updates completion:(void (^)(BOOL))completion -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - as_activity_scope(_changeSet.rootActivity); - { - // Only include client code in the submit activity, the rest just lives in the root activity. - as_activity_scope(_changeSet.submitActivity); - if (updates) { - updates(); - } - } - [self endUpdatesAnimated:animated completion:completion]; -} - -- (void)performBatchUpdates:(NS_NOESCAPE void (^)())updates completion:(void (^)(BOOL))completion -{ - // We capture the current state of whether animations are enabled if they don't provide us with one. - [self performBatchAnimated:[UIView areAnimationsEnabled] updates:updates completion:completion]; -} - -- (void)registerSupplementaryNodeOfKind:(NSString *)elementKind -{ - ASDisplayNodeAssert(elementKind != nil, @"A kind is needed for supplementary node registration"); - [_registeredSupplementaryKinds addObject:elementKind]; - [self registerClass:[_ASCollectionReusableView class] forSupplementaryViewOfKind:elementKind withReuseIdentifier:kReuseIdentifier]; -} - -- (void)insertSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - if (sections.count == 0) { return; } - [self performBatchUpdates:^{ - [_changeSet insertSections:sections animationOptions:kASCollectionViewAnimationNone]; - } completion:nil]; -} - -- (void)deleteSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - if (sections.count == 0) { return; } - [self performBatchUpdates:^{ - [_changeSet deleteSections:sections animationOptions:kASCollectionViewAnimationNone]; - } completion:nil]; -} - -- (void)reloadSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - if (sections.count == 0) { return; } - [self performBatchUpdates:^{ - [_changeSet reloadSections:sections animationOptions:kASCollectionViewAnimationNone]; - } completion:nil]; -} - -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection -{ - ASDisplayNodeAssertMainThread(); - [self performBatchUpdates:^{ - [_changeSet moveSection:section toSection:newSection animationOptions:kASCollectionViewAnimationNone]; - } completion:nil]; -} - -- (id)contextForSection:(NSInteger)section -{ - ASDisplayNodeAssertMainThread(); - return [_dataController.visibleMap contextForSection:section]; -} - -- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths -{ - ASDisplayNodeAssertMainThread(); - if (indexPaths.count == 0) { return; } - [self performBatchUpdates:^{ - [_changeSet insertItems:indexPaths animationOptions:kASCollectionViewAnimationNone]; - } completion:nil]; -} - -- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths -{ - ASDisplayNodeAssertMainThread(); - if (indexPaths.count == 0) { return; } - [self performBatchUpdates:^{ - [_changeSet deleteItems:indexPaths animationOptions:kASCollectionViewAnimationNone]; - } completion:nil]; -} - -- (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths -{ - ASDisplayNodeAssertMainThread(); - if (indexPaths.count == 0) { return; } - [self performBatchUpdates:^{ - [_changeSet reloadItems:indexPaths animationOptions:kASCollectionViewAnimationNone]; - } completion:nil]; -} - -- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath -{ - ASDisplayNodeAssertMainThread(); - if (!_reordering) { - [self performBatchUpdates:^{ - [_changeSet moveItemAtIndexPath:indexPath toIndexPath:newIndexPath animationOptions:kASCollectionViewAnimationNone]; - } completion:nil]; - } else { - [super moveItemAtIndexPath:indexPath toIndexPath:newIndexPath]; - } -} - -- (BOOL)beginInteractiveMovementForItemAtIndexPath:(NSIndexPath *)indexPath { - BOOL success = [super beginInteractiveMovementForItemAtIndexPath:indexPath]; - _reordering = success; - return success; -} - -- (void)endInteractiveMovement { - _reordering = NO; - [super endInteractiveMovement]; -} - -- (void)cancelInteractiveMovement { - _reordering = NO; - [super cancelInteractiveMovement]; -} - -#pragma mark - -#pragma mark Intercepted selectors. - -- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView -{ - if (_superIsPendingDataLoad) { - [_rangeController setNeedsUpdate]; - [self _scheduleCheckForBatchFetchingForNumberOfChanges:1]; - _superIsPendingDataLoad = NO; - } - return _dataController.visibleMap.numberOfSections; -} - -- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section -{ - return [_dataController.visibleMap numberOfItemsInSection:section]; -} - -- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)layout - sizeForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - ASCollectionElement *e = [_dataController.visibleMap elementForItemAtIndexPath:indexPath]; - return e ? [self sizeForElement:e] : ASFlowLayoutDefault(layout, itemSize, CGSizeZero); -} - -- (CGSize)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l - referenceSizeForHeaderInSection:(NSInteger)section -{ - ASDisplayNodeAssertMainThread(); - ASElementMap *map = _dataController.visibleMap; - ASCollectionElement *e = [map supplementaryElementOfKind:UICollectionElementKindSectionHeader - atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; - return e ? [self sizeForElement:e] : ASFlowLayoutDefault(l, headerReferenceSize, CGSizeZero); -} - -- (CGSize)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l - referenceSizeForFooterInSection:(NSInteger)section -{ - ASDisplayNodeAssertMainThread(); - ASElementMap *map = _dataController.visibleMap; - ASCollectionElement *e = [map supplementaryElementOfKind:UICollectionElementKindSectionFooter - atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; - return e ? [self sizeForElement:e] : ASFlowLayoutDefault(l, footerReferenceSize, CGSizeZero); -} - -// For the methods that call delegateIndexPathForSection:withSelector:, translate the section from -// visibleMap to pendingMap. If the section no longer exists, or the delegate doesn't implement -// the selector, we will return NSNotFound (and then use the ASFlowLayoutDefault). -- (NSInteger)delegateIndexForSection:(NSInteger)section withSelector:(SEL)selector -{ - if ([_asyncDelegate respondsToSelector:selector]) { - return [_dataController.pendingMap convertSection:section fromMap:_dataController.visibleMap]; - } else { - return NSNotFound; - } -} - -- (UIEdgeInsets)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l - insetForSectionAtIndex:(NSInteger)section -{ - section = [self delegateIndexForSection:section withSelector:_cmd]; - if (section != NSNotFound) { - return [(id)_asyncDelegate collectionView:cv layout:l insetForSectionAtIndex:section]; - } - return ASFlowLayoutDefault(l, sectionInset, UIEdgeInsetsZero); -} - -- (CGFloat)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l - minimumInteritemSpacingForSectionAtIndex:(NSInteger)section -{ - section = [self delegateIndexForSection:section withSelector:_cmd]; - if (section != NSNotFound) { - return [(id)_asyncDelegate collectionView:cv layout:l - minimumInteritemSpacingForSectionAtIndex:section]; - } - return ASFlowLayoutDefault(l, minimumInteritemSpacing, 10.0); // Default is documented as 10.0 -} - -- (CGFloat)collectionView:(UICollectionView *)cv layout:(UICollectionViewLayout *)l - minimumLineSpacingForSectionAtIndex:(NSInteger)section -{ - section = [self delegateIndexForSection:section withSelector:_cmd]; - if (section != NSNotFound) { - return [(id)_asyncDelegate collectionView:cv layout:l - minimumLineSpacingForSectionAtIndex:section]; - } - return ASFlowLayoutDefault(l, minimumLineSpacing, 10.0); // Default is documented as 10.0 -} - -- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - if ([_registeredSupplementaryKinds containsObject:kind] == NO) { - [self registerSupplementaryNodeOfKind:kind]; - } - - UICollectionReusableView *view = nil; - ASCollectionElement *element = [_dataController.visibleMap supplementaryElementOfKind:kind atIndexPath:indexPath]; - ASCellNode *node = element.node; - ASWrapperCellNode *wrapperNode = (node.shouldUseUIKitCell ? (ASWrapperCellNode *)node : nil); - BOOL shouldDequeueExternally = _asyncDataSourceFlags.interopAlwaysDequeue || (_asyncDataSourceFlags.interopViewForSupplementaryElement && wrapperNode); - - if (wrapperNode.viewForSupplementaryBlock) { - view = wrapperNode.viewForSupplementaryBlock(wrapperNode); - } else if (shouldDequeueExternally) { - // This codepath is used for both IGListKit mode, and app-level UICollectionView interop. - view = [(id)_asyncDataSource collectionView:collectionView viewForSupplementaryElementOfKind:kind atIndexPath:indexPath]; - } else { - ASDisplayNodeAssert(node != nil, @"Supplementary node should exist. Kind = %@, indexPath = %@, collectionDataSource = %@", kind, indexPath, self); - view = [self dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:kReuseIdentifier forIndexPath:indexPath]; - } - - if (_ASCollectionReusableView *reusableView = ASDynamicCastStrict(view, _ASCollectionReusableView)) { - reusableView.element = element; - } - - if (node) { - [_rangeController configureContentView:view forCellNode:node]; - } - - return view; -} - -- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath -{ - UICollectionViewCell *cell = nil; - ASCollectionElement *element = [_dataController.visibleMap elementForItemAtIndexPath:indexPath]; - ASCellNode *node = element.node; - ASWrapperCellNode *wrapperNode = (node.shouldUseUIKitCell ? (ASWrapperCellNode *)node : nil); - BOOL shouldDequeueExternally = _asyncDataSourceFlags.interopAlwaysDequeue || (_asyncDataSourceFlags.interop && wrapperNode); - - if (wrapperNode.cellForItemBlock) { - cell = wrapperNode.cellForItemBlock(wrapperNode); - } else if (shouldDequeueExternally) { - cell = [(id)_asyncDataSource collectionView:collectionView cellForItemAtIndexPath:indexPath]; - } else { - cell = [self dequeueReusableCellWithReuseIdentifier:kReuseIdentifier forIndexPath:indexPath]; - } - - ASDisplayNodeAssert(element != nil, @"Element should exist. indexPath = %@, collectionDataSource = %@", indexPath, self); - ASDisplayNodeAssert(cell != nil, @"UICollectionViewCell must not be nil. indexPath = %@, collectionDataSource = %@", indexPath, self); - - if (_ASCollectionViewCell *asCell = ASDynamicCastStrict(cell, _ASCollectionViewCell)) { - asCell.element = element; - [_rangeController configureContentView:cell.contentView forCellNode:node]; - } - - return cell; -} - -- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)rawCell forItemAtIndexPath:(NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.interopWillDisplayCell) { - ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; - if (node.shouldUseUIKitCell) { - [(id )_asyncDelegate collectionView:collectionView willDisplayCell:rawCell forItemAtIndexPath:indexPath]; - } - } - - _ASCollectionViewCell *cell = ASDynamicCastStrict(rawCell, _ASCollectionViewCell); - if (cell == nil) { - [_rangeController setNeedsUpdate]; - return; - } - - ASCollectionElement *element = cell.element; - if (element) { - ASDisplayNodeAssertTrue([_dataController.visibleMap elementForItemAtIndexPath:indexPath] == element); - [_visibleElements addObject:element]; - } else { - ASDisplayNodeAssert(NO, @"Unexpected nil element for willDisplayCell: %@, %@, %@", rawCell, self, indexPath); - return; - } - - ASCellNode *cellNode = element.node; - cellNode.scrollView = collectionView; - - // Update the selected background view in collectionView:willDisplayCell:forItemAtIndexPath: otherwise it could be too - // early e.g. if the selectedBackgroundView was set in didLoad() - cell.selectedBackgroundView = cellNode.selectedBackgroundView; - cell.backgroundView = cellNode.backgroundView; - - // Under iOS 10+, cells may be removed/re-added to the collection view without - // receiving prepareForReuse/applyLayoutAttributes, as an optimization for e.g. - // if the user is scrolling back and forth across a small set of items. - // In this case, we have to fetch the layout attributes manually. - // This may be possible under iOS < 10 but it has not been observed yet. - if (cell.layoutAttributes == nil) { - cell.layoutAttributes = [collectionView layoutAttributesForItemAtIndexPath:indexPath]; - } - - ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with cell that will be displayed not to be nil. indexPath: %@", indexPath); - - if (_asyncDelegateFlags.collectionNodeWillDisplayItem && self.collectionNode != nil) { - [_asyncDelegate collectionNode:self.collectionNode willDisplayItemWithNode:cellNode]; - } else if (_asyncDelegateFlags.collectionViewWillDisplayNodeForItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self willDisplayNode:cellNode forItemAtIndexPath:indexPath]; - } else if (_asyncDelegateFlags.collectionViewWillDisplayNodeForItemDeprecated) { - [_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath]; - } -#pragma clang diagnostic pop - - [_rangeController setNeedsUpdate]; - - if ([cell consumesCellNodeVisibilityEvents]) { - [_cellsForVisibilityUpdates addObject:cell]; - } -} - -- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)rawCell forItemAtIndexPath:(NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.interopDidEndDisplayingCell) { - ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; - if (node.shouldUseUIKitCell) { - [(id )_asyncDelegate collectionView:collectionView didEndDisplayingCell:rawCell forItemAtIndexPath:indexPath]; - } - } - - _ASCollectionViewCell *cell = ASDynamicCastStrict(rawCell, _ASCollectionViewCell); - if (cell == nil) { - [_rangeController setNeedsUpdate]; - return; - } - - // Retrieve the element from cell instead of visible map because at this point visible map could have been updated and no longer holds the element. - ASCollectionElement *element = cell.element; - if (element) { - [_visibleElements removeObject:element]; - } else { - ASDisplayNodeAssert(NO, @"Unexpected nil element for didEndDisplayingCell: %@, %@, %@", rawCell, self, indexPath); - return; - } - - ASCellNode *cellNode = element.node; - - if (_asyncDelegateFlags.collectionNodeDidEndDisplayingItem) { - if (ASCollectionNode *collectionNode = self.collectionNode) { - [_asyncDelegate collectionNode:collectionNode didEndDisplayingItemWithNode:cellNode]; - } - } else if (_asyncDelegateFlags.collectionViewDidEndDisplayingNodeForItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self didEndDisplayingNode:cellNode forItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } - - [_rangeController setNeedsUpdate]; - - [_cellsForVisibilityUpdates removeObject:cell]; - - cellNode.scrollView = nil; - cell.layoutAttributes = nil; -} - -- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)rawView forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.interopWillDisplaySupplementaryView) { - ASCellNode *node = [self supplementaryNodeForElementKind:elementKind atIndexPath:indexPath]; - if (node.shouldUseUIKitCell) { - [(id )_asyncDelegate collectionView:collectionView willDisplaySupplementaryView:rawView forElementKind:elementKind atIndexPath:indexPath]; - } - } - - _ASCollectionReusableView *view = ASDynamicCastStrict(rawView, _ASCollectionReusableView); - if (view == nil) { - return; - } - - ASCollectionElement *element = view.element; - if (element) { - ASDisplayNodeAssertTrue([_dataController.visibleMap supplementaryElementOfKind:elementKind atIndexPath:indexPath] == view.element); - [_visibleElements addObject:element]; - } else { - ASDisplayNodeAssert(NO, @"Unexpected nil element for willDisplaySupplementaryView: %@, %@, %@", rawView, self, indexPath); - return; - } - - // Under iOS 10+, cells may be removed/re-added to the collection view without - // receiving prepareForReuse/applyLayoutAttributes, as an optimization for e.g. - // if the user is scrolling back and forth across a small set of items. - // In this case, we have to fetch the layout attributes manually. - // This may be possible under iOS < 10 but it has not been observed yet. - if (view.layoutAttributes == nil) { - view.layoutAttributes = [collectionView layoutAttributesForSupplementaryElementOfKind:elementKind atIndexPath:indexPath]; - } - - if (_asyncDelegateFlags.collectionNodeWillDisplaySupplementaryElement) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0); - ASCellNode *node = element.node; - ASDisplayNodeAssert([node.supplementaryElementKind isEqualToString:elementKind], @"Expected node for supplementary element to have kind '%@', got '%@'.", elementKind, node.supplementaryElementKind); - [_asyncDelegate collectionNode:collectionNode willDisplaySupplementaryElementWithNode:node]; - } -} - -- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)rawView forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.interopdidEndDisplayingSupplementaryView) { - ASCellNode *node = [self supplementaryNodeForElementKind:elementKind atIndexPath:indexPath]; - if (node.shouldUseUIKitCell) { - [(id )_asyncDelegate collectionView:collectionView didEndDisplayingSupplementaryView:rawView forElementOfKind:elementKind atIndexPath:indexPath]; - } - } - - _ASCollectionReusableView *view = ASDynamicCastStrict(rawView, _ASCollectionReusableView); - if (view == nil) { - return; - } - - // Retrieve the element from cell instead of visible map because at this point visible map could have been updated and no longer holds the element. - ASCollectionElement *element = view.element; - if (element) { - [_visibleElements removeObject:element]; - } else { - ASDisplayNodeAssert(NO, @"Unexpected nil element for didEndDisplayingSupplementaryView: %@, %@, %@", rawView, self, indexPath); - return; - } - - if (_asyncDelegateFlags.collectionNodeDidEndDisplayingSupplementaryElement) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0); - ASCellNode *node = element.node; - ASDisplayNodeAssert([node.supplementaryElementKind isEqualToString:elementKind], @"Expected node for supplementary element to have kind '%@', got '%@'.", elementKind, node.supplementaryElementKind); - [_asyncDelegate collectionNode:collectionNode didEndDisplayingSupplementaryElementWithNode:node]; - } -} - -- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.collectionNodeShouldSelectItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate collectionNode:collectionNode shouldSelectItemAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.collectionViewShouldSelectItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate collectionView:self shouldSelectItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } - return YES; -} - -- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.collectionNodeDidSelectItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - [_asyncDelegate collectionNode:collectionNode didSelectItemAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.collectionViewDidSelectItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self didSelectItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } -} - -- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.collectionNodeShouldDeselectItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate collectionNode:collectionNode shouldDeselectItemAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.collectionViewShouldDeselectItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate collectionView:self shouldDeselectItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } - return YES; -} - -- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.collectionNodeDidDeselectItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - [_asyncDelegate collectionNode:collectionNode didDeselectItemAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.collectionViewDidDeselectItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self didDeselectItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } -} - -- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.collectionNodeShouldHighlightItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate collectionNode:collectionNode shouldHighlightItemAtIndexPath:indexPath]; - } else { - return YES; - } - } else if (_asyncDelegateFlags.collectionViewShouldHighlightItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate collectionView:self shouldHighlightItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } - return YES; -} - -- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.collectionNodeDidHighlightItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - [_asyncDelegate collectionNode:collectionNode didHighlightItemAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.collectionViewDidHighlightItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self didHighlightItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } -} - -- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.collectionNodeDidUnhighlightItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - [_asyncDelegate collectionNode:collectionNode didUnhighlightItemAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.collectionViewDidUnhighlightItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self didUnhighlightItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } -} - -- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.collectionNodeShouldShowMenuForItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate collectionNode:collectionNode shouldShowMenuForItemAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.collectionViewShouldShowMenuForItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate collectionView:self shouldShowMenuForItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } - return NO; -} - -- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(nonnull SEL)action forItemAtIndexPath:(nonnull NSIndexPath *)indexPath withSender:(nullable id)sender -{ - if (_asyncDelegateFlags.collectionNodeCanPerformActionForItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate collectionNode:collectionNode canPerformAction:action forItemAtIndexPath:indexPath sender:sender]; - } - } else if (_asyncDelegateFlags.collectionViewCanPerformActionForItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate collectionView:self canPerformAction:action forItemAtIndexPath:indexPath withSender:sender]; -#pragma clang diagnostic pop - } - return NO; -} - -- (void)collectionView:(UICollectionView *)collectionView performAction:(nonnull SEL)action forItemAtIndexPath:(nonnull NSIndexPath *)indexPath withSender:(nullable id)sender -{ - if (_asyncDelegateFlags.collectionNodePerformActionForItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0); - indexPath = [self convertIndexPathToCollectionNode:indexPath]; - if (indexPath != nil) { - [_asyncDelegate collectionNode:collectionNode performAction:action forItemAtIndexPath:indexPath sender:sender]; - } - } else if (_asyncDelegateFlags.collectionViewPerformActionForItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self performAction:action forItemAtIndexPath:indexPath withSender:sender]; -#pragma clang diagnostic pop - } -} - -- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath -{ - // Mimic UIKit's gating logic. - // If the data source doesn't support moving, then all bets are off. - if (!_asyncDataSourceFlags.collectionNodeMoveItem) { - return NO; - } - - // Currently we do not support interactive moves when using async layout. The reason is, we do not have a mechanism - // to propagate the "presentation data" element map (containing the speculative in-progress moves) to the layout delegate, - // and this can cause exceptions to be thrown from UICV. For example, if you drag an item out of a section, - // the element map will still contain N items in that section, even though there's only N-1 shown, and UICV will - // throw an exception that you specified an element that doesn't exist. - // - // In iOS >= 11, this is made much easier by the UIDataSourceTranslating API. In previous versions of iOS our best bet - // would be to capture the invalidation contexts that are sent during interactive moves and make our own data source translator. - if ([self.collectionViewLayout isKindOfClass:[ASCollectionLayout class]]) { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - as_log_debug(ASCollectionLog(), "Collection node item interactive movement is not supported when using a layout delegate. This message will only be logged once. Node: %@", ASObjectDescriptionMakeTiny(self)); - }); - return NO; - } - - // If the data source implements canMoveItem, let them decide. - if (_asyncDataSourceFlags.collectionNodeCanMoveItem) { - if (ASCellNode *cellNode = [self nodeForItemAtIndexPath:indexPath]) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO); - return [_asyncDataSource collectionNode:collectionNode canMoveItemWithNode:cellNode]; - } - } - - // Otherwise allow the move for all items. - return YES; -} - -- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath -{ - ASDisplayNodeAssert(_asyncDataSourceFlags.collectionNodeMoveItem, @"Should not allow interactive collection item movement if data source does not support it."); - - // Inform the data source first, in case they call nodeForItemAtIndexPath:. - // We want to make sure we return them the node for the item they have in mind. - if (ASCollectionNode *collectionNode = self.collectionNode) { - [_asyncDataSource collectionNode:collectionNode moveItemAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; - } - - // Now we update our data controller's store. - // Get up to date - [self waitUntilAllUpdatesAreCommitted]; - // Set our flag to suppress informing super about the change. - ASDisplayNodeAssertFalse(_updatingInResponseToInteractiveMove); - _updatingInResponseToInteractiveMove = YES; - // Submit the move - [self moveItemAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; - // Wait for it to finish – should be fast! - [self waitUntilAllUpdatesAreCommitted]; - // Clear the flag - _updatingInResponseToInteractiveMove = NO; -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; - if (ASInterfaceStateIncludesVisible(interfaceState)) { - [self _checkForBatchFetching]; - } - for (_ASCollectionViewCell *cell in _cellsForVisibilityUpdates) { - // _cellsForVisibilityUpdates only includes cells for ASCellNode subclasses with overrides of the visibility method. - [cell cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged inScrollView:scrollView]; - } - if (_asyncDelegateFlags.scrollViewDidScroll) { - [_asyncDelegate scrollViewDidScroll:scrollView]; - } -} - -- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset -{ - CGPoint contentOffset = scrollView.contentOffset; - _deceleratingVelocity = CGPointMake( - contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0), - contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 0) - ); - - if (targetContentOffset != NULL) { - ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); - [self _beginBatchFetchingIfNeededWithContentOffset:*targetContentOffset velocity:velocity]; - } - - if (_asyncDelegateFlags.scrollViewWillEndDragging) { - [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:(targetContentOffset ? : &contentOffset)]; - } -} - -- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView -{ - _deceleratingVelocity = CGPointZero; - - if (_asyncDelegateFlags.scrollViewDidEndDecelerating) { - [_asyncDelegate scrollViewDidEndDecelerating:scrollView]; - } -} - -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView -{ - // If a scroll happens the current range mode needs to go to full - _rangeController.contentHasBeenScrolled = YES; - [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; - - for (_ASCollectionViewCell *cell in _cellsForVisibilityUpdates) { - [cell cellNodeVisibilityEvent:ASCellNodeVisibilityEventWillBeginDragging inScrollView:scrollView]; - } - if (_asyncDelegateFlags.scrollViewWillBeginDragging) { - [_asyncDelegate scrollViewWillBeginDragging:scrollView]; - } -} - -- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate -{ - for (_ASCollectionViewCell *cell in _cellsForVisibilityUpdates) { - [cell cellNodeVisibilityEvent:ASCellNodeVisibilityEventDidEndDragging inScrollView:scrollView]; - } - if (_asyncDelegateFlags.scrollViewDidEndDragging) { - [_asyncDelegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; - } -} - -#pragma mark - Scroll Direction. - -- (BOOL)inverted -{ - return _inverted; -} - -- (void)setInverted:(BOOL)inverted -{ - _inverted = inverted; -} - -- (void)setLeadingScreensForBatching:(CGFloat)leadingScreensForBatching -{ - if (_leadingScreensForBatching != leadingScreensForBatching) { - _leadingScreensForBatching = leadingScreensForBatching; - // Push this to the next runloop to be sure the scroll view has the right content size - dispatch_async(dispatch_get_main_queue(), ^{ - [self _checkForBatchFetching]; - }); - } -} - -- (CGFloat)leadingScreensForBatching -{ - return _leadingScreensForBatching; -} - -- (ASScrollDirection)scrollDirection -{ - CGPoint scrollVelocity; - if (self.isTracking) { - scrollVelocity = [self.panGestureRecognizer velocityInView:self.superview]; - } else { - scrollVelocity = _deceleratingVelocity; - } - - ASScrollDirection scrollDirection = [self _scrollDirectionForVelocity:scrollVelocity]; - return ASScrollDirectionApplyTransform(scrollDirection, self.transform); -} - -- (ASScrollDirection)_scrollDirectionForVelocity:(CGPoint)scrollVelocity -{ - ASScrollDirection direction = ASScrollDirectionNone; - ASScrollDirection scrollableDirections = [self scrollableDirections]; - - if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { // Can scroll horizontally. - if (scrollVelocity.x < 0.0) { - direction |= ASScrollDirectionRight; - } else if (scrollVelocity.x > 0.0) { - direction |= ASScrollDirectionLeft; - } - } - if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { // Can scroll vertically. - if (scrollVelocity.y < 0.0) { - direction |= ASScrollDirectionDown; - } else if (scrollVelocity.y > 0.0) { - direction |= ASScrollDirectionUp; - } - } - - return direction; -} - -- (ASScrollDirection)scrollableDirections -{ - ASDisplayNodeAssertNotNil(self.layoutInspector, @"Layout inspector should be assigned."); - return [self.layoutInspector scrollableDirections]; -} - -- (void)layoutSubviews -{ - if (_cellsForLayoutUpdates.count > 0) { - NSArray *nodes = [_cellsForLayoutUpdates allObjects]; - [_cellsForLayoutUpdates removeAllObjects]; - - NSMutableArray *nodesSizeChanged = [[NSMutableArray alloc] init]; - - [_dataController relayoutNodes:nodes nodesSizeChanged:nodesSizeChanged]; - [self nodesDidRelayout:nodesSizeChanged]; - } - - // Flush any pending invalidation action if needed. - ASCollectionViewInvalidationStyle invalidationStyle = _nextLayoutInvalidationStyle; - _nextLayoutInvalidationStyle = ASCollectionViewInvalidationStyleNone; - switch (invalidationStyle) { - case ASCollectionViewInvalidationStyleWithAnimation: - if (0 == _superBatchUpdateCount) { - if (ASCellLayoutModeIncludes(ASCellLayoutModeAlwaysReloadData)) { - [self _superReloadData:nil completion:nil]; - } else { - [self _superPerformBatchUpdates:nil completion:nil]; - } - } - break; - case ASCollectionViewInvalidationStyleWithoutAnimation: - [self.collectionViewLayout invalidateLayout]; - break; - default: - break; - } - - // To ensure _maxSizeForNodesConstrainedSize is up-to-date for every usage, this call to super must be done last - [super layoutSubviews]; - - if (_zeroContentInsets) { - self.contentInset = UIEdgeInsetsZero; - } - - // Update range controller immediately if possible & needed. - // Calling -updateIfNeeded in here with self.window == nil (early in the collection view's life) - // may cause UICollectionView data related crashes. We'll update in -didMoveToWindow anyway. - if (self.window != nil) { - [_rangeController updateIfNeeded]; - } -} - - -#pragma mark - Batch Fetching - -- (ASBatchContext *)batchContext -{ - return _batchContext; -} - -- (BOOL)canBatchFetch -{ - // if the delegate does not respond to this method, there is no point in starting to fetch - BOOL canFetch = _asyncDelegateFlags.collectionNodeWillBeginBatchFetch || _asyncDelegateFlags.collectionViewWillBeginBatchFetch; - if (canFetch && _asyncDelegateFlags.shouldBatchFetchForCollectionNode) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO); - return [_asyncDelegate shouldBatchFetchForCollectionNode:collectionNode]; - } else if (canFetch && _asyncDelegateFlags.shouldBatchFetchForCollectionView) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate shouldBatchFetchForCollectionView:self]; -#pragma clang diagnostic pop - } else { - return canFetch; - } -} - -- (id)batchFetchingDelegate{ - return self.collectionNode.batchFetchingDelegate; -} - -- (void)_scheduleCheckForBatchFetchingForNumberOfChanges:(NSUInteger)changes -{ - // Prevent fetching will continually trigger in a loop after reaching end of content and no new content was provided - if (changes == 0 && _hasEverCheckedForBatchFetchingDueToUpdate) { - return; - } - _hasEverCheckedForBatchFetchingDueToUpdate = YES; - - // Push this to the next runloop to be sure the scroll view has the right content size - dispatch_async(dispatch_get_main_queue(), ^{ - [self _checkForBatchFetching]; - }); -} - -- (void)_checkForBatchFetching -{ - // Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset: - if (self.isDragging || self.isTracking) { - return; - } - - [self _beginBatchFetchingIfNeededWithContentOffset:self.contentOffset velocity:CGPointZero]; -} - -- (void)_beginBatchFetchingIfNeededWithContentOffset:(CGPoint)contentOffset velocity:(CGPoint)velocity -{ - if (ASDisplayShouldFetchBatchForScrollView(self, self.scrollDirection, self.scrollableDirections, contentOffset, velocity)) { - [self _beginBatchFetching]; - } -} - -- (void)_beginBatchFetching -{ - as_activity_create_for_scope("Batch fetch for collection node"); - [_batchContext beginBatchFetching]; - if (_asyncDelegateFlags.collectionNodeWillBeginBatchFetch) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0); - as_log_debug(ASCollectionLog(), "Beginning batch fetch for %@ with context %@", collectionNode, _batchContext); - [_asyncDelegate collectionNode:collectionNode willBeginBatchFetchWithContext:_batchContext]; - }); - } else if (_asyncDelegateFlags.collectionViewWillBeginBatchFetch) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext]; -#pragma clang diagnostic pop - }); - } -} - -#pragma mark - ASDataControllerSource - -- (BOOL)dataController:(ASDataController *)dataController shouldEagerlyLayoutNode:(ASCellNode *)node -{ - NSAssert(!ASCellLayoutModeIncludes(ASCellLayoutModeAlwaysLazy), - @"ASCellLayoutModeAlwaysLazy flag is no longer supported"); - return !node.shouldUseUIKitCell; -} - -- (BOOL)dataController:(ASDataController *)dataController shouldSynchronouslyProcessChangeSet:(_ASHierarchyChangeSet *)changeSet -{ - // If we have AlwaysSync set, block and donate main priority. - if (ASCellLayoutModeIncludes(ASCellLayoutModeAlwaysSync)) { - return YES; - } - // Prioritize AlwaysAsync over the remaining heuristics for the Default mode. - if (ASCellLayoutModeIncludes(ASCellLayoutModeAlwaysAsync)) { - return NO; - } - // Reload data is expensive, don't block main while doing so. - if (changeSet.includesReloadData) { - return NO; - } - // If we have very few ASCellNodes (besides UIKit passthrough ones), match UIKit by blocking. - if (changeSet.countForAsyncLayout < 2) { - return YES; - } - CGSize contentSize = self.contentSize; - CGSize boundsSize = self.bounds.size; - if (contentSize.height <= boundsSize.height && contentSize.width <= boundsSize.width) { - return YES; - } - return NO; // ASCellLayoutModeNone -} - -- (BOOL)dataControllerShouldSerializeNodeCreation:(ASDataController *)dataController -{ - return ASCellLayoutModeIncludes(ASCellLayoutModeSerializeNodeCreation); -} - -- (id)dataController:(ASDataController *)dataController nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath -{ - if (!_asyncDataSourceFlags.nodeModelForItem) { - return nil; - } - - GET_COLLECTIONNODE_OR_RETURN(collectionNode, nil); - return [_asyncDataSource collectionNode:collectionNode nodeModelForItemAtIndexPath:indexPath]; -} - -- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath shouldAsyncLayout:(BOOL *)shouldAsyncLayout -{ - ASDisplayNodeAssertMainThread(); - ASCellNodeBlock block = nil; - ASCellNode *cell = nil; - - if (_asyncDataSourceFlags.collectionNodeNodeBlockForItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); - block = [_asyncDataSource collectionNode:collectionNode nodeBlockForItemAtIndexPath:indexPath]; - } - if (!block && !cell && _asyncDataSourceFlags.collectionNodeNodeForItem) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); - cell = [_asyncDataSource collectionNode:collectionNode nodeForItemAtIndexPath:indexPath]; - } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - if (!block && !cell && _asyncDataSourceFlags.collectionViewNodeBlockForItem) { - block = [_asyncDataSource collectionView:self nodeBlockForItemAtIndexPath:indexPath]; - } - if (!block && !cell && _asyncDataSourceFlags.collectionViewNodeForItem) { - cell = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; - } -#pragma clang diagnostic pop - - if (block == nil) { - if (cell == nil || ASDynamicCast(cell, ASCellNode) == nil) { - // In this case, either the client is expecting a UIKit passthrough cell to be created automatically, - // or it is an error. - if (_asyncDataSourceFlags.interop) { - cell = [[ASWrapperCellNode alloc] init]; - } else { - ASDisplayNodeFailAssert(@"ASCollection could not get a node block for item at index path %@: %@, %@. If you are trying to display a UICollectionViewCell, make sure your dataSource conforms to the protocol!", indexPath, cell, block); - cell = [[ASCellNode alloc] init]; - } - } - - // This condition is intended to run for either cells received from the datasource, or created just above. - if (cell.shouldUseUIKitCell) { - *shouldAsyncLayout = NO; - } - } - - // Wrap the node block - BOOL disableRangeController = ASCellLayoutModeIncludes(ASCellLayoutModeDisableRangeController); - __weak __typeof__(self) weakSelf = self; - return ^{ - __typeof__(self) strongSelf = weakSelf; - ASCellNode *node = (block ? block() : cell); - ASDisplayNodeAssert([node isKindOfClass:[ASCellNode class]], @"ASCollectionNode provided a non-ASCellNode! %@, %@", node, strongSelf); - - if (!disableRangeController) { - [node enterHierarchyState:ASHierarchyStateRangeManaged]; - } - if (node.interactionDelegate == nil) { - node.interactionDelegate = strongSelf; - } - if (strongSelf.inverted) { - node.transform = CATransform3DMakeScale(1, -1, 1); - } - return node; - }; -} - -- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section -{ - if (_asyncDataSourceFlags.collectionNodeNumberOfItemsInSection) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, 0); - return [_asyncDataSource collectionNode:collectionNode numberOfItemsInSection:section]; - } else if (_asyncDataSourceFlags.collectionViewNumberOfItemsInSection) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDataSource collectionView:self numberOfItemsInSection:section]; -#pragma clang diagnostic pop - } else { - return 0; - } -} - -- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController { - if (_asyncDataSourceFlags.numberOfSectionsInCollectionNode) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, 0); - return [_asyncDataSource numberOfSectionsInCollectionNode:collectionNode]; - } else if (_asyncDataSourceFlags.numberOfSectionsInCollectionView) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDataSource numberOfSectionsInCollectionView:self]; -#pragma clang diagnostic pop - } else { - return 1; - } -} - -- (BOOL)dataController:(ASDataController *)dataController presentedSizeForElement:(ASCollectionElement *)element matchesSize:(CGSize)size -{ - NSIndexPath *indexPath = [self indexPathForNode:element.node]; - if (indexPath == nil) { - ASDisplayNodeFailAssert(@"Data controller should not ask for presented size for element that is not presented."); - return YES; - } - - UICollectionViewLayoutAttributes *attributes; - if (element.supplementaryElementKind == nil) { - attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; - } else { - attributes = [self layoutAttributesForSupplementaryElementOfKind:element.supplementaryElementKind atIndexPath:indexPath]; - } - return CGSizeEqualToSizeWithIn(attributes.size, size, FLT_EPSILON); -} - -#pragma mark - ASDataControllerSource optional methods - -- (ASCellNodeBlock)dataController:(ASDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath shouldAsyncLayout:(BOOL *)shouldAsyncLayout -{ - ASDisplayNodeAssertMainThread(); - ASCellNodeBlock block = nil; - ASCellNode *cell = nil; - if (_asyncDataSourceFlags.collectionNodeNodeBlockForSupplementaryElement) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); - block = [_asyncDataSource collectionNode:collectionNode nodeBlockForSupplementaryElementOfKind:kind atIndexPath:indexPath]; - } - if (!block && !cell && _asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); - cell = [_asyncDataSource collectionNode:collectionNode nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath]; - } - if (!block && !cell && _asyncDataSourceFlags.collectionViewNodeForSupplementaryElement) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - cell = [_asyncDataSource collectionView:self nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath]; -#pragma clang diagnostic pop - } - - if (block == nil) { - if (cell == nil || ASDynamicCast(cell, ASCellNode) == nil) { - // In this case, the app code returned nil for the node and the nodeBlock. - // If the UIKit method is implemented, then we should use a passthrough cell. - // Otherwise the CGSizeZero default will cause UIKit to not show it (so this isn't an error like the cellForItem case). - - BOOL useUIKitCell = _asyncDataSourceFlags.interopViewForSupplementaryElement; - if (useUIKitCell) { - cell = [[ASWrapperCellNode alloc] init]; - } else { - cell = [[ASCellNode alloc] init]; - } - } - - // This condition is intended to run for either cells received from the datasource, or created just above. - if (cell.shouldUseUIKitCell) { - *shouldAsyncLayout = NO; - } - - block = ^{ return cell; }; - } - - // Wrap the node block - // BOOL disableRangeController = ASCellLayoutModeIncludes(ASCellLayoutModeDisableRangeController); - __weak __typeof__(self) weakSelf = self; - return ^{ - __typeof__(self) strongSelf = weakSelf; - ASCellNode *node = block(); - ASDisplayNodeAssert([node isKindOfClass:[ASCellNode class]], - @"ASCollectionNode provided a non-ASCellNode! %@, %@", node, strongSelf); - - // TODO: ASRangeController doesn't currently support managing interfaceState for supplementary nodes. - // For now, we allow the standard ASInterfaceStateInHierarchy behavior by ensuring we do not inform - // the node that it should expect external management of interfaceState. - /* - if (!disableRangeController) { - [node enterHierarchyState:ASHierarchyStateRangeManaged]; - } - */ - - if (node.interactionDelegate == nil) { - node.interactionDelegate = strongSelf; - } - if (strongSelf.inverted) { - node.transform = CATransform3DMakeScale(1, -1, 1); - } - return node; - }; -} - -- (NSArray *)dataController:(ASDataController *)dataController supplementaryNodeKindsInSections:(NSIndexSet *)sections -{ - if (_asyncDataSourceFlags.collectionNodeSupplementaryElementKindsInSection) { - const auto kinds = [[NSMutableSet alloc] init]; - GET_COLLECTIONNODE_OR_RETURN(collectionNode, @[]); - [sections enumerateIndexesUsingBlock:^(NSUInteger section, BOOL * _Nonnull stop) { - NSArray *kindsForSection = [_asyncDataSource collectionNode:collectionNode supplementaryElementKindsInSection:section]; - [kinds addObjectsFromArray:kindsForSection]; - }]; - return [kinds allObjects]; - } else { - // TODO: Lock this - return [_registeredSupplementaryKinds allObjects]; - } -} - -- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath -{ - return [self.layoutInspector collectionView:self constrainedSizeForNodeAtIndexPath:indexPath]; -} - -- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - if (_layoutInspectorFlags.constrainedSizeForSupplementaryNodeOfKindAtIndexPath) { - return [self.layoutInspector collectionView:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; - } - - ASDisplayNodeAssert(NO, @"To support supplementary nodes in ASCollectionView, it must have a layoutInspector for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); - return ASSizeRangeMake(CGSizeZero, CGSizeZero); -} - -- (NSUInteger)dataController:(ASDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section -{ - if (_asyncDataSource == nil) { - return 0; - } - - if (_layoutInspectorFlags.supplementaryNodesOfKindInSection) { - return [self.layoutInspector collectionView:self supplementaryNodesOfKind:kind inSection:section]; - } - - ASDisplayNodeAssert(NO, @"To support supplementary nodes in ASCollectionView, it must have a layoutInspector for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); - return 0; -} - -- (id)dataController:(ASDataController *)dataController contextForSection:(NSInteger)section -{ - ASDisplayNodeAssertMainThread(); - id context = nil; - - if (_asyncDataSourceFlags.collectionNodeContextForSection) { - GET_COLLECTIONNODE_OR_RETURN(collectionNode, nil); - context = [_asyncDataSource collectionNode:collectionNode contextForSection:section]; - } - - if (context != nil) { - context.collectionView = self; - } - return context; -} - -#pragma mark - ASRangeControllerDataSource - -- (ASRangeController *)rangeController -{ - return _rangeController; -} - -- (NSHashTable *)visibleElementsForRangeController:(ASRangeController *)rangeController -{ - return ASPointerTableByFlatMapping(_visibleElements, id element, element); -} - -- (ASElementMap *)elementMapForRangeController:(ASRangeController *)rangeController -{ - return _dataController.visibleMap; -} - -- (ASScrollDirection)scrollDirectionForRangeController:(ASRangeController *)rangeController -{ - return self.scrollDirection; -} - -- (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController -{ - return ASInterfaceStateForDisplayNode(self.collectionNode, self.window); -} - -- (NSString *)nameForRangeControllerDataSource -{ - return self.asyncDataSource ? NSStringFromClass([self.asyncDataSource class]) : NSStringFromClass([self class]); -} - -#pragma mark - ASRangeControllerDelegate - -- (BOOL)rangeControllerShouldUpdateRanges:(ASRangeController *)rangeController -{ - return !ASCellLayoutModeIncludes(ASCellLayoutModeDisableRangeController); -} - -- (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates -{ - ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _superIsPendingDataLoad || _updatingInResponseToInteractiveMove) { - updates(); - [changeSet executeCompletionHandlerWithFinished:NO]; - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - //TODO Do we need to notify _layoutFacilitator before reloadData? - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) { - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:change.indexPaths batched:YES]; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:change.indexSet batched:YES]; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:change.indexSet batched:YES]; - } - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) { - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:change.indexPaths batched:YES]; - } - - ASPerformBlockWithoutAnimation(!changeSet.animated, ^{ - as_activity_scope(as_activity_create("Commit collection update", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT)); - if (changeSet.includesReloadData) { - _superIsPendingDataLoad = YES; - updates(); - [self _superReloadData:nil completion:nil]; - as_log_debug(ASCollectionLog(), "Did reloadData %@", self.collectionNode); - [changeSet executeCompletionHandlerWithFinished:YES]; - } else { - [_layoutFacilitator collectionViewWillPerformBatchUpdates]; - - __block NSUInteger numberOfUpdates = 0; - const auto completion = ^(BOOL finished) { - as_activity_scope(as_activity_create("Handle collection update completion", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT)); - as_log_verbose(ASCollectionLog(), "Update animation finished %{public}@", self.collectionNode); - // Flush any range changes that happened as part of the update animations ending. - [_rangeController updateIfNeeded]; - [self _scheduleCheckForBatchFetchingForNumberOfChanges:numberOfUpdates]; - [changeSet executeCompletionHandlerWithFinished:finished]; - }; - - BOOL shouldReloadData = ASCellLayoutModeIncludes(ASCellLayoutModeAlwaysReloadData); - // TODO: Consider adding !changeSet.isEmpty as a check to also disable shouldReloadData. - if (ASCellLayoutModeIncludes(ASCellLayoutModeAlwaysBatchUpdateSectionReload) && - [changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload].count > 0) { - shouldReloadData = NO; - } - - if (shouldReloadData) { - // When doing a reloadData, the insert / delete calls are not necessary. - // Calling updates() is enough, as it commits .pendingMap to .visibleMap. - [self _superReloadData:updates completion:completion]; - } else { - [self _superPerformBatchUpdates:^{ - updates(); - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeReload]) { - [super reloadItemsAtIndexPaths:change.indexPaths]; - numberOfUpdates++; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload]) { - [super reloadSections:change.indexSet]; - numberOfUpdates++; - } - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeOriginalDelete]) { - [super deleteItemsAtIndexPaths:change.indexPaths]; - numberOfUpdates++; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeOriginalDelete]) { - [super deleteSections:change.indexSet]; - numberOfUpdates++; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeOriginalInsert]) { - [super insertSections:change.indexSet]; - numberOfUpdates++; - } - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeOriginalInsert]) { - [super insertItemsAtIndexPaths:change.indexPaths]; - numberOfUpdates++; - } - } completion:completion]; - } - - as_log_debug(ASCollectionLog(), "Completed batch update %{public}@", self.collectionNode); - - // Flush any range changes that happened as part of submitting the update. - as_activity_scope(changeSet.rootActivity); - [_rangeController updateIfNeeded]; - } - }); -} - -#pragma mark - ASCellNodeDelegate - -- (void)nodeSelectedStateDidChange:(ASCellNode *)node -{ - NSIndexPath *indexPath = [self indexPathForNode:node]; - if (indexPath) { - if (node.isSelected) { - [super selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone]; - } else { - [super deselectItemAtIndexPath:indexPath animated:NO]; - } - } -} - -- (void)nodeHighlightedStateDidChange:(ASCellNode *)node -{ - NSIndexPath *indexPath = [self indexPathForNode:node]; - if (indexPath) { - [self cellForItemAtIndexPath:indexPath].highlighted = node.isHighlighted; - } -} - -- (void)nodeDidInvalidateSize:(ASCellNode *)node -{ - [_cellsForLayoutUpdates addObject:node]; - [self setNeedsLayout]; -} - -- (void)nodesDidRelayout:(NSArray *)nodes -{ - ASDisplayNodeAssertMainThread(); - - if (nodes.count == 0) { - return; - } - - const auto uikitIndexPaths = ASArrayByFlatMapping(nodes, ASCellNode *node, [self indexPathForNode:node]); - - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:uikitIndexPaths batched:NO]; - - ASCollectionViewInvalidationStyle invalidationStyle = _nextLayoutInvalidationStyle; - for (ASCellNode *node in nodes) { - if (invalidationStyle == ASCollectionViewInvalidationStyleNone) { - // We nodesDidRelayout also while we are in layoutSubviews. This should be no problem as CA will ignore this - // call while be in a layout pass - [self setNeedsLayout]; - invalidationStyle = ASCollectionViewInvalidationStyleWithAnimation; - } - - // If we think we're going to animate, check if this node will prevent it. - if (invalidationStyle == ASCollectionViewInvalidationStyleWithAnimation) { - // TODO: Incorporate `shouldAnimateSizeChanges` into ASEnvironmentState for performance benefit. - static dispatch_once_t onceToken; - static BOOL (^shouldNotAnimateBlock)(ASDisplayNode *); - dispatch_once(&onceToken, ^{ - shouldNotAnimateBlock = ^BOOL(ASDisplayNode * _Nonnull node) { - return (node.shouldAnimateSizeChanges == NO); - }; - }); - if (ASDisplayNodeFindFirstNode(node, shouldNotAnimateBlock) != nil) { - // One single non-animated node causes the whole layout update to be non-animated - invalidationStyle = ASCollectionViewInvalidationStyleWithoutAnimation; - break; - } - } - } - _nextLayoutInvalidationStyle = invalidationStyle; -} - -#pragma mark - _ASDisplayView behavior substitutions -// Need these to drive interfaceState so we know when we are visible, if not nested in another range-managing element. -// Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView. -- (void)willMoveToWindow:(UIWindow *)newWindow -{ - BOOL visible = (newWindow != nil); - ASDisplayNode *node = self.collectionNode; - if (visible && !node.inHierarchy) { - [node __enterHierarchy]; - } -} - -- (void)didMoveToWindow -{ - BOOL visible = (self.window != nil); - ASDisplayNode *node = self.collectionNode; - BOOL rangeControllerNeedsUpdate = ![node supportsRangeManagedInterfaceState];; - - if (!visible && node.inHierarchy) { - if (rangeControllerNeedsUpdate) { - rangeControllerNeedsUpdate = NO; - // Exit CellNodes first before Collection to match UIKit behaviors (tear down bottom up). - // Although we have not yet cleared the interfaceState's Visible bit (this happens in __exitHierarchy), - // the ASRangeController will get the correct value from -interfaceStateForRangeController:. - [_rangeController updateRanges]; - } - [node __exitHierarchy]; - } - - // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their - // their update in the layout pass - if (rangeControllerNeedsUpdate) { - [_rangeController updateRanges]; - } - - // When we aren't visible, we will only fetch up to the visible area. Now that we are visible, - // we will fetch visible area + leading screens, so we need to check. - if (visible) { - [self _checkForBatchFetching]; - } -} - -- (void)willMoveToSuperview:(UIView *)newSuperview -{ - if (self.superview == nil && newSuperview != nil) { - _keepalive_node = self.collectionNode; - } -} - -- (void)didMoveToSuperview -{ - if (self.superview == nil) { - _keepalive_node = nil; - } -} - -#pragma mark ASCALayerExtendedDelegate - -/** - * TODO: This code was added when we used @c calculatedSize as the size for - * items (e.g. collectionView:layout:sizeForItemAtIndexPath:) and so it - * was critical that we remeasured all nodes at this time. - * - * The assumption was that cv-bounds-size-change -> constrained-size-change, so - * this was the time when we get new constrained sizes for all items and remeasure - * them. However, the constrained sizes for items can be invalidated for many other - * reasons, hence why we never reuse the old constrained size anymore. - * - * UICollectionView inadvertently triggers a -prepareLayout call to its layout object - * between [super setFrame:] and [self layoutSubviews] during size changes. So we need - * to get in there and re-measure our nodes before that -prepareLayout call. - * We can't wait until -layoutSubviews or the end of -setFrame:. - * - * @see @p testThatNodeCalculatedSizesAreUpdatedBeforeFirstPrepareLayoutAfterRotation - */ -- (void)layer:(CALayer *)layer didChangeBoundsWithOldValue:(CGRect)oldBounds newValue:(CGRect)newBounds -{ - CGSize newSize = newBounds.size; - CGSize lastUsedSize = _lastBoundsSizeUsedForMeasuringNodes; - if (CGSizeEqualToSize(lastUsedSize, newSize)) { - return; - } - if (_hasDataControllerLayoutDelegate || self.collectionViewLayout == nil) { - // Let the layout delegate handle bounds changes if it's available. If no layout, it will init in the new state. - return; - } - - _lastBoundsSizeUsedForMeasuringNodes = newSize; - - // Laying out all nodes is expensive. - // We only need to do this if the bounds changed in the non-scrollable direction. - // If, for example, a vertical flow layout has its height changed due to a status bar - // appearance update, we do not need to relayout all nodes. - // For a more permanent fix to the unsafety mentioned above, see https://github.com/facebook/AsyncDisplayKit/pull/2182 - ASScrollDirection scrollDirection = self.scrollableDirections; - BOOL fixedVertically = (ASScrollDirectionContainsVerticalDirection (scrollDirection) == NO); - BOOL fixedHorizontally = (ASScrollDirectionContainsHorizontalDirection(scrollDirection) == NO); - - BOOL changedInNonScrollingDirection = (fixedHorizontally && newSize.width != lastUsedSize.width) || - (fixedVertically && newSize.height != lastUsedSize.height); - - if (changedInNonScrollingDirection) { - [self relayoutItems]; - } -} - -#pragma mark - UICollectionView dead-end intercepts - -- (void)setPrefetchDataSource:(id)prefetchDataSource -{ - return; -} - -- (void)setPrefetchingEnabled:(BOOL)prefetchingEnabled -{ - return; -} - -#pragma mark - Accessibility overrides - -- (NSArray *)accessibilityElements -{ - [self waitUntilAllUpdatesAreCommitted]; - return [super accessibilityElements]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionViewFlowLayoutInspector.h b/submodules/AsyncDisplayKit/Source/ASCollectionViewFlowLayoutInspector.h deleted file mode 100644 index 0553dcd0b6..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionViewFlowLayoutInspector.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// ASCollectionViewFlowLayoutInspector.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 - -NS_ASSUME_NONNULL_BEGIN - -@class ASCollectionView; -@class UICollectionViewFlowLayout; - -/** - * A layout inspector implementation specific for the sizing behavior of UICollectionViewFlowLayouts - */ -AS_SUBCLASSING_RESTRICTED -@interface ASCollectionViewFlowLayoutInspector : NSObject - -@property (nonatomic, weak, readonly) UICollectionViewFlowLayout *layout; - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionViewFlowLayoutInspector.mm b/submodules/AsyncDisplayKit/Source/ASCollectionViewFlowLayoutInspector.mm deleted file mode 100644 index 6dc9d73bfb..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionViewFlowLayoutInspector.mm +++ /dev/null @@ -1,159 +0,0 @@ -// -// ASCollectionViewFlowLayoutInspector.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 -#import -#import -#import -#import -#import - -#define kDefaultItemSize CGSizeMake(50, 50) - -#pragma mark - ASCollectionViewFlowLayoutInspector - -@interface ASCollectionViewFlowLayoutInspector () -@property (nonatomic, weak) UICollectionViewFlowLayout *layout; -@end - -@implementation ASCollectionViewFlowLayoutInspector { - struct { - unsigned int implementsSizeRangeForHeader:1; - unsigned int implementsReferenceSizeForHeader:1; - unsigned int implementsSizeRangeForFooter:1; - unsigned int implementsReferenceSizeForFooter:1; - unsigned int implementsConstrainedSizeForNodeAtIndexPathDeprecated:1; - unsigned int implementsConstrainedSizeForItemAtIndexPath:1; - } _delegateFlags; -} - -#pragma mark Lifecycle - -- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout; -{ - NSParameterAssert(flowLayout); - - self = [super init]; - if (self != nil) { - _layout = flowLayout; - } - return self; -} - -#pragma mark ASCollectionViewLayoutInspecting - -- (void)didChangeCollectionViewDelegate:(id)delegate; -{ - if (delegate == nil) { - memset(&_delegateFlags, 0, sizeof(_delegateFlags)); - } else { - _delegateFlags.implementsSizeRangeForHeader = [delegate respondsToSelector:@selector(collectionNode:sizeRangeForHeaderInSection:)]; - _delegateFlags.implementsReferenceSizeForHeader = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]; - _delegateFlags.implementsSizeRangeForFooter = [delegate respondsToSelector:@selector(collectionNode:sizeRangeForFooterInSection:)]; - _delegateFlags.implementsReferenceSizeForFooter = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]; - _delegateFlags.implementsConstrainedSizeForNodeAtIndexPathDeprecated = [delegate respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; - _delegateFlags.implementsConstrainedSizeForItemAtIndexPath = [delegate respondsToSelector:@selector(collectionNode:constrainedSizeForItemAtIndexPath:)]; - } -} - -- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath -{ - ASSizeRange result = ASSizeRangeUnconstrained; - if (_delegateFlags.implementsConstrainedSizeForItemAtIndexPath) { - result = [collectionView.asyncDelegate collectionNode:collectionView.collectionNode constrainedSizeForItemAtIndexPath:indexPath]; - } else if (_delegateFlags.implementsConstrainedSizeForNodeAtIndexPathDeprecated) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - result = [collectionView.asyncDelegate collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } else { - // With 2.0 `collectionView:constrainedSizeForNodeAtIndexPath:` was moved to the delegate. Assert if not implemented on the delegate but on the data source - ASDisplayNodeAssert([collectionView.asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)] == NO, @"collectionView:constrainedSizeForNodeAtIndexPath: was moved from the ASCollectionDataSource to the ASCollectionDelegate."); - } - - // If we got no size range: - if (ASSizeRangeEqualToSizeRange(result, ASSizeRangeUnconstrained)) { - // Use itemSize if they set it. - CGSize itemSize = _layout.itemSize; - if (CGSizeEqualToSize(itemSize, kDefaultItemSize) == NO) { - result = ASSizeRangeMake(itemSize, itemSize); - } else { - // Compute constraint from scroll direction otherwise. - result = NodeConstrainedSizeForScrollDirection(collectionView); - } - } - - return result; -} - -- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - ASSizeRange result = ASSizeRangeZero; - if (ASObjectIsEqual(kind, UICollectionElementKindSectionHeader)) { - if (_delegateFlags.implementsSizeRangeForHeader) { - result = [[self delegateForCollectionView:collectionView] collectionNode:collectionView.collectionNode sizeRangeForHeaderInSection:indexPath.section]; - } else if (_delegateFlags.implementsReferenceSizeForHeader) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CGSize exactSize = [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForHeaderInSection:indexPath.section]; -#pragma clang diagnostic pop - result = ASSizeRangeMake(exactSize); - } else { - result = ASSizeRangeMake(_layout.headerReferenceSize); - } - } else if (ASObjectIsEqual(kind, UICollectionElementKindSectionFooter)) { - if (_delegateFlags.implementsSizeRangeForFooter) { - result = [[self delegateForCollectionView:collectionView] collectionNode:collectionView.collectionNode sizeRangeForFooterInSection:indexPath.section]; - } else if (_delegateFlags.implementsReferenceSizeForFooter) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CGSize exactSize = [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForFooterInSection:indexPath.section]; -#pragma clang diagnostic pop - result = ASSizeRangeMake(exactSize); - } else { - result = ASSizeRangeMake(_layout.footerReferenceSize); - } - } else { - ASDisplayNodeFailAssert(@"Unexpected supplementary kind: %@", kind); - return ASSizeRangeZero; - } - - if (_layout.scrollDirection == UICollectionViewScrollDirectionVertical) { - result.min.width = result.max.width = CGRectGetWidth(collectionView.bounds); - } else { - result.min.height = result.max.height = CGRectGetHeight(collectionView.bounds); - } - return result; -} - -- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section -{ - ASSizeRange constraint = [self collectionView:collectionView constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; - if (_layout.scrollDirection == UICollectionViewScrollDirectionVertical) { - return (constraint.max.height > 0 ? 1 : 0); - } else { - return (constraint.max.width > 0 ? 1 : 0); - } -} - -- (ASScrollDirection)scrollableDirections -{ - return (self.layout.scrollDirection == UICollectionViewScrollDirectionHorizontal) ? ASScrollDirectionHorizontalDirections : ASScrollDirectionVerticalDirections; -} - -#pragma mark - Private helpers - -- (id)delegateForCollectionView:(ASCollectionView *)collectionView -{ - return (id)collectionView.asyncDelegate; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionViewLayoutController.h b/submodules/AsyncDisplayKit/Source/ASCollectionViewLayoutController.h deleted file mode 100644 index 9441486b54..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionViewLayoutController.h +++ /dev/null @@ -1,27 +0,0 @@ -// -// ASCollectionViewLayoutController.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 - -NS_ASSUME_NONNULL_BEGIN - -@class ASCollectionView; - -AS_SUBCLASSING_RESTRICTED -@interface ASCollectionViewLayoutController : ASAbstractLayoutController - -- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionViewLayoutController.mm b/submodules/AsyncDisplayKit/Source/ASCollectionViewLayoutController.mm deleted file mode 100644 index 1d7f1ca013..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionViewLayoutController.mm +++ /dev/null @@ -1,130 +0,0 @@ -// -// ASCollectionViewLayoutController.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 - -#import -#import -#import -#import -#import - -struct ASRangeGeometry { - CGRect rangeBounds; - CGRect updateBounds; -}; -typedef struct ASRangeGeometry ASRangeGeometry; - -#pragma mark - -#pragma mark ASCollectionViewLayoutController - -@interface ASCollectionViewLayoutController () -{ - @package - ASCollectionView * __weak _collectionView; - UICollectionViewLayout * __strong _collectionViewLayout; -} -@end - -@implementation ASCollectionViewLayoutController - -- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView -{ - if (!(self = [super init])) { - return nil; - } - - _collectionView = collectionView; - _collectionViewLayout = [collectionView collectionViewLayout]; - return self; -} - -- (NSHashTable *)elementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType map:(ASElementMap *)map -{ - ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType]; - CGRect rangeBounds = [self rangeBoundsWithScrollDirection:scrollDirection rangeTuningParameters:tuningParameters]; - return [self elementsWithinRangeBounds:rangeBounds map:map]; -} - -- (void)allElementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSHashTable *__autoreleasing _Nullable *)displaySet preloadSet:(NSHashTable *__autoreleasing _Nullable *)preloadSet map:(ASElementMap *)map -{ - if (displaySet == NULL || preloadSet == NULL) { - return; - } - - ASRangeTuningParameters displayParams = [self tuningParametersForRangeMode:rangeMode rangeType:ASLayoutRangeTypeDisplay]; - ASRangeTuningParameters preloadParams = [self tuningParametersForRangeMode:rangeMode rangeType:ASLayoutRangeTypePreload]; - CGRect displayBounds = [self rangeBoundsWithScrollDirection:scrollDirection rangeTuningParameters:displayParams]; - CGRect preloadBounds = [self rangeBoundsWithScrollDirection:scrollDirection rangeTuningParameters:preloadParams]; - - CGRect unionBounds = CGRectUnion(displayBounds, preloadBounds); - NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:unionBounds]; - NSInteger count = layoutAttributes.count; - - __auto_type display = [[NSHashTable alloc] initWithOptions:NSHashTableObjectPointerPersonality capacity:count]; - __auto_type preload = [[NSHashTable alloc] initWithOptions:NSHashTableObjectPointerPersonality capacity:count]; - - for (UICollectionViewLayoutAttributes *la in layoutAttributes) { - // Manually filter out elements that don't intersect the range bounds. - // See comment in elementsForItemsWithinRangeBounds: - // This is re-implemented here so that the iteration over layoutAttributes can be done once to check both ranges. - CGRect frame = la.frame; - BOOL intersectsDisplay = CGRectIntersectsRect(displayBounds, frame); - BOOL intersectsPreload = CGRectIntersectsRect(preloadBounds, frame); - if (intersectsDisplay == NO && intersectsPreload == NO && CATransform3DIsIdentity(la.transform3D) == YES) { - // Questionable why the element would be included here, but it doesn't belong. - continue; - } - - // Avoid excessive retains and releases, as well as property calls. We know the element is kept alive by map. - __unsafe_unretained ASCollectionElement *e = [map elementForLayoutAttributes:la]; - if (e != nil && intersectsDisplay) { - [display addObject:e]; - } - if (e != nil && intersectsPreload) { - [preload addObject:e]; - } - } - - *displaySet = display; - *preloadSet = preload; - return; -} - -- (NSHashTable *)elementsWithinRangeBounds:(CGRect)rangeBounds map:(ASElementMap *)map -{ - NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:rangeBounds]; - NSHashTable *elementSet = [[NSHashTable alloc] initWithOptions:NSHashTableObjectPointerPersonality capacity:layoutAttributes.count]; - - for (UICollectionViewLayoutAttributes *la in layoutAttributes) { - // Manually filter out elements that don't intersect the range bounds. - // If a layout returns elements outside the requested rect this can be a huge problem. - // For instance in a paging flow, you may only want to preload 3 pages (one center, one on each side) - // but if flow layout includes the 4th page (which it does! as of iOS 9&10), you will preload a 4th - // page as well. - if (CATransform3DIsIdentity(la.transform3D) && CGRectIntersectsRect(la.frame, rangeBounds) == NO) { - continue; - } - [elementSet addObject:[map elementForLayoutAttributes:la]]; - } - - return elementSet; -} - -- (CGRect)rangeBoundsWithScrollDirection:(ASScrollDirection)scrollDirection - rangeTuningParameters:(ASRangeTuningParameters)tuningParameters -{ - CGRect rect = _collectionView.bounds; - - return CGRectExpandToRangeWithScrollableDirections(rect, tuningParameters, [_collectionView scrollableDirections], scrollDirection); -} - -@end -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionViewLayoutFacilitatorProtocol.h b/submodules/AsyncDisplayKit/Source/ASCollectionViewLayoutFacilitatorProtocol.h deleted file mode 100644 index b60dd0e02f..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionViewLayoutFacilitatorProtocol.h +++ /dev/null @@ -1,48 +0,0 @@ -// -// 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 - -/** - * This facilitator protocol is intended to help Layout to better - * gel with the CollectionView - */ -@protocol ASCollectionViewLayoutFacilitatorProtocol - -/** - * 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 diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionViewLayoutInspector.h b/submodules/AsyncDisplayKit/Source/ASCollectionViewLayoutInspector.h deleted file mode 100644 index be812b68a3..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionViewLayoutInspector.h +++ /dev/null @@ -1,88 +0,0 @@ -// -// ASCollectionViewLayoutInspector.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 -#import -#import - -@class ASCollectionView; -@protocol ASCollectionDataSource; -@protocol ASCollectionDelegate; - -NS_ASSUME_NONNULL_BEGIN - -AS_EXTERN ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView *collectionView); - -@protocol ASCollectionViewLayoutInspecting - -/** - * Asks the inspector to provide a constrained size range for the given collection view node. - */ -- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Return the directions in which your collection view can scroll - */ -- (ASScrollDirection)scrollableDirections; - -@optional - -/** - * Asks the inspector to provide a constrained size range for the given supplementary node. - */ -- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -/** - * Asks the inspector for the number of supplementary views for the given kind in the specified section. - */ -- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section; - -/** - * Allow the inspector to respond to delegate changes. - * - * @discussion A great time to update perform selector caches! - */ -- (void)didChangeCollectionViewDelegate:(nullable id)delegate; - -/** - * Allow the inspector to respond to dataSource changes. - * - * @discussion A great time to update perform selector caches! - */ -- (void)didChangeCollectionViewDataSource:(nullable id)dataSource; - -#pragma mark Deprecated Methods - -/** - * Asks the inspector for the number of supplementary sections in the collection view for the given kind. - * - * @deprecated This method will not be called, and it is only deprecated as a reminder to remove it. - * Supplementary elements must exist in the same sections as regular collection view items i.e. -numberOfSectionsInCollectionView: - */ -- (NSUInteger)collectionView:(ASCollectionView *)collectionView numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead."); - -@end - -/** - * A layout inspector for non-flow layouts that returns a constrained size to let the cells layout itself as - * far as possible based on the scrollable direction of the collection view. - * It doesn't support supplementary nodes and therefore doesn't implement delegate methods - * that are related to supplementary node's management. - * - * @warning This class is not meant to be subclassed and will be restricted in the future. - */ -@interface ASCollectionViewLayoutInspector : NSObject -@end - - -NS_ASSUME_NONNULL_END - -#endif \ No newline at end of file diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionViewLayoutInspector.mm b/submodules/AsyncDisplayKit/Source/ASCollectionViewLayoutInspector.mm deleted file mode 100644 index f02fe1c171..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionViewLayoutInspector.mm +++ /dev/null @@ -1,78 +0,0 @@ -// -// ASCollectionViewLayoutInspector.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 - -#import -#import -#import - -#pragma mark - Helper Functions - -// Returns a constrained size to let the cells layout itself as far as possible based on the scrollable direction -// of the collection view -ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView *collectionView) { - CGSize maxSize = collectionView.bounds.size; - UIEdgeInsets contentInset = collectionView.contentInset; - if (ASScrollDirectionContainsHorizontalDirection(collectionView.scrollableDirections)) { - maxSize.width = CGFLOAT_MAX; - maxSize.height -= (contentInset.top + contentInset.bottom); - } else { - maxSize.width -= (contentInset.left + contentInset.right); - maxSize.height = CGFLOAT_MAX; - } - return ASSizeRangeMake(CGSizeZero, maxSize); -} - -#pragma mark - ASCollectionViewLayoutInspector - -@implementation ASCollectionViewLayoutInspector { - struct { - unsigned int implementsConstrainedSizeForNodeAtIndexPathDeprecated:1; - unsigned int implementsConstrainedSizeForNodeAtIndexPath:1; - } _delegateFlags; -} - -#pragma mark ASCollectionViewLayoutInspecting - -- (void)didChangeCollectionViewDelegate:(id)delegate -{ - if (delegate == nil) { - memset(&_delegateFlags, 0, sizeof(_delegateFlags)); - } else { - _delegateFlags.implementsConstrainedSizeForNodeAtIndexPathDeprecated = [delegate respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; - _delegateFlags.implementsConstrainedSizeForNodeAtIndexPath = [delegate respondsToSelector:@selector(collectionNode:constrainedSizeForItemAtIndexPath:)]; - } -} - -- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath -{ - if (_delegateFlags.implementsConstrainedSizeForNodeAtIndexPath) { - return [collectionView.asyncDelegate collectionNode:collectionView.collectionNode constrainedSizeForItemAtIndexPath:indexPath]; - } else if (_delegateFlags.implementsConstrainedSizeForNodeAtIndexPathDeprecated) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [collectionView.asyncDelegate collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } else { - // With 2.0 `collectionView:constrainedSizeForNodeAtIndexPath:` was moved to the delegate. Assert if not implemented on the delegate but on the data source - ASDisplayNodeAssert([collectionView.asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)] == NO, @"collectionView:constrainedSizeForNodeAtIndexPath: was moved from the ASCollectionDataSource to the ASCollectionDelegate."); - } - - return NodeConstrainedSizeForScrollDirection(collectionView); -} - -- (ASScrollDirection)scrollableDirections -{ - return ASScrollDirectionNone; -} - -@end - -#endif \ No newline at end of file diff --git a/submodules/AsyncDisplayKit/Source/ASCollectionViewProtocols.h b/submodules/AsyncDisplayKit/Source/ASCollectionViewProtocols.h deleted file mode 100644 index 459e12efcd..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCollectionViewProtocols.h +++ /dev/null @@ -1,103 +0,0 @@ -// -// 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 -#import - -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 - -@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 - -@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 diff --git a/submodules/AsyncDisplayKit/Source/ASControlNode+Private.h b/submodules/AsyncDisplayKit/Source/ASControlNode+Private.h deleted file mode 100644 index 02f54a20ec..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASControlNode+Private.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// ASControlNode+Private.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 - -@interface ASControlNode (Private) - -#if TARGET_OS_TV -- (void)_pressDown; -#endif - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASControlNode+Subclasses.h b/submodules/AsyncDisplayKit/Source/ASControlNode+Subclasses.h deleted file mode 100644 index 2e9cdb2849..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASControlNode+Subclasses.h +++ /dev/null @@ -1,68 +0,0 @@ -// -// 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 -#import - -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 diff --git a/submodules/AsyncDisplayKit/Source/ASControlNode.h b/submodules/AsyncDisplayKit/Source/ASControlNode.h deleted file mode 100644 index 0918fdb0b0..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASControlNode.h +++ /dev/null @@ -1,149 +0,0 @@ -// -// 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 - -#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 *)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 diff --git a/submodules/AsyncDisplayKit/Source/ASControlNode.mm b/submodules/AsyncDisplayKit/Source/ASControlNode.mm deleted file mode 100644 index 34bc21ed38..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASControlNode.mm +++ /dev/null @@ -1,516 +0,0 @@ -// -// 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 -#import -#import -#import -#import -#import -#import "Private/ASInternalHelpers.h" -#import -#import -#import - -// 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, NSMutableArray *> *_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 _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 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 eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent); - NSMutableArray *eventTargetActionArray = _controlEventDispatchTable[eventKey]; - if (!eventTargetActionArray) { - return; - } - - NSPredicate *filterPredicate = [NSPredicate predicateWithBlock:^BOOL(ASControlTargetAction *_Nullable evaluatedObject, NSDictionary * _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 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 _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 diff --git a/submodules/AsyncDisplayKit/Source/ASControlTargetAction.h b/submodules/AsyncDisplayKit/Source/ASControlTargetAction.h deleted file mode 100644 index 5a3595c5d3..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASControlTargetAction.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// ASControlTargetAction.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 - -/** - @abstract ASControlTargetAction stores target action pairs registered for specific ASControlNodeEvent values. - */ -@interface ASControlTargetAction : NSObject - -/** - The action to be called on the registered target. - */ -@property (nonatomic) SEL action; - -/** - Event handler target. The specified action will be called on this object. - */ -@property (nonatomic, weak) id target; - -/** - Indicated whether this target was created without a target, so the action should travel up in the responder chain. - */ -@property (nonatomic, readonly) BOOL createdWithNoTarget; - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASControlTargetAction.mm b/submodules/AsyncDisplayKit/Source/ASControlTargetAction.mm deleted file mode 100644 index 49c2314fcc..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASControlTargetAction.mm +++ /dev/null @@ -1,65 +0,0 @@ -// -// ASControlTargetAction.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 "ASControlTargetAction.h" - -@implementation ASControlTargetAction -{ - __weak id _target; - BOOL _createdWithNoTarget; -} - -- (void)setTarget:(id)target { - _target = target; - - if (!target) { - _createdWithNoTarget = YES; - } -} - -- (id)target { - return _target; -} - -- (BOOL)isEqual:(id)object { - if (![object isKindOfClass:[ASControlTargetAction class]]) { - return NO; - } - - ASControlTargetAction *otherObject = (ASControlTargetAction *)object; - - BOOL areTargetsEqual; - - if (self.target != nil && otherObject.target != nil && self.target == otherObject.target) { - areTargetsEqual = YES; - } - else if (self.target == nil && otherObject.target == nil && self.createdWithNoTarget && otherObject.createdWithNoTarget) { - areTargetsEqual = YES; - } - else { - areTargetsEqual = NO; - } - - if (!areTargetsEqual) { - return NO; - } - - if (self.action && otherObject.action && self.action == otherObject.action) { - return YES; - } - else { - return NO; - } -} - -- (NSUInteger)hash { - return [self.target hash]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASCornerLayoutSpec.h b/submodules/AsyncDisplayKit/Source/ASCornerLayoutSpec.h deleted file mode 100644 index 799c9c28c3..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCornerLayoutSpec.h +++ /dev/null @@ -1,75 +0,0 @@ -// -// ASCornerLayoutSpec.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -/** - The corner location for positioning corner element. - */ -typedef NS_ENUM(NSInteger, ASCornerLayoutLocation) { - ASCornerLayoutLocationTopLeft, - ASCornerLayoutLocationTopRight, - ASCornerLayoutLocationBottomLeft, - ASCornerLayoutLocationBottomRight, -}; - -NS_ASSUME_NONNULL_BEGIN - -/** - A layout spec that positions a corner element which relatives to the child element. - - @warning Both child element and corner element must have valid preferredSize for layout calculation. - */ -@interface ASCornerLayoutSpec : ASLayoutSpec - -/** - A layout spec that positions a corner element which relatives to the child element. - - @param child A child that is laid out to determine the size of this spec. - @param corner A layoutElement object that is laid out to a corner on the child. - @param location The corner position option. - @return An ASCornerLayoutSpec object with a given child and an layoutElement that act as corner. - */ -- (instancetype)initWithChild:(id )child corner:(id )corner location:(ASCornerLayoutLocation)location AS_WARN_UNUSED_RESULT; - -/** - A layout spec that positions a corner element which relatives to the child element. - - @param child A child that is laid out to determine the size of this spec. - @param corner A layoutElement object that is laid out to a corner on the child. - @param location The corner position option. - @return An ASCornerLayoutSpec object with a given child and an layoutElement that act as corner. - */ -+ (instancetype)cornerLayoutSpecWithChild:(id )child corner:(id )corner location:(ASCornerLayoutLocation)location NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -/** - A layoutElement object that is laid out to a corner on the child. - */ -@property (nonatomic) id corner; - -/** - The corner position option. - */ -@property (nonatomic) ASCornerLayoutLocation cornerLocation; - -/** - The point which offsets from the corner location. Use this property to make delta - distance from the default corner location. Default is CGPointZero. - */ -@property (nonatomic) CGPoint offset; - -/** - Whether should include corner element into layout size calculation. If included, - the layout size will be the union size of both child and corner; If not included, - the layout size will be only child's size. Default is NO. - */ -@property (nonatomic) BOOL wrapsCorner; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASCornerLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/ASCornerLayoutSpec.mm deleted file mode 100644 index 6663b28935..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASCornerLayoutSpec.mm +++ /dev/null @@ -1,165 +0,0 @@ -// -// ASCornerLayoutSpec.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import - -CGPoint as_calculatedCornerOriginIn(CGRect baseFrame, CGSize cornerSize, ASCornerLayoutLocation cornerLocation, CGPoint offset) -{ - CGPoint cornerOrigin = CGPointZero; - CGPoint baseOrigin = baseFrame.origin; - CGSize baseSize = baseFrame.size; - - switch (cornerLocation) { - case ASCornerLayoutLocationTopLeft: - cornerOrigin.x = baseOrigin.x - cornerSize.width / 2; - cornerOrigin.y = baseOrigin.y - cornerSize.height / 2; - break; - case ASCornerLayoutLocationTopRight: - cornerOrigin.x = baseOrigin.x + baseSize.width - cornerSize.width / 2; - cornerOrigin.y = baseOrigin.y - cornerSize.height / 2; - break; - case ASCornerLayoutLocationBottomLeft: - cornerOrigin.x = baseOrigin.x - cornerSize.width / 2; - cornerOrigin.y = baseOrigin.y + baseSize.height - cornerSize.height / 2; - break; - case ASCornerLayoutLocationBottomRight: - cornerOrigin.x = baseOrigin.x + baseSize.width - cornerSize.width / 2; - cornerOrigin.y = baseOrigin.y + baseSize.height - cornerSize.height / 2; - break; - } - - cornerOrigin.x += offset.x; - cornerOrigin.y += offset.y; - - return cornerOrigin; -} - -static NSUInteger const kBaseChildIndex = 0; -static NSUInteger const kCornerChildIndex = 1; - -@interface ASCornerLayoutSpec() -@end - -@implementation ASCornerLayoutSpec - -- (instancetype)initWithChild:(id )child corner:(id )corner location:(ASCornerLayoutLocation)location -{ - self = [super init]; - if (self) { - self.child = child; - self.corner = corner; - self.cornerLocation = location; - } - return self; -} - -+ (instancetype)cornerLayoutSpecWithChild:(id )child corner:(id )corner location:(ASCornerLayoutLocation)location NS_RETURNS_RETAINED -{ - return [[self alloc] initWithChild:child corner:corner location:location]; -} - -#pragma mark - Children - -- (void)setChild:(id)child -{ - ASDisplayNodeAssertNotNil(child, @"Child shouldn't be nil."); - [super setChild:child atIndex:kBaseChildIndex]; -} - -- (id)child -{ - return [super childAtIndex:kBaseChildIndex]; -} - -- (void)setCorner:(id)corner -{ - ASDisplayNodeAssertNotNil(corner, @"Corner element cannot be nil."); - [super setChild:corner atIndex:kCornerChildIndex]; -} - -- (id)corner -{ - return [super childAtIndex:kCornerChildIndex]; -} - -#pragma mark - Calculation - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - CGSize size = { - ASPointsValidForSize(constrainedSize.max.width) == NO ? ASLayoutElementParentDimensionUndefined : constrainedSize.max.width, - ASPointsValidForSize(constrainedSize.max.height) == NO ? ASLayoutElementParentDimensionUndefined : constrainedSize.max.height - }; - - id child = self.child; - id corner = self.corner; - - // Element validation - [self _validateElement:child]; - [self _validateElement:corner]; - - CGRect childFrame = CGRectZero; - CGRect cornerFrame = CGRectZero; - - // Layout child - ASLayout *childLayout = [child layoutThatFits:constrainedSize parentSize:size]; - childFrame.size = childLayout.size; - - // Layout corner - ASLayout *cornerLayout = [corner layoutThatFits:constrainedSize parentSize:size]; - cornerFrame.size = cornerLayout.size; - - // Calculate corner's position - CGPoint relativePosition = as_calculatedCornerOriginIn(childFrame, cornerFrame.size, _cornerLocation, _offset); - - // Update corner's position - cornerFrame.origin = relativePosition; - - // Calculate size - CGRect frame = childFrame; - if (_wrapsCorner) { - frame = CGRectUnion(childFrame, cornerFrame); - frame.size = ASSizeRangeClamp(constrainedSize, frame.size); - } - - // Shift sublayouts' positions if they are off the bounds. - if (frame.origin.x != 0) { - CGFloat deltaX = frame.origin.x; - childFrame.origin.x -= deltaX; - cornerFrame.origin.x -= deltaX; - } - - if (frame.origin.y != 0) { - CGFloat deltaY = frame.origin.y; - childFrame.origin.y -= deltaY; - cornerFrame.origin.y -= deltaY; - } - - childLayout.position = childFrame.origin; - cornerLayout.position = cornerFrame.origin; - - return [ASLayout layoutWithLayoutElement:self size:frame.size sublayouts:@[childLayout, cornerLayout]]; -} - -- (void)_validateElement:(id )element -{ - // Validate non-nil element - if (element == nil) { - ASDisplayNodeAssertNotNil(element, @"[%@]: Must have a non-nil child/corner for layout calculation.", self.class); - } - // Validate preferredSize if needed - CGSize size = element.style.preferredSize; - if (!CGSizeEqualToSize(size, CGSizeZero) && !ASIsCGSizeValidForSize(size) && (size.width < 0 || (size.height < 0))) { - ASDisplayNodeFailAssert(@"[%@]: Should give a valid preferredSize value for %@ before corner's position calculation.", self.class, element); - } -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASDataController.h b/submodules/AsyncDisplayKit/Source/ASDataController.h deleted file mode 100644 index 5a03f24e23..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDataController.h +++ /dev/null @@ -1,291 +0,0 @@ -// -// ASDataController.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 -#import -#import -#import -#ifdef __cplusplus -#import -#endif - -NS_ASSUME_NONNULL_BEGIN - -#if ASEVENTLOG_ENABLE -#define ASDataControllerLogEvent(dataController, ...) [dataController.eventLog logEventWithBacktrace:(AS_SAVE_EVENT_BACKTRACES ? [NSThread callStackSymbols] : nil) format:__VA_ARGS__] -#else -#define ASDataControllerLogEvent(dataController, ...) -#endif - -@class ASCellNode; -@class ASCollectionElement; -@class ASCollectionLayoutContext; -@class ASCollectionLayoutState; -@class ASDataController; -@class ASElementMap; -@class ASLayout; -@class _ASHierarchyChangeSet; -@protocol ASRangeManagingNode; -@protocol ASTraitEnvironment; -@protocol ASSectionContext; - -typedef NSUInteger ASDataControllerAnimationOptions; - -AS_EXTERN NSString * const ASDataControllerRowNodeKind; -AS_EXTERN NSString * const ASCollectionInvalidUpdateException; - -/** - Data source for data controller - It will be invoked in the same thread as the api call of ASDataController. - */ - -@protocol ASDataControllerSource - -/** - Fetch the ASCellNode block for specific index path. This block should return the ASCellNode for the specified index path. - */ -- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath shouldAsyncLayout:(BOOL *)shouldAsyncLayout; - -/** - Fetch the number of rows in specific section. - */ -- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section; - -/** - Fetch the number of sections. - */ -- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController; - -/** - Returns if the collection element size matches a given size. - @precondition The element is present in the data controller's visible map. - */ -- (BOOL)dataController:(ASDataController *)dataController presentedSizeForElement:(ASCollectionElement *)element matchesSize:(CGSize)size; - -- (nullable id)dataController:(ASDataController *)dataController nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Called just after dispatching ASCellNode allocation and layout to the concurrent background queue. - * In some cases, for example on the first content load for a screen, it may be desirable to call - * -waitUntilAllUpdatesAreProcessed at this point. - * - * Returning YES 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. - */ -- (BOOL)dataController:(ASDataController *)dataController shouldSynchronouslyProcessChangeSet:(_ASHierarchyChangeSet *)changeSet; -- (BOOL)dataController:(ASDataController *)dataController shouldEagerlyLayoutNode:(ASCellNode *)node; -- (BOOL)dataControllerShouldSerializeNodeCreation:(ASDataController *)dataController; - -@optional - -/** - The constrained size range for layout. Called only if collection layout delegate is not provided. - */ -- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath; - -- (NSArray *)dataController:(ASDataController *)dataController supplementaryNodeKindsInSections:(NSIndexSet *)sections; - -- (NSUInteger)dataController:(ASDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section; - -- (ASCellNodeBlock)dataController:(ASDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath shouldAsyncLayout:(BOOL *)shouldAsyncLayout; - -/** - The constrained size range for layout. Called only if no data controller layout delegate is provided. - */ -- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; - -- (nullable id)dataController:(ASDataController *)dataController contextForSection:(NSInteger)section; - -@end - -/** - Delegate for notify the data updating of data controller. - These methods will be invoked from main thread right now, but it may be moved to background thread in the future. - */ -@protocol ASDataControllerDelegate - -/** - * Called for change set updates. - * - * @param changeSet The change set that includes all updates - * - * @param updates The block that performs relevant data updates. - * - * @discussion The updates block must always be executed or the data controller will get into a bad state. - * It should be called at the time the backing view is ready to process the updates, - * i.e inside the updates block of `-[UICollectionView performBatchUpdates:completion:] or after calling `-[UITableView beginUpdates]`. - */ -- (void)dataController:(ASDataController *)dataController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates; - -@end - -@protocol ASDataControllerLayoutDelegate - -/** - * @abstract Returns a layout context needed for a coming layout pass with the given elements. - * The context should contain the elements and any additional information needed. - * - * @discussion This method will be called on main thread. - */ -- (ASCollectionLayoutContext *)layoutContextWithElements:(ASElementMap *)elements; - -/** - * @abstract Prepares and returns a new layout for given context. - * - * @param context A context that was previously returned by `-layoutContextWithElements:`. - * - * @return The new layout calculated for the given context. - * - * @discussion This method is called ahead of time, i.e before the underlying collection/table view is aware of the provided elements. - * As a result, clients must solely rely on the given context and should not reach out to other objects for information not available in the context. - * - * This method will be called on background theads. It must be thread-safe and should not change any internal state of the conforming object. - * It must block the calling thread but can dispatch to other theads to reduce total blocking time. - */ -+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context; - -@end - -/** - * Controller to layout data in background, and managed data updating. - * - * All operations are asynchronous and thread safe. You can call it from background thread (it is recommendated) and the data - * will be updated asynchronously. The dataSource must be updated to reflect the changes before these methods has been called. - * For each data updating, the corresponding methods in delegate will be called. - */ -@interface ASDataController : NSObject - -- (instancetype)initWithDataSource:(id)dataSource node:(nullable id)node eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; - -/** - * The node that owns this data controller, if any. - * - * NOTE: Soon we will drop support for using ASTableView/ASCollectionView without the node, so this will be non-null. - */ -@property (nullable, nonatomic, weak, readonly) id node; - -/** - * The map that is currently displayed. The "UIKit index space." - * - * This property will only be changed on the main thread. - */ -@property (copy, readonly) ASElementMap *visibleMap; - -/** - * The latest map fetched from the data source. May be more recent than @c visibleMap. - * - * This property will only be changed on the main thread. - */ -@property (copy, readonly) ASElementMap *pendingMap; - -/** - Data source for fetching data info. - */ -@property (nonatomic, weak, readonly) id dataSource; - -/** - An object that will be included in the backtrace of any update validation exceptions that occur. - */ -@property (nonatomic, weak) id validationErrorSource; - -/** - Delegate to notify when data is updated. - */ -@property (nonatomic, weak) id delegate; - -/** - * Delegate for preparing layouts. Main thead only. - */ -@property (nonatomic, weak) id layoutDelegate; - -#ifdef __cplusplus -/** - * Returns the most recently gathered item counts from the data source. If the counts - * have been invalidated, this synchronously queries the data source and saves the result. - * - * This must be called on the main thread. - */ -- (std::vector)itemCountsFromDataSource; -#endif - -/** - * Returns YES if reloadData has been called at least once. Before this point it is - * important to ignore/suppress some operations. For example, inserting a section - * before the initial data load should have no effect. - * - * This must be called on the main thread. - */ -@property (nonatomic, readonly) BOOL initialReloadDataHasBeenCalled; - -#if ASEVENTLOG_ENABLE -/* - * @abstract The primitive event tracing object. You shouldn't directly use it to log event. Use the ASDataControllerLogEvent macro instead. - */ -@property (nonatomic, readonly) ASEventLog *eventLog; -#endif - -/** @name Data Updating */ - -- (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet; - -/** - * Re-measures all loaded nodes in the backing store. - * - * @discussion Used to respond to a change in size of the containing view - * (e.g. ASTableView or ASCollectionView after an orientation change). - * - * The invalidationBlock is called after flushing the ASMainSerialQueue, which ensures that any in-progress - * layout calculations have been applied. The block will not be called if data hasn't been loaded. - */ -- (void)relayoutAllNodesWithInvalidationBlock:(nullable void (^)(void))invalidationBlock; - -/** - * Re-measures given nodes in the backing store. - * - * @discussion Used to respond to setNeedsLayout calls in ASCellNode - */ -- (void)relayoutNodes:(id)nodes nodesSizeChanged:(NSMutableArray *)nodesSizesChanged; - -/** - * See ASCollectionNode.h for full documentation of these methods. - */ -@property (nonatomic, readonly) BOOL isProcessingUpdates; -- (void)onDidFinishProcessingUpdates:(void (^)(void))completion; -- (void)waitUntilAllUpdatesAreProcessed; - -/** - * See ASCollectionNode.h for full documentation of these methods. - */ -@property (nonatomic, readonly, getter=isSynchronized) BOOL synchronized; -- (void)onDidFinishSynchronizing:(void (^)(void))completion; - -/** - * Notifies the data controller object that its environment has changed. The object will request its environment delegate for new information - * and propagate the information to all visible elements, including ones that are being prepared in background. - * - * @discussion If called before the initial @c reloadData, this method will do nothing and the trait collection of the initial load will be requested from the environment delegate. - * - * @discussion This method can be called on any threads. - */ -- (void)environmentDidChange; - -/** - * Reset visibleMap and pendingMap when asyncDataSource and asyncDelegate of collection view become nil. - */ -- (void)clearData; - -@end - -NS_ASSUME_NONNULL_END -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASDataController.mm b/submodules/AsyncDisplayKit/Source/ASDataController.mm deleted file mode 100644 index b29ad7c155..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDataController.mm +++ /dev/null @@ -1,958 +0,0 @@ -// -// ASDataController.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 - -#include - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#import "Private/ASInternalHelpers.h" -#import -#import -#import - -//#define LOG(...) NSLog(__VA_ARGS__) -#define LOG(...) - -#define ASSERT_ON_EDITING_QUEUE ASDisplayNodeAssertNotNil(dispatch_get_specific(&kASDataControllerEditingQueueKey), @"%@ must be called on the editing transaction queue.", NSStringFromSelector(_cmd)) - -const static char * kASDataControllerEditingQueueKey = "kASDataControllerEditingQueueKey"; -const static char * kASDataControllerEditingQueueContext = "kASDataControllerEditingQueueContext"; - -NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; -NSString * const ASCollectionInvalidUpdateException = @"ASCollectionInvalidUpdateException"; - -typedef dispatch_block_t ASDataControllerCompletionBlock; - -typedef void (^ASDataControllerSynchronizationBlock)(); - -@interface ASDataController () { - id _layoutDelegate; - - NSInteger _nextSectionID; - - BOOL _itemCountsFromDataSourceAreValid; // Main thread only. - std::vector _itemCountsFromDataSource; // Main thread only. - - ASMainSerialQueue *_mainSerialQueue; - - dispatch_queue_t _editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. - dispatch_group_t _editingTransactionGroup; // Group of all edit transaction blocks. Useful for waiting. - std::atomic _editingTransactionGroupCount; - - BOOL _initialReloadDataHasBeenCalled; - - BOOL _synchronized; - NSMutableSet *_onDidFinishSynchronizingBlocks; - - struct { - unsigned int supplementaryNodeKindsInSections:1; - unsigned int supplementaryNodesOfKindInSection:1; - unsigned int supplementaryNodeBlockOfKindAtIndexPath:1; - unsigned int constrainedSizeForNodeAtIndexPath:1; - unsigned int constrainedSizeForSupplementaryNodeOfKindAtIndexPath:1; - unsigned int contextForSection:1; - } _dataSourceFlags; -} - -@property (copy) ASElementMap *pendingMap; -@property (copy) ASElementMap *visibleMap; -@end - -@implementation ASDataController - -#pragma mark - Lifecycle - -- (instancetype)initWithDataSource:(id)dataSource node:(nullable id)node eventLog:(ASEventLog *)eventLog -{ - if (!(self = [super init])) { - return nil; - } - - _node = node; - _dataSource = dataSource; - - _dataSourceFlags.supplementaryNodeKindsInSections = [_dataSource respondsToSelector:@selector(dataController:supplementaryNodeKindsInSections:)]; - _dataSourceFlags.supplementaryNodesOfKindInSection = [_dataSource respondsToSelector:@selector(dataController:supplementaryNodesOfKind:inSection:)]; - _dataSourceFlags.supplementaryNodeBlockOfKindAtIndexPath = [_dataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:shouldAsyncLayout:)]; - _dataSourceFlags.constrainedSizeForNodeAtIndexPath = [_dataSource respondsToSelector:@selector(dataController:constrainedSizeForNodeAtIndexPath:)]; - _dataSourceFlags.constrainedSizeForSupplementaryNodeOfKindAtIndexPath = [_dataSource respondsToSelector:@selector(dataController:constrainedSizeForSupplementaryNodeOfKind:atIndexPath:)]; - _dataSourceFlags.contextForSection = [_dataSource respondsToSelector:@selector(dataController:contextForSection:)]; - -#if ASEVENTLOG_ENABLE - _eventLog = eventLog; -#endif - - self.visibleMap = self.pendingMap = [[ASElementMap alloc] init]; - - _nextSectionID = 0; - - _mainSerialQueue = [[ASMainSerialQueue alloc] init]; - - _synchronized = YES; - _onDidFinishSynchronizingBlocks = [[NSMutableSet alloc] init]; - - const char *queueName = [[NSString stringWithFormat:@"org.AsyncDisplayKit.ASDataController.editingTransactionQueue:%p", self] cStringUsingEncoding:NSASCIIStringEncoding]; - _editingTransactionQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL); - dispatch_queue_set_specific(_editingTransactionQueue, &kASDataControllerEditingQueueKey, &kASDataControllerEditingQueueContext, NULL); - _editingTransactionGroup = dispatch_group_create(); - - return self; -} - -- (id)layoutDelegate -{ - ASDisplayNodeAssertMainThread(); - return _layoutDelegate; -} - -- (void)setLayoutDelegate:(id)layoutDelegate -{ - ASDisplayNodeAssertMainThread(); - if (layoutDelegate != _layoutDelegate) { - _layoutDelegate = layoutDelegate; - } -} - -#pragma mark - Cell Layout - -- (void)_allocateNodesFromElements:(NSArray *)elements -{ - ASSERT_ON_EDITING_QUEUE; - - NSUInteger nodeCount = elements.count; - __weak id weakDataSource = _dataSource; - if (nodeCount == 0 || weakDataSource == nil) { - return; - } - - ASSignpostStart(ASSignpostDataControllerBatch); - - { - as_activity_create_for_scope("Data controller batch"); - - dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0); - NSUInteger threadCount = 0; - if ([_dataSource dataControllerShouldSerializeNodeCreation:self]) { - threadCount = 1; - } - ASDispatchApply(nodeCount, queue, threadCount, ^(size_t i) { - __strong id strongDataSource = weakDataSource; - if (strongDataSource == nil) { - return; - } - - unowned ASCollectionElement *element = elements[i]; - - NSMutableDictionary *dict = [[NSThread currentThread] threadDictionary]; - dict[ASThreadDictMaxConstraintSizeKey] = - [NSValue valueWithCGSize:element.constrainedSize.max]; - unowned ASCellNode *node = element.node; - [dict removeObjectForKey:ASThreadDictMaxConstraintSizeKey]; - - // Layout the node if the size range is valid. - ASSizeRange sizeRange = element.constrainedSize; - if (ASSizeRangeHasSignificantArea(sizeRange)) { - [self _layoutNode:node withConstrainedSize:sizeRange]; - } - }); - } - - ASSignpostEndCustom(ASSignpostDataControllerBatch, self, 0, (weakDataSource != nil ? ASSignpostColorDefault : ASSignpostColorRed)); -} - -/** - * Measure and layout the given node with the constrained size range. - */ -- (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrainedSize -{ - if (![_dataSource dataController:self shouldEagerlyLayoutNode:node]) { - return; - } - - ASDisplayNodeAssert(ASSizeRangeHasSignificantArea(constrainedSize), @"Attempt to layout cell node with invalid size range %@", NSStringFromASSizeRange(constrainedSize)); - - CGRect frame = CGRectZero; - frame.size = [node layoutThatFits:constrainedSize].size; - node.frame = frame; -} - -#pragma mark - Data Source Access (Calling _dataSource) - -- (NSArray *)_allIndexPathsForItemsOfKind:(NSString *)kind inSections:(NSIndexSet *)sections -{ - ASDisplayNodeAssertMainThread(); - - if (sections.count == 0 || _dataSource == nil) { - return @[]; - } - - const auto indexPaths = [[NSMutableArray alloc] init]; - if ([kind isEqualToString:ASDataControllerRowNodeKind]) { - std::vector counts = [self itemCountsFromDataSource]; - [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - for (NSUInteger sectionIndex = range.location; sectionIndex < NSMaxRange(range); sectionIndex++) { - NSUInteger itemCount = counts[sectionIndex]; - for (NSUInteger i = 0; i < itemCount; i++) { - [indexPaths addObject:[NSIndexPath indexPathForItem:i inSection:sectionIndex]]; - } - } - }]; - } else if (_dataSourceFlags.supplementaryNodesOfKindInSection) { - id dataSource = _dataSource; - [sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) { - for (NSUInteger sectionIndex = range.location; sectionIndex < NSMaxRange(range); sectionIndex++) { - NSUInteger itemCount = [dataSource dataController:self supplementaryNodesOfKind:kind inSection:sectionIndex]; - for (NSUInteger i = 0; i < itemCount; i++) { - [indexPaths addObject:[NSIndexPath indexPathForItem:i inSection:sectionIndex]]; - } - } - }]; - } - - return indexPaths; -} - -/** - * Agressively repopulates supplementary nodes of all kinds for sections that contains some given index paths. - * - * @param map The element map into which to apply the change. - * @param indexPaths The index paths belongs to sections whose supplementary nodes need to be repopulated. - * @param changeSet The changeset that triggered this repopulation. - * @param traitCollection The trait collection needed to initialize elements - * @param indexPathsAreNew YES if index paths are "after the update," NO otherwise. - * @param shouldFetchSizeRanges Whether constrained sizes should be fetched from data source - */ -- (void)_repopulateSupplementaryNodesIntoMap:(ASMutableElementMap *)map - forSectionsContainingIndexPaths:(NSArray *)indexPaths - changeSet:(_ASHierarchyChangeSet *)changeSet - traitCollection:(ASPrimitiveTraitCollection)traitCollection - indexPathsAreNew:(BOOL)indexPathsAreNew - shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges - previousMap:(ASElementMap *)previousMap -{ - ASDisplayNodeAssertMainThread(); - - if (indexPaths.count == 0) { - return; - } - - // Remove all old supplementaries from these sections - NSIndexSet *oldSections = [NSIndexSet as_sectionsFromIndexPaths:indexPaths]; - - // Add in new ones with the new kinds. - NSIndexSet *newSections; - if (indexPathsAreNew) { - newSections = oldSections; - } else { - newSections = [oldSections as_indexesByMapping:^NSUInteger(NSUInteger oldSection) { - return [changeSet newSectionForOldSection:oldSection]; - }]; - } - - for (NSString *kind in [self supplementaryKindsInSections:newSections]) { - [self _insertElementsIntoMap:map kind:kind forSections:newSections traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges changeSet:changeSet previousMap:previousMap]; - } -} - -/** - * Update supplementary nodes of all kinds for sections. - * - * @param map The element map into which to apply the change. - * @param traitCollection The trait collection needed to initialize elements - * @param shouldFetchSizeRanges Whether constrained sizes should be fetched from data source - */ -- (void)_updateSupplementaryNodesIntoMap:(ASMutableElementMap *)map - traitCollection:(ASPrimitiveTraitCollection)traitCollection - shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges - previousMap:(ASElementMap *)previousMap -{ - ASDisplayNodeAssertMainThread(); - if (self.layoutDelegate != nil) { - // TODO: https://github.com/TextureGroup/Texture/issues/948 - return; - } - NSUInteger sectionCount = [self itemCountsFromDataSource].size(); - if (sectionCount > 0) { - NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; - ASSizeRange newSizeRange = ASSizeRangeZero; - for (NSString *kind in [self supplementaryKindsInSections:sectionIndexes]) { - NSArray *indexPaths = [self _allIndexPathsForItemsOfKind:kind inSections:sectionIndexes]; - NSMutableArray *indexPathsToDeleteForKind = [[NSMutableArray alloc] init]; - NSMutableArray *indexPathsToInsertForKind = [[NSMutableArray alloc] init]; - // If supplementary node does exist and size is now zero, remove it. - // If supplementary node doesn't exist and size is now non-zero, insert one. - for (NSIndexPath *indexPath in indexPaths) { - ASCollectionElement *previousElement = [previousMap supplementaryElementOfKind:kind atIndexPath:indexPath]; - newSizeRange = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - BOOL sizeRangeIsZero = ASSizeRangeEqualToSizeRange(ASSizeRangeZero, newSizeRange); - if (previousElement != nil && sizeRangeIsZero) { - [indexPathsToDeleteForKind addObject:indexPath]; - } else if (previousElement == nil && !sizeRangeIsZero) { - [indexPathsToInsertForKind addObject:indexPath]; - } - } - - [map removeSupplementaryElementsAtIndexPaths:indexPathsToDeleteForKind kind:kind]; - [self _insertElementsIntoMap:map kind:kind atIndexPaths:indexPathsToInsertForKind traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges changeSet:nil previousMap:previousMap]; - } - } -} - -/** - * Inserts new elements of a certain kind for some sections - * - * @param kind The kind of the elements, e.g ASDataControllerRowNodeKind - * @param sections The sections that should be populated by new elements - * @param traitCollection The trait collection needed to initialize elements - * @param shouldFetchSizeRanges Whether constrained sizes should be fetched from data source - */ -- (void)_insertElementsIntoMap:(ASMutableElementMap *)map - kind:(NSString *)kind - forSections:(NSIndexSet *)sections - traitCollection:(ASPrimitiveTraitCollection)traitCollection - shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges - changeSet:(_ASHierarchyChangeSet *)changeSet - previousMap:(ASElementMap *)previousMap -{ - ASDisplayNodeAssertMainThread(); - - if (sections.count == 0 || _dataSource == nil) { - return; - } - - NSArray *indexPaths = [self _allIndexPathsForItemsOfKind:kind inSections:sections]; - [self _insertElementsIntoMap:map kind:kind atIndexPaths:indexPaths traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges changeSet:changeSet previousMap:previousMap]; -} - -/** - * Inserts new elements of a certain kind at some index paths - * - * @param map The map to insert the elements into. - * @param kind The kind of the elements, e.g ASDataControllerRowNodeKind - * @param indexPaths The index paths at which new elements should be populated - * @param traitCollection The trait collection needed to initialize elements - * @param shouldFetchSizeRanges Whether constrained sizes should be fetched from data source - */ -- (void)_insertElementsIntoMap:(ASMutableElementMap *)map - kind:(NSString *)kind - atIndexPaths:(NSArray *)indexPaths - traitCollection:(ASPrimitiveTraitCollection)traitCollection - shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges - changeSet:(_ASHierarchyChangeSet *)changeSet - previousMap:(ASElementMap *)previousMap -{ - ASDisplayNodeAssertMainThread(); - - if (indexPaths.count == 0 || _dataSource == nil) { - return; - } - - BOOL isRowKind = [kind isEqualToString:ASDataControllerRowNodeKind]; - if (!isRowKind && !_dataSourceFlags.supplementaryNodeBlockOfKindAtIndexPath) { - // Populating supplementary elements but data source doesn't support. - return; - } - - LOG(@"Populating elements of kind: %@, for index paths: %@", kind, indexPaths); - id dataSource = self.dataSource; - id node = self.node; - BOOL shouldAsyncLayout = YES; - for (NSIndexPath *indexPath in indexPaths) { - ASCellNodeBlock nodeBlock; - id nodeModel; - if (isRowKind) { - nodeModel = [dataSource dataController:self nodeModelForItemAtIndexPath:indexPath]; - - // Get the prior element and attempt to update the existing cell node. - if (nodeModel != nil && !changeSet.includesReloadData) { - NSIndexPath *oldIndexPath = [changeSet oldIndexPathForNewIndexPath:indexPath]; - if (oldIndexPath != nil) { - ASCollectionElement *oldElement = [previousMap elementForItemAtIndexPath:oldIndexPath]; - ASCellNode *oldNode = oldElement.node; - if ([oldNode canUpdateToNodeModel:nodeModel]) { - // Just wrap the node in a block. The collection element will -setNodeModel: - nodeBlock = ^{ - return oldNode; - }; - } - } - } - if (nodeBlock == nil) { - nodeBlock = [dataSource dataController:self nodeBlockAtIndexPath:indexPath shouldAsyncLayout:&shouldAsyncLayout]; - } - } else { - nodeBlock = [dataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath shouldAsyncLayout:&shouldAsyncLayout]; - } - - ASSizeRange constrainedSize = ASSizeRangeUnconstrained; - if (shouldFetchSizeRanges) { - constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath]; - } - - ASCollectionElement *element = [[ASCollectionElement alloc] initWithNodeModel:nodeModel - nodeBlock:nodeBlock - supplementaryElementKind:isRowKind ? nil : kind - constrainedSize:constrainedSize - owningNode:node - traitCollection:traitCollection]; - [map insertElement:element atIndexPath:indexPath]; - changeSet.countForAsyncLayout += (shouldAsyncLayout ? 1 : 0); - } -} - -- (void)invalidateDataSourceItemCounts -{ - ASDisplayNodeAssertMainThread(); - _itemCountsFromDataSourceAreValid = NO; -} - -- (std::vector)itemCountsFromDataSource -{ - ASDisplayNodeAssertMainThread(); - if (NO == _itemCountsFromDataSourceAreValid) { - id source = self.dataSource; - NSInteger sectionCount = [source numberOfSectionsInDataController:self]; - std::vector newCounts; - newCounts.reserve(sectionCount); - for (NSInteger i = 0; i < sectionCount; i++) { - newCounts.push_back([source dataController:self rowsInSection:i]); - } - _itemCountsFromDataSource = newCounts; - _itemCountsFromDataSourceAreValid = YES; - } - return _itemCountsFromDataSource; -} - -- (NSArray *)supplementaryKindsInSections:(NSIndexSet *)sections -{ - if (_dataSourceFlags.supplementaryNodeKindsInSections) { - return [_dataSource dataController:self supplementaryNodeKindsInSections:sections]; - } - - return @[]; -} - -/** - * Returns constrained size for the node of the given kind and at the given index path. - * NOTE: index path must be in the data-source index space. - */ -- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - - id dataSource = _dataSource; - if (dataSource == nil || indexPath == nil) { - return ASSizeRangeZero; - } - - if ([kind isEqualToString:ASDataControllerRowNodeKind]) { - ASDisplayNodeAssert(_dataSourceFlags.constrainedSizeForNodeAtIndexPath, @"-dataController:constrainedSizeForNodeAtIndexPath: must also be implemented"); - return [dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath]; - } - - if (_dataSourceFlags.constrainedSizeForSupplementaryNodeOfKindAtIndexPath){ - return [dataSource dataController:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath]; - } - - ASDisplayNodeAssert(NO, @"Unknown constrained size for node of kind %@ by data source %@", kind, dataSource); - return ASSizeRangeZero; -} - -#pragma mark - Batching (External API) - -- (void)waitUntilAllUpdatesAreProcessed -{ - // Schedule block in main serial queue to wait until all operations are finished that are - // where scheduled while waiting for the _editingTransactionQueue to finish - [self _scheduleBlockOnMainSerialQueue:^{ }]; -} - -- (BOOL)isProcessingUpdates -{ - ASDisplayNodeAssertMainThread(); - return _mainSerialQueue.numberOfScheduledBlocks > 0 || _editingTransactionGroupCount > 0; -} - -- (void)onDidFinishProcessingUpdates:(void (^)())completion -{ - ASDisplayNodeAssertMainThread(); - if (!completion) { - return; - } - if ([self isProcessingUpdates] == NO) { - ASPerformBlockOnMainThread(completion); - } else { - dispatch_async(_editingTransactionQueue, ^{ - // Retry the block. If we're done processing updates, it'll run immediately, otherwise - // wait again for updates to quiesce completely. - // Don't use _mainSerialQueue so that we don't affect -isProcessingUpdates. - dispatch_async(dispatch_get_main_queue(), ^{ - [self onDidFinishProcessingUpdates:completion]; - }); - }); - } -} - -- (BOOL)isSynchronized { - return _synchronized; -} - -- (void)onDidFinishSynchronizing:(void (^)())completion { - ASDisplayNodeAssertMainThread(); - if (!completion) { - return; - } - if ([self isSynchronized]) { - ASPerformBlockOnMainThread(completion); - } else { - // Hang on to the completion block so that it gets called the next time view is synchronized to data. - [_onDidFinishSynchronizingBlocks addObject:[completion copy]]; - } -} - -- (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet -{ - ASDisplayNodeAssertMainThread(); - - _synchronized = NO; - - [changeSet addCompletionHandler:^(BOOL finished) { - _synchronized = YES; - [self onDidFinishProcessingUpdates:^{ - if (_synchronized) { - for (ASDataControllerSynchronizationBlock block in _onDidFinishSynchronizingBlocks) { - block(); - } - [_onDidFinishSynchronizingBlocks removeAllObjects]; - } - }]; - }]; - - if (changeSet.includesReloadData) { - if (_initialReloadDataHasBeenCalled) { - as_log_debug(ASCollectionLog(), "reloadData %@", ASViewToDisplayNode(ASDynamicCast(self.dataSource, UIView))); - } else { - as_log_debug(ASCollectionLog(), "Initial reloadData %@", ASViewToDisplayNode(ASDynamicCast(self.dataSource, UIView))); - _initialReloadDataHasBeenCalled = YES; - } - } else { - as_log_debug(ASCollectionLog(), "performBatchUpdates %@ %@", ASViewToDisplayNode(ASDynamicCast(self.dataSource, UIView)), changeSet); - } - - NSTimeInterval transactionQueueFlushDuration = 0.0f; - { - AS::ScopeTimer t(transactionQueueFlushDuration); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - } - - // 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 (!_initialReloadDataHasBeenCalled) { - as_log_debug(ASCollectionLog(), "%@ Skipped update because load hasn't happened.", ASObjectDescriptionMakeTiny(_dataSource)); - [changeSet executeCompletionHandlerWithFinished:YES]; - return; - } - - [self invalidateDataSourceItemCounts]; - - // Log events -#if ASEVENTLOG_ENABLE - ASDataControllerLogEvent(self, @"updateWithChangeSet waited on previous update for %fms. changeSet: %@", - transactionQueueFlushDuration * 1000.0f, changeSet); - NSTimeInterval changeSetStartTime = CACurrentMediaTime(); - NSString *changeSetDescription = ASObjectDescriptionMakeTiny(changeSet); - [changeSet addCompletionHandler:^(BOOL finished) { - ASDataControllerLogEvent(self, @"finishedUpdate in %fms: %@", - (CACurrentMediaTime() - changeSetStartTime) * 1000.0f, changeSetDescription); - }]; -#endif - - // 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; - } - } - - BOOL canDelegate = (self.layoutDelegate != nil); - ASElementMap *newMap; - ASCollectionLayoutContext *layoutContext; - { - as_activity_scope(as_activity_create("Latch new data for collection update", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT)); - - // Step 1: Populate a new map that reflects the data source's state and use it as pendingMap - ASElementMap *previousMap = self.pendingMap; - if (changeSet.isEmpty) { - // If the change set is empty, nothing has changed so we can just reuse the previous map - newMap = previousMap; - } else { - // Mutable copy of current data. - ASMutableElementMap *mutableMap = [previousMap mutableCopy]; - - // Step 1.1: Update the mutable copies to match the data source's state - [self _updateSectionsInMap:mutableMap changeSet:changeSet]; - ASPrimitiveTraitCollection existingTraitCollection = [self.node primitiveTraitCollection]; - [self _updateElementsInMap:mutableMap changeSet:changeSet traitCollection:existingTraitCollection shouldFetchSizeRanges:(! canDelegate) previousMap:previousMap]; - - // Step 1.2: Clone the new data - newMap = [mutableMap copy]; - } - self.pendingMap = newMap; - - // Step 2: Ask layout delegate for contexts - if (canDelegate) { - layoutContext = [self.layoutDelegate layoutContextWithElements:newMap]; - } - } - - as_log_debug(ASCollectionLog(), "New content: %@", newMap.smallDescription); - - Class layoutDelegateClass = [self.layoutDelegate class]; - ++_editingTransactionGroupCount; - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - __block __unused os_activity_scope_state_s preparationScope = {}; // unused if deployment target < iOS10 - as_activity_scope_enter(as_activity_create("Prepare nodes for collection update", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT), &preparationScope); - - // Step 3: Call the layout delegate if possible. Otherwise, allocate and layout all elements - if (canDelegate) { - [layoutDelegateClass calculateLayoutWithContext:layoutContext]; - } else { - const auto elementsToProcess = [[NSMutableArray alloc] init]; - for (ASCollectionElement *element in newMap) { - ASCellNode *nodeIfAllocated = element.nodeIfAllocated; - if (nodeIfAllocated.shouldUseUIKitCell) { - // If the node exists and we know it is a passthrough cell, we know it will never have a .calculatedLayout. - continue; - } else if (nodeIfAllocated.calculatedLayout == nil) { - // If the node hasn't been allocated, or it doesn't have a valid layout, let's process it. - [elementsToProcess addObject:element]; - } - } - [self _allocateNodesFromElements:elementsToProcess]; - } - - // Step 4: Inform the delegate on main thread - [_mainSerialQueue performBlockOnMainThread:^{ - as_activity_scope_leave(&preparationScope); - [_delegate dataController:self updateWithChangeSet:changeSet updates:^{ - // Step 5: Deploy the new data as "completed" - // - // Note that since the backing collection view might be busy responding to user events (e.g scrolling), - // it will not consume the batch update blocks immediately. - // As a result, in a short intermidate time, the view will still be relying on the old data source state. - // Thus, we can't just swap the new map immediately before step 4, but until this update block is executed. - // (https://github.com/TextureGroup/Texture/issues/378) - self.visibleMap = newMap; - }]; - }]; - --_editingTransactionGroupCount; - }); - - // We've now dispatched node allocation and layout to a concurrent background queue. - // In some cases, it's advantageous to prevent the main thread from returning, to ensure the next - // frame displayed to the user has the view updates in place. Doing this does slightly reduce - // total latency, by donating the main thread's priority to the background threads. As such, the - // two cases where it makes sense to block: - // 1. There is very little work to be performed in the background (UIKit passthrough) - // 2. There is a higher priority on display latency than smoothness, e.g. app startup. - if ([_dataSource dataController:self shouldSynchronouslyProcessChangeSet:changeSet]) { - [self waitUntilAllUpdatesAreProcessed]; - } -} - -/** - * Update sections based on the given change set. - */ -- (void)_updateSectionsInMap:(ASMutableElementMap *)map changeSet:(_ASHierarchyChangeSet *)changeSet -{ - ASDisplayNodeAssertMainThread(); - - if (changeSet.includesReloadData) { - [map removeAllSections]; - - NSUInteger sectionCount = [self itemCountsFromDataSource].size(); - NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; - [self _insertSectionsIntoMap:map indexes:sectionIndexes]; - // Return immediately because reloadData can't be used in conjuntion with other updates. - return; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { - [map removeSectionsAtIndexes:change.indexSet]; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { - [self _insertSectionsIntoMap:map indexes:change.indexSet]; - } -} - -- (void)_insertSectionsIntoMap:(ASMutableElementMap *)map indexes:(NSIndexSet *)sectionIndexes -{ - ASDisplayNodeAssertMainThread(); - - [sectionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { - id context; - if (_dataSourceFlags.contextForSection) { - context = [_dataSource dataController:self contextForSection:idx]; - } - ASSection *section = [[ASSection alloc] initWithSectionID:_nextSectionID context:context]; - [map insertSection:section atIndex:idx]; - _nextSectionID++; - }]; -} - -/** - * Update elements based on the given change set. - */ -- (void)_updateElementsInMap:(ASMutableElementMap *)map - changeSet:(_ASHierarchyChangeSet *)changeSet - traitCollection:(ASPrimitiveTraitCollection)traitCollection - shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges - previousMap:(ASElementMap *)previousMap -{ - ASDisplayNodeAssertMainThread(); - - if (changeSet.includesReloadData) { - [map removeAllElements]; - - NSUInteger sectionCount = [self itemCountsFromDataSource].size(); - if (sectionCount > 0) { - NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; - [self _insertElementsIntoMap:map sections:sectionIndexes traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges changeSet:changeSet previousMap:previousMap]; - } - // Return immediately because reloadData can't be used in conjuntion with other updates. - return; - } - - // Migrate old supplementary nodes to their new index paths. - [map migrateSupplementaryElementsWithSectionMapping:changeSet.sectionMapping]; - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) { - [map removeItemsAtIndexPaths:change.indexPaths]; - // Aggressively repopulate supplementary nodes (#1773 & #1629) - [self _repopulateSupplementaryNodesIntoMap:map forSectionsContainingIndexPaths:change.indexPaths - changeSet:changeSet - traitCollection:traitCollection - indexPathsAreNew:NO - shouldFetchSizeRanges:shouldFetchSizeRanges - previousMap:previousMap]; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { - NSIndexSet *sectionIndexes = change.indexSet; - [map removeSectionsOfItems:sectionIndexes]; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { - [self _insertElementsIntoMap:map sections:change.indexSet traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges changeSet:changeSet previousMap:previousMap]; - } - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) { - [self _insertElementsIntoMap:map kind:ASDataControllerRowNodeKind atIndexPaths:change.indexPaths traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges changeSet:changeSet previousMap:previousMap]; - // Aggressively reload supplementary nodes (#1773 & #1629) - [self _repopulateSupplementaryNodesIntoMap:map forSectionsContainingIndexPaths:change.indexPaths - changeSet:changeSet - traitCollection:traitCollection - indexPathsAreNew:YES - shouldFetchSizeRanges:shouldFetchSizeRanges - previousMap:previousMap]; - } -} - -- (void)_insertElementsIntoMap:(ASMutableElementMap *)map - sections:(NSIndexSet *)sectionIndexes - traitCollection:(ASPrimitiveTraitCollection)traitCollection - shouldFetchSizeRanges:(BOOL)shouldFetchSizeRanges - changeSet:(_ASHierarchyChangeSet *)changeSet - previousMap:(ASElementMap *)previousMap -{ - ASDisplayNodeAssertMainThread(); - - if (sectionIndexes.count == 0 || _dataSource == nil) { - return; - } - - // Items - [map insertEmptySectionsOfItemsAtIndexes:sectionIndexes]; - [self _insertElementsIntoMap:map kind:ASDataControllerRowNodeKind forSections:sectionIndexes traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges changeSet:changeSet previousMap:previousMap]; - - // Supplementaries - for (NSString *kind in [self supplementaryKindsInSections:sectionIndexes]) { - // Step 2: Populate new elements for all sections - [self _insertElementsIntoMap:map kind:kind forSections:sectionIndexes traitCollection:traitCollection shouldFetchSizeRanges:shouldFetchSizeRanges changeSet:changeSet previousMap:previousMap]; - } -} - -#pragma mark - Relayout - -- (void)relayoutNodes:(id)nodes nodesSizeChanged:(NSMutableArray *)nodesSizesChanged -{ - NSParameterAssert(nodes); - NSParameterAssert(nodesSizesChanged); - - ASDisplayNodeAssertMainThread(); - if (!_initialReloadDataHasBeenCalled) { - return; - } - - id dataSource = self.dataSource; - const auto visibleMap = self.visibleMap; - const auto pendingMap = self.pendingMap; - for (ASCellNode *node in nodes) { - const auto element = node.collectionElement; - NSIndexPath *indexPathInPendingMap = [pendingMap indexPathForElement:element]; - // Ensure the element is present in both maps or skip it. If it's not in the visible map, - // then we can't check the presented size. If it's not in the pending map, we can't get the constrained size. - // This will only happen if the element has been deleted, so the specifics of this behavior aren't important. - if (indexPathInPendingMap == nil || [visibleMap indexPathForElement:element] == nil) { - continue; - } - - NSString *kind = element.supplementaryElementKind ?: ASDataControllerRowNodeKind; - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPathInPendingMap]; - [self _layoutNode:node withConstrainedSize:constrainedSize]; - - BOOL matchesSize = [dataSource dataController:self presentedSizeForElement:element matchesSize:node.frame.size]; - if (! matchesSize) { - [nodesSizesChanged addObject:node]; - } - } -} - -- (void)relayoutAllNodesWithInvalidationBlock:(nullable void (^)())invalidationBlock -{ - ASDisplayNodeAssertMainThread(); - if (!_initialReloadDataHasBeenCalled) { - return; - } - - // Can't relayout right away because _visibleMap may not be up-to-date, - // i.e there might be some nodes that were measured using the old constrained size but haven't been added to _visibleMap - LOG(@"Edit Command - relayoutRows"); - [self _scheduleBlockOnMainSerialQueue:^{ - // Because -invalidateLayout doesn't trigger any operations by itself, and we answer queries from UICollectionView using layoutThatFits:, - // we invalidate the layout before we have updated all of the cells. Any cells that the collection needs the size of immediately will get - // -layoutThatFits: with a new constraint, on the main thread, and synchronously calculate them. Meanwhile, relayoutAllNodes will update - // the layout of any remaining nodes on background threads (and fast-return for any nodes that the UICV got to first). - if (invalidationBlock) { - invalidationBlock(); - } - [self _relayoutAllNodes]; - }]; -} - -- (void)_relayoutAllNodes -{ - ASDisplayNodeAssertMainThread(); - // Aggressively repopulate all supplemtary elements - // Assuming this method is run on the main serial queue, _pending and _visible maps are synced and can be manipulated directly. - ASDisplayNodeAssert(_visibleMap == _pendingMap, @"Expected visible and pending maps to be synchronized: %@", self); - - ASMutableElementMap *newMap = [_pendingMap mutableCopy]; - [self _updateSupplementaryNodesIntoMap:newMap - traitCollection:[self.node primitiveTraitCollection] - shouldFetchSizeRanges:YES - previousMap:_pendingMap]; - _pendingMap = [newMap copy]; - _visibleMap = _pendingMap; - - for (ASCollectionElement *element in _visibleMap) { - // Ignore this element if it is no longer in the latest data. It is still recognized in the UIKit world but will be deleted soon. - NSIndexPath *indexPathInPendingMap = [_pendingMap indexPathForElement:element]; - if (indexPathInPendingMap == nil) { - continue; - } - - NSString *kind = element.supplementaryElementKind ?: ASDataControllerRowNodeKind; - ASSizeRange newConstrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPathInPendingMap]; - - if (ASSizeRangeHasSignificantArea(newConstrainedSize)) { - element.constrainedSize = newConstrainedSize; - - // Node may not be allocated yet (e.g node virtualization or same size optimization) - // Call context.nodeIfAllocated here to avoid premature node allocation and layout - ASCellNode *node = element.nodeIfAllocated; - if (node) { - [self _layoutNode:node withConstrainedSize:newConstrainedSize]; - } - } - } -} - -# pragma mark - ASPrimitiveTraitCollection - -- (void)environmentDidChange -{ - ASPerformBlockOnMainThread(^{ - if (!_initialReloadDataHasBeenCalled) { - return; - } - - // Can't update the trait collection right away because _visibleMap may not be up-to-date, - // i.e there might be some elements that were allocated using the old trait collection but haven't been added to _visibleMap - [self _scheduleBlockOnMainSerialQueue:^{ - ASPrimitiveTraitCollection newTraitCollection = [self.node primitiveTraitCollection]; - for (ASCollectionElement *element in _visibleMap) { - element.traitCollection = newTraitCollection; - } - }]; - }); -} - -- (void)clearData -{ - ASDisplayNodeAssertMainThread(); - if (_initialReloadDataHasBeenCalled) { - [self waitUntilAllUpdatesAreProcessed]; - self.visibleMap = self.pendingMap = [[ASElementMap alloc] init]; - } -} - -# pragma mark - Helper methods - -- (void)_scheduleBlockOnMainSerialQueue:(dispatch_block_t)block -{ - ASDisplayNodeAssertMainThread(); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - [_mainSerialQueue performBlockOnMainThread:block]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASDefaultPlayButton.h b/submodules/AsyncDisplayKit/Source/ASDefaultPlayButton.h deleted file mode 100644 index e3bc15246d..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDefaultPlayButton.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// ASDefaultPlayButton.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 - -@interface ASDefaultPlayButton : ASButtonNode - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASDefaultPlayButton.mm b/submodules/AsyncDisplayKit/Source/ASDefaultPlayButton.mm deleted file mode 100644 index 3ece99407c..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDefaultPlayButton.mm +++ /dev/null @@ -1,66 +0,0 @@ -// -// ASDefaultPlayButton.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 "ASDefaultPlayButton.h" -#import - -@implementation ASDefaultPlayButton - -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - - self.opaque = NO; - - return self; -} - -+ (void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing -{ - CGFloat originX = bounds.size.width/4; - CGRect buttonBounds = CGRectMake(originX, bounds.size.height/4, bounds.size.width/2, bounds.size.height/2); - CGFloat widthHeight = buttonBounds.size.width; - - //When the video isn't a square, the lower bound should be used to figure out the circle size - if (bounds.size.width < bounds.size.height) { - widthHeight = bounds.size.width/2; - originX = (bounds.size.width - widthHeight)/2; - buttonBounds = CGRectMake(originX, (bounds.size.height - widthHeight)/2, widthHeight, widthHeight); - } - if (bounds.size.width > bounds.size.height) { - widthHeight = bounds.size.height/2; - originX = (bounds.size.width - widthHeight)/2; - buttonBounds = CGRectMake(originX, (bounds.size.height - widthHeight)/2, widthHeight, widthHeight); - } - - CGContextRef context = UIGraphicsGetCurrentContext(); - - // Circle Drawing - UIBezierPath *ovalPath = [UIBezierPath bezierPathWithOvalInRect: buttonBounds]; - [[UIColor colorWithWhite:0.0 alpha:0.5] setFill]; - [ovalPath fill]; - - // Triangle Drawing - CGContextSaveGState(context); - - UIBezierPath *trianglePath = [UIBezierPath bezierPath]; - [trianglePath moveToPoint:CGPointMake(originX + widthHeight/3, bounds.size.height/4 + (bounds.size.height/2)/4)]; - [trianglePath addLineToPoint:CGPointMake(originX + widthHeight/3, bounds.size.height - bounds.size.height/4 - (bounds.size.height/2)/4)]; - [trianglePath addLineToPoint:CGPointMake(bounds.size.width - originX - widthHeight/4, bounds.size.height/2)]; - - [trianglePath closePath]; - [[UIColor colorWithWhite:0.9 alpha:0.9] setFill]; - [trianglePath fill]; - - CGContextRestoreGState(context); -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASDefaultPlaybackButton.h b/submodules/AsyncDisplayKit/Source/ASDefaultPlaybackButton.h deleted file mode 100644 index ae7e245dc0..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDefaultPlaybackButton.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ASDefaultPlaybackButton.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 - -typedef NS_ENUM(NSInteger, ASDefaultPlaybackButtonType) { - ASDefaultPlaybackButtonTypePlay, - ASDefaultPlaybackButtonTypePause -}; - -@interface ASDefaultPlaybackButton : ASControlNode -@property (nonatomic) ASDefaultPlaybackButtonType buttonType; -@end diff --git a/submodules/AsyncDisplayKit/Source/ASDefaultPlaybackButton.mm b/submodules/AsyncDisplayKit/Source/ASDefaultPlaybackButton.mm deleted file mode 100644 index 38166a7c0e..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDefaultPlaybackButton.mm +++ /dev/null @@ -1,84 +0,0 @@ -// -// ASDefaultPlaybackButton.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 "ASDefaultPlaybackButton.h" -#import - -@interface ASDefaultPlaybackButton() -{ - ASDefaultPlaybackButtonType _buttonType; -} -@end - -@implementation ASDefaultPlaybackButton -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - - self.opaque = NO; - - return self; -} - -- (void)setButtonType:(ASDefaultPlaybackButtonType)buttonType -{ - ASDefaultPlaybackButtonType oldType = _buttonType; - _buttonType = buttonType; - - if (oldType != _buttonType) { - [self setNeedsDisplay]; - } -} - -- (nullable NSDictionary *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer -{ - return @{ - @"buttonType" : @(self.buttonType), - @"color" : self.tintColor - }; -} - -+ (void)drawRect:(CGRect)bounds withParameters:(NSDictionary *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing -{ - ASDefaultPlaybackButtonType buttonType = (ASDefaultPlaybackButtonType)[parameters[@"buttonType"] intValue]; - UIColor *color = parameters[@"color"]; - - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextSaveGState(context); - UIBezierPath* bezierPath = [UIBezierPath bezierPath]; - if (buttonType == ASDefaultPlaybackButtonTypePlay) { - [bezierPath moveToPoint: CGPointMake(0, 0)]; - [bezierPath addLineToPoint: CGPointMake(0, bounds.size.height)]; - [bezierPath addLineToPoint: CGPointMake(bounds.size.width, bounds.size.height/2)]; - [bezierPath addLineToPoint: CGPointMake(0, 0)]; - [bezierPath closePath]; - } else if (buttonType == ASDefaultPlaybackButtonTypePause) { - CGFloat pauseSingleLineWidth = bounds.size.width / 3.0; - [bezierPath moveToPoint: CGPointMake(0, bounds.size.height)]; - [bezierPath addLineToPoint: CGPointMake(pauseSingleLineWidth, bounds.size.height)]; - [bezierPath addLineToPoint: CGPointMake(pauseSingleLineWidth, 0)]; - [bezierPath addLineToPoint: CGPointMake(0, 0)]; - [bezierPath addLineToPoint: CGPointMake(0, bounds.size.height)]; - [bezierPath closePath]; - [bezierPath moveToPoint: CGPointMake(pauseSingleLineWidth * 2, 0)]; - [bezierPath addLineToPoint: CGPointMake(pauseSingleLineWidth * 2, bounds.size.height)]; - [bezierPath addLineToPoint: CGPointMake(bounds.size.width, bounds.size.height)]; - [bezierPath addLineToPoint: CGPointMake(bounds.size.width, 0)]; - [bezierPath addLineToPoint: CGPointMake(pauseSingleLineWidth * 2, 0)]; - [bezierPath closePath]; - } - - [color setFill]; - [bezierPath fill]; - - CGContextRestoreGState(context); -} -@end diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Yoga.h b/submodules/AsyncDisplayKit/Source/ASDisplayNode+Yoga.h deleted file mode 100644 index 93cbf7a7c1..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Yoga.h +++ /dev/null @@ -1,102 +0,0 @@ -// -// 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 - -#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 diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Yoga.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNode+Yoga.mm deleted file mode 100644 index dcecda7279..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Yoga.mm +++ /dev/null @@ -1,472 +0,0 @@ -// -// 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 - -#if YOGA /* YOGA */ - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#import - -#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 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 _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 */ diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm.orig b/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm.orig deleted file mode 100644 index 50510817ab..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm.orig +++ /dev/null @@ -1,3927 +0,0 @@ -// -// 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 -// - -#import - -#import -#import -#import -#import -#import - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -// Conditionally time these scopes to our debug ivars (only exist in debug/profile builds) -#if TIME_DISPLAYNODE_OPS - #define TIME_SCOPED(outVar) ASDN::ScopeTimer t(outVar) -#else - #define TIME_SCOPED(outVar) -#endif -// This is trying to merge non-rangeManaged with rangeManaged, so both range-managed and standalone nodes wait before firing their exit-visibility handlers, as UIViewController transitions now do rehosting at both start & end of animation. -// Enable this will mitigate interface updating state when coalescing disabled. -// TODO(wsdwsd0829): Rework enabling code to ensure that interface state behavior is not altered when ASCATransactionQueue is disabled. -#define ENABLE_NEW_EXIT_HIERARCHY_BEHAVIOR 0 - -static ASDisplayNodeNonFatalErrorBlock _nonFatalErrorBlock = nil; -NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; - -// Forward declare CALayerDelegate protocol as the iOS 10 SDK moves CALayerDelegate from an informal delegate to a protocol. -// We have to forward declare the protocol as this place otherwise it will not compile compiling with an Base SDK < iOS 10 -@protocol CALayerDelegate; - -@interface ASDisplayNode () -/** - * See ASDisplayNodeInternal.h for ivars - */ - -@end - -@implementation ASDisplayNode - -@dynamic layoutElementType; - -@synthesize threadSafeBounds = _threadSafeBounds; - -static std::atomic_bool storesUnflattenedLayouts = ATOMIC_VAR_INIT(NO); - -BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector) -{ - return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector); -} - -// For classes like ASTableNode, ASCollectionNode, ASScrollNode and similar - we have to be sure to set certain properties -// like setFrame: and setBackgroundColor: directly to the UIView and not apply it to the layer only. -BOOL ASDisplayNodeNeedsSpecialPropertiesHandling(BOOL isSynchronous, BOOL isLayerBacked) -{ - return isSynchronous && !isLayerBacked; -} - -_ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node) -{ - ASLockScope(node); - _ASPendingState *result = node->_pendingViewState; - if (result == nil) { - result = [[_ASPendingState alloc] init]; - node->_pendingViewState = result; - } - return result; -} - -/** - * Returns ASDisplayNodeFlags for the given class/instance. instance MAY BE NIL. - * - * @param c the class, required - * @param instance the instance, which may be nil. (If so, the class is inspected instead) - * @remarks The instance value is used only if we suspect the class may be dynamic (because it overloads - * +respondsToSelector: or -respondsToSelector.) In that case we use our "slow path", calling this - * method on each -init and passing the instance value. While this may seem like an unlikely scenario, - * it turns our our own internal tests use a dynamic class, so it's worth capturing this edge case. - * - * @return ASDisplayNode flags. - */ -static struct ASDisplayNodeFlags GetASDisplayNodeFlags(Class c, ASDisplayNode *instance) -{ - ASDisplayNodeCAssertNotNil(c, @"class is required"); - - struct ASDisplayNodeFlags flags = {0}; - - flags.isInHierarchy = NO; - flags.displaysAsynchronously = YES; - flags.shouldAnimateSizeChanges = YES; - flags.implementsDrawRect = ([c respondsToSelector:@selector(drawRect:withParameters:isCancelled:isRasterizing:)] ? 1 : 0); - flags.implementsImageDisplay = ([c respondsToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0); - if (instance) { - flags.implementsDrawParameters = ([instance respondsToSelector:@selector(drawParametersForAsyncLayer:)] ? 1 : 0); - } else { - flags.implementsDrawParameters = ([c instancesRespondToSelector:@selector(drawParametersForAsyncLayer:)] ? 1 : 0); - } - - - return flags; -} - -/** - * Returns ASDisplayNodeMethodOverrides for the given class - * - * @param c the class, required. - * - * @return ASDisplayNodeMethodOverrides. - */ -static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) -{ - ASDisplayNodeCAssertNotNil(c, @"class is required"); - - ASDisplayNodeMethodOverrides overrides = ASDisplayNodeMethodOverrideNone; - - // Handling touches - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesBegan:withEvent:))) { - overrides |= ASDisplayNodeMethodOverrideTouchesBegan; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesMoved:withEvent:))) { - overrides |= ASDisplayNodeMethodOverrideTouchesMoved; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesCancelled:withEvent:))) { - overrides |= ASDisplayNodeMethodOverrideTouchesCancelled; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesEnded:withEvent:))) { - overrides |= ASDisplayNodeMethodOverrideTouchesEnded; - } - - // Responder chain - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(canBecomeFirstResponder))) { - overrides |= ASDisplayNodeMethodOverrideCanBecomeFirstResponder; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(becomeFirstResponder))) { - overrides |= ASDisplayNodeMethodOverrideBecomeFirstResponder; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(canResignFirstResponder))) { - overrides |= ASDisplayNodeMethodOverrideCanResignFirstResponder; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(resignFirstResponder))) { - overrides |= ASDisplayNodeMethodOverrideResignFirstResponder; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(isFirstResponder))) { - overrides |= ASDisplayNodeMethodOverrideIsFirstResponder; - } - - // Layout related methods - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(layoutSpecThatFits:))) { - overrides |= ASDisplayNodeMethodOverrideLayoutSpecThatFits; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(calculateLayoutThatFits:)) || - ASDisplayNodeSubclassOverridesSelector(c, @selector(calculateLayoutThatFits: - restrictedToSize: - relativeToParentSize:))) { - overrides |= ASDisplayNodeMethodOverrideCalcLayoutThatFits; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(calculateSizeThatFits:))) { - overrides |= ASDisplayNodeMethodOverrideCalcSizeThatFits; - } - - return overrides; -} - -+ (void)initialize -{ -#if ASDISPLAYNODE_ASSERTIONS_ENABLED - if (self != [ASDisplayNode class]) { - - // Subclasses should never override these. Use unused to prevent warnings - __unused NSString *classString = NSStringFromClass(self); - - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedSize)), @"Subclass %@ must not override calculatedSize method.", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedLayout)), @"Subclass %@ must not override calculatedLayout method.", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:)), @"Subclass %@ must not override layoutThatFits: method. Instead override calculateLayoutThatFits:.", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:parentSize:)), @"Subclass %@ must not override layoutThatFits:parentSize method. Instead override calculateLayoutThatFits:.", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearContents)), @"Subclass %@ must not override recursivelyClearContents method.", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearPreloadedData)), @"Subclass %@ must not override recursivelyClearFetchedData method.", classString); - } else { - // Check if subnodes where modified during the creation of the layout - __block IMP originalLayoutSpecThatFitsIMP = ASReplaceMethodWithBlock(self, @selector(_locked_layoutElementThatFits:), ^(ASDisplayNode *_self, ASSizeRange sizeRange) { - NSArray *oldSubnodes = _self.subnodes; - ASLayoutSpec *layoutElement = ((ASLayoutSpec *( *)(id, SEL, ASSizeRange))originalLayoutSpecThatFitsIMP)(_self, @selector(_locked_layoutElementThatFits:), sizeRange); - NSArray *subnodes = _self.subnodes; - ASDisplayNodeAssert(oldSubnodes.count == subnodes.count, @"Adding or removing nodes in layoutSpecBlock or layoutSpecThatFits: is not allowed and can cause unexpected behavior."); - for (NSInteger i = 0; i < oldSubnodes.count; i++) { - ASDisplayNodeAssert(oldSubnodes[i] == subnodes[i], @"Adding or removing nodes in layoutSpecBlock or layoutSpecThatFits: is not allowed and can cause unexpected behavior."); - } - return layoutElement; - }); - } -#endif - - // Below we are pre-calculating values per-class and dynamically adding a method (_staticInitialize) to populate these values - // when each instance is constructed. These values don't change for each class, so there is significant performance benefit - // in doing it here. +initialize is guaranteed to be called before any instance method so it is safe to add this method here. - // Note that we take care to detect if the class overrides +respondsToSelector: or -respondsToSelector and take the slow path - // (recalculating for each instance) to make sure we are always correct. - - BOOL classOverridesRespondsToSelector = ASSubclassOverridesClassSelector([NSObject class], self, @selector(respondsToSelector:)); - BOOL instancesOverrideRespondsToSelector = ASSubclassOverridesSelector([NSObject class], self, @selector(respondsToSelector:)); - struct ASDisplayNodeFlags flags = GetASDisplayNodeFlags(self, nil); - ASDisplayNodeMethodOverrides methodOverrides = GetASDisplayNodeMethodOverrides(self); - - __unused Class initializeSelf = self; - - IMP staticInitialize = imp_implementationWithBlock(^(ASDisplayNode *node) { - ASDisplayNodeAssert(node.class == initializeSelf, @"Node class %@ does not have a matching _staticInitialize method; check to ensure [super initialize] is called within any custom +initialize implementations! Overridden methods will not be called unless they are also implemented by superclass %@", node.class, initializeSelf); - node->_flags = (classOverridesRespondsToSelector || instancesOverrideRespondsToSelector) ? GetASDisplayNodeFlags(node.class, node) : flags; - node->_methodOverrides = (classOverridesRespondsToSelector) ? GetASDisplayNodeMethodOverrides(node.class) : methodOverrides; - }); - - class_replaceMethod(self, @selector(_staticInitialize), staticInitialize, "v:@"); -} - -#if !AS_INITIALIZE_FRAMEWORK_MANUALLY -+ (void)load -{ - ASInitializeFrameworkMainThread(); -} -#endif - -+ (Class)viewClass -{ - return [_ASDisplayView class]; -} - -+ (Class)layerClass -{ - return [_ASDisplayLayer class]; -} - -#pragma mark - Lifecycle - -- (void)_staticInitialize -{ - ASDisplayNodeAssert(NO, @"_staticInitialize must be overridden"); -} - -- (void)_initializeInstance -{ - [self _staticInitialize]; - -#if ASEVENTLOG_ENABLE - _eventLog = [[ASEventLog alloc] initWithObject:self]; -#endif - - _viewClass = [self.class viewClass]; - _layerClass = [self.class layerClass]; - BOOL isSynchronous = ![_viewClass isSubclassOfClass:[_ASDisplayView class]] - || ![_layerClass isSubclassOfClass:[_ASDisplayLayer class]]; - setFlag(Synchronous, isSynchronous); - - - _contentsScaleForDisplay = ASScreenScale(); - _drawingPriority = ASDefaultDrawingPriority; - - _primitiveTraitCollection = ASPrimitiveTraitCollectionMakeDefault(); - - _layoutVersion = 1; - - _defaultLayoutTransitionDuration = 0.2; - _defaultLayoutTransitionDelay = 0.0; - _defaultLayoutTransitionOptions = UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionTransitionNone; - - _flags.canClearContentsOfLayer = YES; - _flags.canCallSetNeedsDisplayOfLayer = YES; - - _fallbackSafeAreaInsets = UIEdgeInsetsZero; - _fallbackInsetsLayoutMarginsFromSafeArea = YES; - _isViewControllerRoot = NO; - - _automaticallyRelayoutOnSafeAreaChanges = NO; - _automaticallyRelayoutOnLayoutMarginsChanges = NO; - - ASDisplayNodeLogEvent(self, @"init"); -} - -- (instancetype)init -{ - if (!(self = [super init])) - return nil; - - [self _initializeInstance]; - - return self; -} - -- (instancetype)initWithViewClass:(Class)viewClass -{ - if (!(self = [self init])) - return nil; - - ASDisplayNodeAssert([viewClass isSubclassOfClass:[UIView class]], @"should initialize with a subclass of UIView"); - - _viewClass = viewClass; - setFlag(Synchronous, ![viewClass isSubclassOfClass:[_ASDisplayView class]]); - - return self; -} - -- (instancetype)initWithLayerClass:(Class)layerClass -{ - if (!(self = [self init])) { - return nil; - } - - ASDisplayNodeAssert([layerClass isSubclassOfClass:[CALayer class]], @"should initialize with a subclass of CALayer"); - - _layerClass = layerClass; - _flags.layerBacked = YES; - setFlag(Synchronous, ![layerClass isSubclassOfClass:[_ASDisplayLayer class]]); - - return self; -} - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock -{ - return [self initWithViewBlock:viewBlock didLoadBlock:nil]; -} - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock -{ - if (!(self = [self init])) { - return nil; - } - - [self setViewBlock:viewBlock]; - if (didLoadBlock != nil) { - [self onDidLoad:didLoadBlock]; - } - - return self; -} - -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock -{ - return [self initWithLayerBlock:layerBlock didLoadBlock:nil]; -} - -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock -{ - if (!(self = [self init])) { - return nil; - } - - [self setLayerBlock:layerBlock]; - if (didLoadBlock != nil) { - [self onDidLoad:didLoadBlock]; - } - - return self; -} - -ASSynthesizeLockingMethodsWithMutex(__instanceLock__); - -- (void)setViewBlock:(ASDisplayNodeViewBlock)viewBlock -{ - ASDisplayNodeAssertFalse(self.nodeLoaded); - ASDisplayNodeAssertNotNil(viewBlock, @"should initialize with a valid block that returns a UIView"); - - _viewBlock = viewBlock; - setFlag(Synchronous, YES); -} - -- (void)setLayerBlock:(ASDisplayNodeLayerBlock)layerBlock -{ - ASDisplayNodeAssertFalse(self.nodeLoaded); - ASDisplayNodeAssertNotNil(layerBlock, @"should initialize with a valid block that returns a CALayer"); - - _layerBlock = layerBlock; - _flags.layerBacked = YES; - setFlag(Synchronous, YES); -} - -- (ASDisplayNodeMethodOverrides)methodOverrides -{ - return _methodOverrides; -} - -- (void)onDidLoad:(ASDisplayNodeDidLoadBlock)body -{ - ASDN::MutexLocker l(__instanceLock__); - - if ([self _locked_isNodeLoaded]) { - ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexUnlocker l(__instanceLock__); - body(self); - } else if (_onDidLoadBlocks == nil) { - _onDidLoadBlocks = [NSMutableArray arrayWithObject:body]; - } else { - [_onDidLoadBlocks addObject:body]; - } -} - -- (void)dealloc -{ - _flags.isDeallocating = YES; - - // Synchronous nodes may not be able to call the hierarchy notifications, so only enforce for regular nodes. - ASDisplayNodeAssert(checkFlag(Synchronous) || !ASInterfaceStateIncludesVisible(_interfaceState), @"Node should always be marked invisible before deallocating. Node: %@", self); - - self.asyncLayer.asyncDelegate = nil; - _view.asyncdisplaykit_node = nil; - _layer.asyncdisplaykit_node = nil; - - // Remove any subnodes so they lose their connection to the now deallocated parent. This can happen - // because subnodes do not retain their supernode, but subnodes can legitimately remain alive if another - // thing outside the view hierarchy system (e.g. async display, controller code, etc). keeps a retained - // reference to subnodes. - - for (ASDisplayNode *subnode in _subnodes) - [subnode _setSupernode:nil]; - - [self scheduleIvarsForMainThreadDeallocation]; - - // TODO: Remove this? If supernode isn't already nil, this method isn't dealloc-safe anyway. - [self _setSupernode:nil]; -} - -#pragma mark - Loading - -- (BOOL)_locked_shouldLoadViewOrLayer -{ - ASAssertLocked(__instanceLock__); - return !_flags.isDeallocating && !(_hierarchyState & ASHierarchyStateRasterized); -} - -- (UIView *)_locked_viewToLoad -{ - ASAssertLocked(__instanceLock__); - - UIView *view = nil; - if (_viewBlock) { - view = _viewBlock(); - ASDisplayNodeAssertNotNil(view, @"View block returned nil"); - ASDisplayNodeAssert(![view isKindOfClass:[_ASDisplayView class]], @"View block should return a synchronously displayed view"); - _viewBlock = nil; - _viewClass = [view class]; - } else { - view = [[_viewClass alloc] init]; - } - - // Special handling of wrapping UIKit components - if (checkFlag(Synchronous)) { - [self checkResponderCompatibility]; - - // UIImageView layers. More details on the flags - if ([_viewClass isSubclassOfClass:[UIImageView class]]) { - _flags.canClearContentsOfLayer = NO; - _flags.canCallSetNeedsDisplayOfLayer = NO; - } - - // UIActivityIndicator - if ([_viewClass isSubclassOfClass:[UIActivityIndicatorView class]] - || [_viewClass isSubclassOfClass:[UIVisualEffectView class]]) { - self.opaque = NO; - } - - // CAEAGLLayer - if([[view.layer class] isSubclassOfClass:[CAEAGLLayer class]]){ - _flags.canClearContentsOfLayer = NO; - } - } - - return view; -} - -- (CALayer *)_locked_layerToLoad -{ - ASAssertLocked(__instanceLock__); - ASDisplayNodeAssert(_flags.layerBacked, @"_layerToLoad is only for layer-backed nodes"); - - CALayer *layer = nil; - if (_layerBlock) { - layer = _layerBlock(); - ASDisplayNodeAssertNotNil(layer, @"Layer block returned nil"); - ASDisplayNodeAssert(![layer isKindOfClass:[_ASDisplayLayer class]], @"Layer block should return a synchronously displayed layer"); - _layerBlock = nil; - _layerClass = [layer class]; - } else { - layer = [[_layerClass alloc] init]; - } - - return layer; -} - -- (void)_locked_loadViewOrLayer -{ - ASAssertLocked(__instanceLock__); - - if (_flags.layerBacked) { - TIME_SCOPED(_debugTimeToCreateView); - _layer = [self _locked_layerToLoad]; - static int ASLayerDelegateAssociationKey; - - /** - * CALayer's .delegate property is documented to be weak, but the implementation is actually assign. - * Because our layer may survive longer than the node (e.g. if someone else retains it, or if the node - * begins deallocation on a background thread and it waiting for the -dealloc call to reach main), the only - * way to avoid a dangling pointer is to use a weak proxy. - */ - ASWeakProxy *instance = [ASWeakProxy weakProxyWithTarget:self]; - _layer.delegate = (id)instance; - objc_setAssociatedObject(_layer, &ASLayerDelegateAssociationKey, instance, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } else { - TIME_SCOPED(_debugTimeToCreateView); - _view = [self _locked_viewToLoad]; - _view.asyncdisplaykit_node = self; - _layer = _view.layer; - } - _layer.asyncdisplaykit_node = self; - - self._locked_asyncLayer.asyncDelegate = self; -} - -- (void)_didLoad -{ - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - ASDisplayNodeLogEvent(self, @"didLoad"); - as_log_verbose(ASNodeLog(), "didLoad %@", self); - TIME_SCOPED(_debugTimeForDidLoad); - - [self didLoad]; - - __instanceLock__.lock(); - let onDidLoadBlocks = ASTransferStrong(_onDidLoadBlocks); - __instanceLock__.unlock(); - - for (ASDisplayNodeDidLoadBlock block in onDidLoadBlocks) { - block(self); - } - [self enumerateInterfaceStateDelegates:^(id del) { - [del nodeDidLoad]; - }]; -} - -- (void)didLoad -{ - ASDisplayNodeAssertMainThread(); - - // Subclass hook -} - -- (BOOL)isNodeLoaded -{ - if (ASDisplayNodeThreadIsMain()) { - // Because the view and layer can only be created and destroyed on Main, that is also the only thread - // where the state of this property can change. As an optimization, we can avoid locking. - return _loaded(self); - } else { - ASDN::MutexLocker l(__instanceLock__); - return [self _locked_isNodeLoaded]; - } -} - -- (BOOL)_locked_isNodeLoaded -{ - ASAssertLocked(__instanceLock__); - return _loaded(self); -} - -#pragma mark - Misc Setter / Getter - -- (UIView *)view -{ - ASDN::MutexLocker l(__instanceLock__); - - ASDisplayNodeAssert(!_flags.layerBacked, @"Call to -view undefined on layer-backed nodes"); - BOOL isLayerBacked = _flags.layerBacked; - if (isLayerBacked) { - return nil; - } - - if (_view != nil) { - return _view; - } - - if (![self _locked_shouldLoadViewOrLayer]) { - return nil; - } - - // Loading a view needs to happen on the main thread - ASDisplayNodeAssertMainThread(); - [self _locked_loadViewOrLayer]; - - // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout - // but automatic subnode management would require us to modify the node tree - // in the background on a loaded node, which isn't currently supported. - if (_pendingViewState.hasSetNeedsLayout) { - // Need to unlock before calling setNeedsLayout to avoid deadlocks. - // MutexUnlocker will re-lock at the end of scope. - ASDN::MutexUnlocker u(__instanceLock__); - [self __setNeedsLayout]; - } - - [self _locked_applyPendingStateToViewOrLayer]; - - { - // The following methods should not be called with a lock - ASDN::MutexUnlocker u(__instanceLock__); - - // No need for the lock as accessing the subviews or layers are always happening on main - [self _addSubnodeViewsAndLayers]; - - // A subclass hook should never be called with a lock - [self _didLoad]; - } - - return _view; -} - -- (CALayer *)layer -{ - ASDN::MutexLocker l(__instanceLock__); - if (_layer != nil) { - return _layer; - } - - if (![self _locked_shouldLoadViewOrLayer]) { - return nil; - } - - // Loading a layer needs to happen on the main thread - ASDisplayNodeAssertMainThread(); - [self _locked_loadViewOrLayer]; - - // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout - // but automatic subnode management would require us to modify the node tree - // in the background on a loaded node, which isn't currently supported. - if (_pendingViewState.hasSetNeedsLayout) { - // Need to unlock before calling setNeedsLayout to avoid deadlocks. - // MutexUnlocker will re-lock at the end of scope. - ASDN::MutexUnlocker u(__instanceLock__); - [self __setNeedsLayout]; - } - - [self _locked_applyPendingStateToViewOrLayer]; - - { - // The following methods should not be called with a lock - ASDN::MutexUnlocker u(__instanceLock__); - - // No need for the lock as accessing the subviews or layers are always happening on main - [self _addSubnodeViewsAndLayers]; - - // A subclass hook should never be called with a lock - [self _didLoad]; - } - - return _layer; -} - -// Returns nil if the layer is not an _ASDisplayLayer; will not create the layer if nil. -- (_ASDisplayLayer *)asyncLayer -{ - ASDN::MutexLocker l(__instanceLock__); - return [self _locked_asyncLayer]; -} - -- (_ASDisplayLayer *)_locked_asyncLayer -{ - ASAssertLocked(__instanceLock__); - return [_layer isKindOfClass:[_ASDisplayLayer class]] ? (_ASDisplayLayer *)_layer : nil; -} - -- (BOOL)isSynchronous -{ - return checkFlag(Synchronous); -} - -- (void)setLayerBacked:(BOOL)isLayerBacked -{ - // Only call this if assertions are enabled – it could be expensive. - ASDisplayNodeAssert(!isLayerBacked || self.supportsLayerBacking, @"Node %@ does not support layer backing.", self); - - ASDN::MutexLocker l(__instanceLock__); - if (_flags.layerBacked == isLayerBacked) { - return; - } - - if ([self _locked_isNodeLoaded]) { - ASDisplayNodeFailAssert(@"Cannot change layerBacked after view/layer has loaded."); - return; - } - - _flags.layerBacked = isLayerBacked; -} - -- (BOOL)isLayerBacked -{ - ASDN::MutexLocker l(__instanceLock__); - return _flags.layerBacked; -} - -- (BOOL)supportsLayerBacking -{ - ASDN::MutexLocker l(__instanceLock__); - return !checkFlag(Synchronous) && !_flags.viewEverHadAGestureRecognizerAttached && _viewClass == [_ASDisplayView class] && _layerClass == [_ASDisplayLayer class]; -} - -- (BOOL)shouldAnimateSizeChanges -{ - ASDN::MutexLocker l(__instanceLock__); - return _flags.shouldAnimateSizeChanges; -} - -- (void)setShouldAnimateSizeChanges:(BOOL)shouldAnimateSizeChanges -{ - ASDN::MutexLocker l(__instanceLock__); - _flags.shouldAnimateSizeChanges = shouldAnimateSizeChanges; -} - -- (CGRect)threadSafeBounds -{ - ASDN::MutexLocker l(__instanceLock__); - return [self _locked_threadSafeBounds]; -} - -- (CGRect)_locked_threadSafeBounds -{ - ASAssertLocked(__instanceLock__); - return _threadSafeBounds; -} - -- (void)setThreadSafeBounds:(CGRect)newBounds -{ - ASDN::MutexLocker l(__instanceLock__); - _threadSafeBounds = newBounds; -} - -- (void)nodeViewDidAddGestureRecognizer -{ - ASDN::MutexLocker l(__instanceLock__); - _flags.viewEverHadAGestureRecognizerAttached = YES; -} - -- (UIEdgeInsets)fallbackSafeAreaInsets -{ - ASDN::MutexLocker l(__instanceLock__); - return _fallbackSafeAreaInsets; -} - -- (void)setFallbackSafeAreaInsets:(UIEdgeInsets)insets -{ - BOOL needsManualUpdate; - BOOL updatesLayoutMargins; - - { - ASDN::MutexLocker l(__instanceLock__); - ASDisplayNodeAssertThreadAffinity(self); - - if (UIEdgeInsetsEqualToEdgeInsets(insets, _fallbackSafeAreaInsets)) { - return; - } - - _fallbackSafeAreaInsets = insets; - needsManualUpdate = !AS_AT_LEAST_IOS11 || _flags.layerBacked; - updatesLayoutMargins = needsManualUpdate && [self _locked_insetsLayoutMarginsFromSafeArea]; - } - - if (needsManualUpdate) { - [self safeAreaInsetsDidChange]; - } - - if (updatesLayoutMargins) { - [self layoutMarginsDidChange]; - } -} - -- (void)_fallbackUpdateSafeAreaOnChildren -{ - ASDisplayNodeAssertThreadAffinity(self); - - UIEdgeInsets insets = self.safeAreaInsets; - CGRect bounds = self.bounds; - - for (ASDisplayNode *child in self.subnodes) { - if (AS_AT_LEAST_IOS11 && !child.layerBacked) { - // In iOS 11 view-backed nodes already know what their safe area is. - continue; - } - - if (child.viewControllerRoot) { - // Its safe area is controlled by a view controller. Don't override it. - continue; - } - - CGRect childFrame = child.frame; - UIEdgeInsets childInsets = UIEdgeInsetsMake(MAX(insets.top - (CGRectGetMinY(childFrame) - CGRectGetMinY(bounds)), 0), - MAX(insets.left - (CGRectGetMinX(childFrame) - CGRectGetMinX(bounds)), 0), - MAX(insets.bottom - (CGRectGetMaxY(bounds) - CGRectGetMaxY(childFrame)), 0), - MAX(insets.right - (CGRectGetMaxX(bounds) - CGRectGetMaxX(childFrame)), 0)); - - child.fallbackSafeAreaInsets = childInsets; - } -} - -- (BOOL)isViewControllerRoot -{ - ASDN::MutexLocker l(__instanceLock__); - return _isViewControllerRoot; -} - -- (void)setViewControllerRoot:(BOOL)flag -{ - ASDN::MutexLocker l(__instanceLock__); - _isViewControllerRoot = flag; -} - -- (BOOL)automaticallyRelayoutOnSafeAreaChanges -{ - ASDN::MutexLocker l(__instanceLock__); - return _automaticallyRelayoutOnSafeAreaChanges; -} - -- (void)setAutomaticallyRelayoutOnSafeAreaChanges:(BOOL)flag -{ - ASDN::MutexLocker l(__instanceLock__); - _automaticallyRelayoutOnSafeAreaChanges = flag; -} - -- (BOOL)automaticallyRelayoutOnLayoutMarginsChanges -{ - ASDN::MutexLocker l(__instanceLock__); - return _automaticallyRelayoutOnLayoutMarginsChanges; -} - -- (void)setAutomaticallyRelayoutOnLayoutMarginsChanges:(BOOL)flag -{ - ASDN::MutexLocker l(__instanceLock__); - _automaticallyRelayoutOnLayoutMarginsChanges = flag; -} - -- (void)__setNodeController:(ASNodeController *)controller -{ - // See docs for why we don't lock. - if (controller.shouldInvertStrongReference) { - _strongNodeController = controller; - _weakNodeController = nil; - } else { - _weakNodeController = controller; - _strongNodeController = nil; - } -} - -#pragma mark - UIResponder - -#define HANDLE_NODE_RESPONDER_METHOD(__sel) \ - /* All responder methods should be called on the main thread */ \ - ASDisplayNodeAssertMainThread(); \ - if (checkFlag(Synchronous)) { \ - /* If the view is not a _ASDisplayView subclass (Synchronous) just call through to the view as we - expect it's a non _ASDisplayView subclass that will respond */ \ - return [_view __sel]; \ - } else { \ - if (ASSubclassOverridesSelector([_ASDisplayView class], _viewClass, @selector(__sel))) { \ - /* If the subclass overwrites canBecomeFirstResponder just call through - to it as we expect it will handle it */ \ - return [_view __sel]; \ - } else { \ - /* Call through to _ASDisplayView's superclass to get it handled */ \ - return [(_ASDisplayView *)_view __##__sel]; \ - } \ - } \ - -- (void)checkResponderCompatibility -{ -#if ASDISPLAYNODE_ASSERTIONS_ENABLED - // There are certain cases we cannot handle and are not supported: - // 1. If the _view class is not a subclass of _ASDisplayView - if (checkFlag(Synchronous)) { - // 2. At least one UIResponder methods are overwritten in the node subclass - NSString *message = @"Overwritting %@ and having a backing view that is not an _ASDisplayView is not supported."; - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(canBecomeFirstResponder)), ([NSString stringWithFormat:message, @"canBecomeFirstResponder"])); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(becomeFirstResponder)), ([NSString stringWithFormat:message, @"becomeFirstResponder"])); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(canResignFirstResponder)), ([NSString stringWithFormat:message, @"canResignFirstResponder"])); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(resignFirstResponder)), ([NSString stringWithFormat:message, @"resignFirstResponder"])); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(isFirstResponder)), ([NSString stringWithFormat:message, @"isFirstResponder"])); - } -#endif -} - -- (BOOL)__canBecomeFirstResponder -{ - if (_view == nil) { - // By default we return NO if not view is created yet - return NO; - } - - HANDLE_NODE_RESPONDER_METHOD(canBecomeFirstResponder); -} - -- (BOOL)__becomeFirstResponder -{ - // Note: This implicitly loads the view if it hasn't been loaded yet. - [self view]; - - if (![self canBecomeFirstResponder]) { - return NO; - } - - HANDLE_NODE_RESPONDER_METHOD(becomeFirstResponder); -} - -- (BOOL)__canResignFirstResponder -{ - if (_view == nil) { - // By default we return YES if no view is created yet - return YES; - } - - HANDLE_NODE_RESPONDER_METHOD(canResignFirstResponder); -} - -- (BOOL)__resignFirstResponder -{ - // Note: This implicitly loads the view if it hasn't been loaded yet. - [self view]; - - if (![self canResignFirstResponder]) { - return NO; - } - - HANDLE_NODE_RESPONDER_METHOD(resignFirstResponder); -} - -- (BOOL)__isFirstResponder -{ - if (_view == nil) { - // If no view is created yet we can just return NO as it's unlikely it's the first responder - return NO; - } - - HANDLE_NODE_RESPONDER_METHOD(isFirstResponder); -} - -#pragma mark - -- (NSString *)debugName -{ - ASDN::MutexLocker l(__instanceLock__); - return _debugName; -} - -- (void)setDebugName:(NSString *)debugName -{ - ASDN::MutexLocker l(__instanceLock__); - if (!ASObjectIsEqual(_debugName, debugName)) { - _debugName = [debugName copy]; - } -} - -#pragma mark - Layout - -// 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)) - - -#pragma mark - -- (BOOL)canLayoutAsynchronous -{ - return !self.isNodeLoaded; -} - -#pragma mark Layout Pass - -- (void)__setNeedsLayout -{ - [self invalidateCalculatedLayout]; -} - -- (void)invalidateCalculatedLayout -{ - ASDN::MutexLocker l(__instanceLock__); - - _layoutVersion++; - - _unflattenedLayout = nil; - -#if YOGA - [self invalidateCalculatedYogaLayout]; -#endif -} - -- (void)__layout -{ - ASDisplayNodeAssertThreadAffinity(self); - ASAssertUnlocked(__instanceLock__); - - BOOL loaded = NO; - { - ASDN::MutexLocker l(__instanceLock__); - loaded = [self _locked_isNodeLoaded]; - CGRect bounds = _threadSafeBounds; - - if (CGRectEqualToRect(bounds, CGRectZero)) { - // Performing layout on a zero-bounds view often results in frame calculations - // with negative sizes after applying margins, which will cause - // layoutThatFits: on subnodes to assert. - as_log_debug(OS_LOG_DISABLED, "Warning: No size given for node before node was trying to layout itself: %@. Please provide a frame for the node.", self); - return; - } - - // If a current layout transition is in progress there is no need to do a measurement and layout pass in here as - // this is supposed to happen within the layout transition process - if (_transitionID != ASLayoutElementContextInvalidTransitionID) { - return; - } - - as_activity_create_for_scope("-[ASDisplayNode __layout]"); - - // This method will confirm that the layout is up to date (and update if needed). - // Importantly, it will also APPLY the layout to all of our subnodes if (unless parent is transitioning). - { - ASDN::MutexUnlocker u(__instanceLock__); - [self _u_measureNodeWithBoundsIfNecessary:bounds]; - } - - [self _locked_layoutPlaceholderIfNecessary]; - } - - [self _layoutSublayouts]; - - // Per API contract, `-layout` and `-layoutDidFinish` are called only if the node is loaded. - if (loaded) { - ASPerformBlockOnMainThread(^{ - [self layout]; - [self _layoutClipCornersIfNeeded]; - [self layoutDidFinish]; - }); - } - - [self _fallbackUpdateSafeAreaOnChildren]; -} - -- (void)layoutDidFinish -{ - // Hook for subclasses - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - ASDisplayNodeAssertTrue(self.isNodeLoaded); -} - -#pragma mark Calculation - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize - restrictedToSize:(ASLayoutElementSize)size - relativeToParentSize:(CGSize)parentSize -{ -<<<<<<< HEAD - // We only want one calculateLayout signpost interval per thread. -#ifndef MINIMAL_ASDK - static _Thread_local NSInteger tls_callDepth; -======= ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e - as_activity_scope_verbose(as_activity_create("Calculate node layout", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)); - as_log_verbose(ASLayoutLog(), "Calculating layout for %@ sizeRange %@", self, NSStringFromASSizeRange(constrainedSize)); - -#if AS_KDEBUG_ENABLE - // We only want one calculateLayout signpost interval per thread. - // Currently there is no fallback for profiling i386, since it's not useful. - static _Thread_local NSInteger tls_callDepth; - if (tls_callDepth++ == 0) { - ASSignpostStart(ASSignpostCalculateLayout); - } -#endif - - ASSizeRange styleAndParentSize = ASLayoutElementSizeResolve(self.style.size, parentSize); - const ASSizeRange resolvedRange = ASSizeRangeIntersect(constrainedSize, styleAndParentSize); - ASLayout *result = [self calculateLayoutThatFits:resolvedRange]; -#ifndef MINIMAL_ASDK - as_log_verbose(ASLayoutLog(), "Calculated layout %@", result); - -#if AS_KDEBUG_ENABLE - if (--tls_callDepth == 0) { - ASSignpostEnd(ASSignpostCalculateLayout); - } -#endif -<<<<<<< HEAD -======= - ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e - return result; -} - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - __ASDisplayNodeCheckForLayoutMethodOverrides; - - ASDN::MutexLocker l(__instanceLock__); - -#if YOGA - // 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. - YGNodeRef yogaNode = _style.yogaNode; - BOOL hasYogaParent = (_yogaParent != nil); - BOOL hasYogaChildren = (_yogaChildren.count > 0); - BOOL usesYoga = (yogaNode != NULL && (hasYogaParent || hasYogaChildren)); - if (usesYoga) { - // This node has some connection to a Yoga tree. - if ([self shouldHaveYogaMeasureFunc] == NO) { - // 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. - ASDN::MutexUnlocker ul(__instanceLock__); - 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); - } - } -#endif /* YOGA */ - - // 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 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 - { - ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec); - ASTraitCollectionPropagateDown(layoutElement, self.primitiveTraitCollection); - } - - BOOL measureLayoutComputation = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutComputation; - if (measureLayoutComputation) { - _layoutComputationNumberOfPasses++; - } - - // Layout element layout creation - ASLayout *layout = ({ - ASDN::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; -} - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - __ASDisplayNodeCheckForLayoutMethodOverrides; - - ASDisplayNodeLogEvent(self, @"calculateSizeThatFits: with constrainedSize: %@", NSStringFromCGSize(constrainedSize)); - - return ASIsCGSizeValidForSize(constrainedSize) ? constrainedSize : CGSizeZero; -} - -- (id)_locked_layoutElementThatFits:(ASSizeRange)constrainedSize -{ - ASAssertLocked(__instanceLock__); - __ASDisplayNodeCheckForLayoutMethodOverrides; - - BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec; - - if (_layoutSpecBlock != NULL) { - return ({ - ASDN::MutexLocker l(__instanceLock__); - ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec); - _layoutSpecBlock(self, constrainedSize); - }); - } else { - return ({ - ASDN::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]; -} - -- (void)layout -{ - // Hook for subclasses - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - ASDisplayNodeAssertTrue(self.isNodeLoaded); - [self enumerateInterfaceStateDelegates:^(id del) { - [del nodeDidLayout]; - }]; -} - -#pragma mark Layout Transition - -- (void)_layoutTransitionMeasurementDidFinish -{ - // Hook for subclasses - No-Op in ASDisplayNode -} - -#pragma mark <_ASTransitionContextCompletionDelegate> - -/** - * After completeTransition: is called on the ASContextTransitioning object in animateLayoutTransition: this - * delegate method will be called that start the completion process of the transition - */ -- (void)transitionContext:(_ASTransitionContext *)context didComplete:(BOOL)didComplete -{ - ASDisplayNodeAssertMainThread(); - - [self didCompleteLayoutTransition:context]; - - _pendingLayoutTransitionContext = nil; - - [self _pendingLayoutTransitionDidComplete]; -} - -- (void)calculatedLayoutDidChange -{ - // Subclass override -} - -#pragma mark - Display - -NSString * const ASRenderingEngineDidDisplayScheduledNodesNotification = @"ASRenderingEngineDidDisplayScheduledNodes"; -NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp"; - -- (BOOL)displaysAsynchronously -{ - ASDN::MutexLocker l(__instanceLock__); - return [self _locked_displaysAsynchronously]; -} - -/** - * Core implementation of -displaysAsynchronously. - */ -- (BOOL)_locked_displaysAsynchronously -{ - ASAssertLocked(__instanceLock__); - return checkFlag(Synchronous) == NO && _flags.displaysAsynchronously; -} - -- (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously -{ - ASDisplayNodeAssertThreadAffinity(self); - - ASDN::MutexLocker l(__instanceLock__); - - // Can't do this for synchronous nodes (using layers that are not _ASDisplayLayer and so we can't control display prevention/cancel) - if (checkFlag(Synchronous)) { - return; - } - - if (_flags.displaysAsynchronously == displaysAsynchronously) { - return; - } - - _flags.displaysAsynchronously = displaysAsynchronously; - - self._locked_asyncLayer.displaysAsynchronously = displaysAsynchronously; -} - -- (BOOL)rasterizesSubtree -{ - ASDN::MutexLocker l(__instanceLock__); - return _flags.rasterizesSubtree; -} - -- (void)enableSubtreeRasterization -{ - ASDN::MutexLocker l(__instanceLock__); - // Already rasterized from self. - if (_flags.rasterizesSubtree) { - return; - } - - // If rasterized from above, bail. - if (ASHierarchyStateIncludesRasterized(_hierarchyState)) { - ASDisplayNodeFailAssert(@"Subnode of a rasterized node should not have redundant -enableSubtreeRasterization."); - return; - } - - // Ensure not loaded. - if ([self _locked_isNodeLoaded]) { - ASDisplayNodeFailAssert(@"Cannot call %@ on loaded node: %@", NSStringFromSelector(_cmd), self); - return; - } - - // Ensure no loaded subnodes - ASDisplayNode *loadedSubnode = ASDisplayNodeFindFirstSubnode(self, ^BOOL(ASDisplayNode * _Nonnull node) { - return node.nodeLoaded; - }); - if (loadedSubnode != nil) { - ASDisplayNodeFailAssert(@"Cannot call %@ on node %@ with loaded subnode %@", NSStringFromSelector(_cmd), self, loadedSubnode); - return; - } - - _flags.rasterizesSubtree = YES; - - // Tell subnodes that now they're in a rasterized hierarchy (while holding lock!) - for (ASDisplayNode *subnode in _subnodes) { - [subnode enterHierarchyState:ASHierarchyStateRasterized]; - } -} - -- (CGFloat)contentsScaleForDisplay -{ - ASDN::MutexLocker l(__instanceLock__); - - return _contentsScaleForDisplay; -} - -- (void)setContentsScaleForDisplay:(CGFloat)contentsScaleForDisplay -{ - ASDN::MutexLocker l(__instanceLock__); - - if (_contentsScaleForDisplay == contentsScaleForDisplay) { - return; - } - - _contentsScaleForDisplay = contentsScaleForDisplay; -} - -- (void)displayImmediately -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(!checkFlag(Synchronous), @"this method is designed for asynchronous mode only"); - - [self.asyncLayer displayImmediately]; -} - -- (void)recursivelyDisplayImmediately -{ - for (ASDisplayNode *child in self.subnodes) { - [child recursivelyDisplayImmediately]; - } - [self displayImmediately]; -} - -- (void)__setNeedsDisplay -{ - BOOL shouldScheduleForDisplay = NO; - { - ASDN::MutexLocker l(__instanceLock__); - BOOL nowDisplay = ASInterfaceStateIncludesDisplay(_interfaceState); - // FIXME: This should not need to recursively display, so create a non-recursive variant. - // The semantics of setNeedsDisplay (as defined by CALayer behavior) are not recursive. - if (_layer != nil && !checkFlag(Synchronous) && nowDisplay && [self _implementsDisplay]) { - shouldScheduleForDisplay = YES; - } - } - - if (shouldScheduleForDisplay) { - [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; - } -} - -+ (void)scheduleNodeForRecursiveDisplay:(ASDisplayNode *)node -{ - static dispatch_once_t onceToken; - static ASRunLoopQueue *renderQueue; - dispatch_once(&onceToken, ^{ - renderQueue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() - retainObjects:NO - handler:^(ASDisplayNode * _Nonnull dequeuedItem, BOOL isQueueDrained) { - [dequeuedItem _recursivelyTriggerDisplayAndBlock:NO]; - if (isQueueDrained) { - CFTimeInterval timestamp = CACurrentMediaTime(); - [[NSNotificationCenter defaultCenter] postNotificationName:ASRenderingEngineDidDisplayScheduledNodesNotification - object:nil - userInfo:@{ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp: @(timestamp)}]; - } - }]; - }); - - as_log_verbose(ASDisplayLog(), "%s %@", sel_getName(_cmd), node); - [renderQueue enqueue:node]; -} - -/// Helper method to summarize whether or not the node run through the display process -- (BOOL)_implementsDisplay -{ - ASDN::MutexLocker l(__instanceLock__); - - return _flags.implementsDrawRect || _flags.implementsImageDisplay || _flags.rasterizesSubtree; -} - -// Track that a node will be displayed as part of the current node hierarchy. -// The node sending the message should usually be passed as the parameter, similar to the delegation pattern. -- (void)_pendingNodeWillDisplay:(ASDisplayNode *)node -{ - ASDisplayNodeAssertMainThread(); - - // No lock needed as _pendingDisplayNodes is main thread only - if (!_pendingDisplayNodes) { - _pendingDisplayNodes = [[ASWeakSet alloc] init]; - } - - [_pendingDisplayNodes addObject:node]; -} - -// Notify that a node that was pending display finished -// The node sending the message should usually be passed as the parameter, similar to the delegation pattern. -- (void)_pendingNodeDidDisplay:(ASDisplayNode *)node -{ - ASDisplayNodeAssertMainThread(); - - // No lock for _pendingDisplayNodes needed as it's main thread only - [_pendingDisplayNodes removeObject:node]; - - if (_pendingDisplayNodes.isEmpty) { - - [self hierarchyDisplayDidFinish]; - [self enumerateInterfaceStateDelegates:^(id delegate) { - [delegate hierarchyDisplayDidFinish]; - }]; - - BOOL placeholderShouldPersist = [self placeholderShouldPersist]; - - __instanceLock__.lock(); - if (_placeholderLayer.superlayer && !placeholderShouldPersist) { - void (^cleanupBlock)() = ^{ - [_placeholderLayer removeFromSuperlayer]; - }; - - if (_placeholderFadeDuration > 0.0 && ASInterfaceStateIncludesVisible(self.interfaceState)) { - [CATransaction begin]; - [CATransaction setCompletionBlock:cleanupBlock]; - [CATransaction setAnimationDuration:_placeholderFadeDuration]; - _placeholderLayer.opacity = 0.0; - [CATransaction commit]; - } else { - cleanupBlock(); - } - } - __instanceLock__.unlock(); - } -} - -- (void)hierarchyDisplayDidFinish -{ - // Subclass hook -} - -// Helper method to determine if it's safe to call setNeedsDisplay on a layer without throwing away the content. -// For details look at the comment on the canCallSetNeedsDisplayOfLayer flag -- (BOOL)_canCallSetNeedsDisplayOfLayer -{ - ASDN::MutexLocker l(__instanceLock__); - return _flags.canCallSetNeedsDisplayOfLayer; -} - -void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) -{ - // This recursion must handle layers in various states: - // 1. Just added to hierarchy, CA hasn't yet called -display - // 2. Previously in a hierarchy (such as a working window owned by an Intelligent Preloading class, like ASTableView / ASCollectionView / ASViewController) - // 3. Has no content to display at all - // Specifically for case 1), we need to explicitly trigger a -display call now. - // Otherwise, there is no opportunity to block the main thread after CoreAnimation's transaction commit - // (even a runloop observer at a late call order will not stop the next frame from compositing, showing placeholders). - - ASDisplayNode *node = [layer asyncdisplaykit_node]; - - if (node.isSynchronous && [node _canCallSetNeedsDisplayOfLayer]) { - // Layers for UIKit components that are wrapped within a node needs to be set to be displayed as the contents of - // the layer get's cleared and would not be recreated otherwise. - // We do not call this for _ASDisplayLayer as an optimization. - [layer setNeedsDisplay]; - } - - if ([node _implementsDisplay]) { - // For layers that do get displayed here, this immediately kicks off the work on the concurrent -[_ASDisplayLayer displayQueue]. - // At the same time, it creates an associated _ASAsyncTransaction, which we can use to block on display completion. See ASDisplayNode+AsyncDisplay.mm. - [layer displayIfNeeded]; - } - - // Kick off the recursion first, so that all necessary display calls are sent and the displayQueue is full of parallelizable work. - // NOTE: The docs report that `sublayers` returns a copy but it actually doesn't. - for (CALayer *sublayer in [layer.sublayers copy]) { - recursivelyTriggerDisplayForLayer(sublayer, shouldBlock); - } - - if (shouldBlock) { - // As the recursion unwinds, verify each transaction is complete and block if it is not. - // While blocking on one transaction, others may be completing concurrently, so it doesn't matter which blocks first. - BOOL waitUntilComplete = (!node.shouldBypassEnsureDisplay); - if (waitUntilComplete) { - for (_ASAsyncTransaction *transaction in [layer.asyncdisplaykit_asyncLayerTransactions copy]) { - // Even if none of the layers have had a chance to start display earlier, they will still be allowed to saturate a multicore CPU while blocking main. - // This significantly reduces time on the main thread relative to UIKit. - [transaction waitUntilComplete]; - } - } - } -} - -- (void)_recursivelyTriggerDisplayAndBlock:(BOOL)shouldBlock -{ - ASDisplayNodeAssertMainThread(); - - CALayer *layer = self.layer; - // -layoutIfNeeded is recursive, and even walks up to superlayers to check if they need layout, - // so we should call it outside of starting the recursion below. If our own layer is not marked - // as dirty, we can assume layout has run on this subtree before. - if ([layer needsLayout]) { - [layer layoutIfNeeded]; - } - recursivelyTriggerDisplayForLayer(layer, shouldBlock); -} - -- (void)recursivelyEnsureDisplaySynchronously:(BOOL)synchronously -{ - [self _recursivelyTriggerDisplayAndBlock:synchronously]; -} - -- (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay -{ - ASDN::MutexLocker l(__instanceLock__); - _flags.shouldBypassEnsureDisplay = shouldBypassEnsureDisplay; -} - -- (BOOL)shouldBypassEnsureDisplay -{ - ASDN::MutexLocker l(__instanceLock__); - return _flags.shouldBypassEnsureDisplay; -} - -- (void)setNeedsDisplayAtScale:(CGFloat)contentsScale -{ - { - ASDN::MutexLocker l(__instanceLock__); - if (contentsScale == _contentsScaleForDisplay) { - return; - } - - _contentsScaleForDisplay = contentsScale; - } - - [self setNeedsDisplay]; -} - -- (void)recursivelySetNeedsDisplayAtScale:(CGFloat)contentsScale -{ - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { - [node setNeedsDisplayAtScale:contentsScale]; - }); -} - -- (void)_layoutClipCornersIfNeeded -{ - ASDisplayNodeAssertMainThread(); - if (_clipCornerLayers[0] == nil) { - return; - } - - CGSize boundsSize = self.bounds.size; - for (int idx = 0; idx < NUM_CLIP_CORNER_LAYERS; idx++) { - BOOL isTop = (idx == 0 || idx == 1); - BOOL isRight = (idx == 1 || idx == 2); - if (_clipCornerLayers[idx]) { - // Note the Core Animation coordinates are reversed for y; 0 is at the bottom. - _clipCornerLayers[idx].position = CGPointMake(isRight ? boundsSize.width : 0.0, isTop ? boundsSize.height : 0.0); - [_layer addSublayer:_clipCornerLayers[idx]]; - } - } -} - -- (void)_updateClipCornerLayerContentsWithRadius:(CGFloat)radius backgroundColor:(UIColor *)backgroundColor -{ - ASPerformBlockOnMainThread(^{ - for (int idx = 0; idx < NUM_CLIP_CORNER_LAYERS; idx++) { - // Layers are, in order: Top Left, Top Right, Bottom Right, Bottom Left. - // anchorPoint is Bottom Left at 0,0 and Top Right at 1,1. - BOOL isTop = (idx == 0 || idx == 1); - BOOL isRight = (idx == 1 || idx == 2); - - CGSize size = CGSizeMake(radius + 1, radius + 1); - ASGraphicsBeginImageContextWithOptions(size, NO, self.contentsScaleForDisplay); - - CGContextRef ctx = UIGraphicsGetCurrentContext(); - if (isRight == YES) { - CGContextTranslateCTM(ctx, -radius + 1, 0); - } - if (isTop == YES) { - CGContextTranslateCTM(ctx, 0, -radius + 1); - } - UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, radius * 2, radius * 2) cornerRadius:radius]; - [roundedRect setUsesEvenOddFillRule:YES]; - [roundedRect appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(-1, -1, radius * 2 + 1, radius * 2 + 1)]]; - [backgroundColor setFill]; - [roundedRect fill]; - - // No lock needed, as _clipCornerLayers is only modified on the main thread. - CALayer *clipCornerLayer = _clipCornerLayers[idx]; - clipCornerLayer.contents = (id)(ASGraphicsGetImageAndEndCurrentContext().CGImage); - clipCornerLayer.bounds = CGRectMake(0.0, 0.0, size.width, size.height); - clipCornerLayer.anchorPoint = CGPointMake(isRight ? 1.0 : 0.0, isTop ? 1.0 : 0.0); - } - [self _layoutClipCornersIfNeeded]; - }); -} - -- (void)_setClipCornerLayersVisible:(BOOL)visible -{ - ASPerformBlockOnMainThread(^{ - ASDisplayNodeAssertMainThread(); - if (visible) { - for (int idx = 0; idx < NUM_CLIP_CORNER_LAYERS; idx++) { - if (_clipCornerLayers[idx] == nil) { - static ASDisplayNodeCornerLayerDelegate *clipCornerLayers; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - clipCornerLayers = [[ASDisplayNodeCornerLayerDelegate alloc] init]; - }); - _clipCornerLayers[idx] = [[CALayer alloc] init]; - _clipCornerLayers[idx].zPosition = 99999; - _clipCornerLayers[idx].delegate = clipCornerLayers; - } - } - [self _updateClipCornerLayerContentsWithRadius:_cornerRadius backgroundColor:self.backgroundColor]; - } else { - for (int idx = 0; idx < NUM_CLIP_CORNER_LAYERS; idx++) { - [_clipCornerLayers[idx] removeFromSuperlayer]; - _clipCornerLayers[idx] = nil; - } - } - }); -} - -- (void)updateCornerRoundingWithType:(ASCornerRoundingType)newRoundingType cornerRadius:(CGFloat)newCornerRadius -{ - __instanceLock__.lock(); - CGFloat oldCornerRadius = _cornerRadius; - ASCornerRoundingType oldRoundingType = _cornerRoundingType; - - _cornerRadius = newCornerRadius; - _cornerRoundingType = newRoundingType; - __instanceLock__.unlock(); - - ASPerformBlockOnMainThread(^{ - ASDisplayNodeAssertMainThread(); - - if (oldRoundingType != newRoundingType || oldCornerRadius != newCornerRadius) { - if (oldRoundingType == ASCornerRoundingTypeDefaultSlowCALayer) { - if (newRoundingType == ASCornerRoundingTypePrecomposited) { - self.layerCornerRadius = 0.0; - if (oldCornerRadius > 0.0) { - [self displayImmediately]; - } else { - [self setNeedsDisplay]; // Async display is OK if we aren't replacing an existing .cornerRadius. - } - } - else if (newRoundingType == ASCornerRoundingTypeClipping) { - self.layerCornerRadius = 0.0; - [self _setClipCornerLayersVisible:YES]; - } else if (newRoundingType == ASCornerRoundingTypeDefaultSlowCALayer) { - self.layerCornerRadius = newCornerRadius; - } - } - else if (oldRoundingType == ASCornerRoundingTypePrecomposited) { - if (newRoundingType == ASCornerRoundingTypeDefaultSlowCALayer) { - self.layerCornerRadius = newCornerRadius; - [self setNeedsDisplay]; - } - else if (newRoundingType == ASCornerRoundingTypePrecomposited) { - // Corners are already precomposited, but the radius has changed. - // Default to async re-display. The user may force a synchronous display if desired. - [self setNeedsDisplay]; - } - else if (newRoundingType == ASCornerRoundingTypeClipping) { - [self _setClipCornerLayersVisible:YES]; - [self setNeedsDisplay]; - } - } - else if (oldRoundingType == ASCornerRoundingTypeClipping) { - if (newRoundingType == ASCornerRoundingTypeDefaultSlowCALayer) { - self.layerCornerRadius = newCornerRadius; - [self _setClipCornerLayersVisible:NO]; - } - else if (newRoundingType == ASCornerRoundingTypePrecomposited) { - [self _setClipCornerLayersVisible:NO]; - [self displayImmediately]; - } - else if (newRoundingType == ASCornerRoundingTypeClipping) { - // Clip corners already exist, but the radius has changed. - [self _updateClipCornerLayerContentsWithRadius:newCornerRadius backgroundColor:self.backgroundColor]; - } - } - } - }); -} - -- (void)recursivelySetDisplaySuspended:(BOOL)flag -{ - _recursivelySetDisplaySuspended(self, nil, flag); -} - -// TODO: Replace this with ASDisplayNodePerformBlockOnEveryNode or a variant with a condition / test block. -static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, BOOL flag) -{ - // If there is no layer, but node whose its view is loaded, then we can traverse down its layer hierarchy. Otherwise we must stick to the node hierarchy to avoid loading views prematurely. Note that for nodes that haven't loaded their views, they can't possibly have subviews/sublayers, so we don't need to traverse the layer hierarchy for them. - if (!layer && node && node.nodeLoaded) { - layer = node.layer; - } - - // If we don't know the node, but the layer is an async layer, get the node from the layer. - if (!node && layer && [layer isKindOfClass:[_ASDisplayLayer class]]) { - node = layer.asyncdisplaykit_node; - } - - // Set the flag on the node. If this is a pure layer (no node) then this has no effect (plain layers don't support preventing/cancelling display). - node.displaySuspended = flag; - - if (layer && !node.rasterizesSubtree) { - // If there is a layer, recurse down the layer hierarchy to set the flag on descendants. This will cover both layer-based and node-based children. - for (CALayer *sublayer in layer.sublayers) { - _recursivelySetDisplaySuspended(nil, sublayer, flag); - } - } else { - // If there is no layer (view not loaded yet) or this node rasterizes descendants (there won't be a layer tree to traverse), recurse down the subnode hierarchy to set the flag on descendants. This covers only node-based children, but for a node whose view is not loaded it can't possibly have nodeless children. - for (ASDisplayNode *subnode in node.subnodes) { - _recursivelySetDisplaySuspended(subnode, nil, flag); - } - } -} - -- (BOOL)displaySuspended -{ - ASDN::MutexLocker l(__instanceLock__); - return _flags.displaySuspended; -} - -- (void)setDisplaySuspended:(BOOL)flag -{ - ASDisplayNodeAssertThreadAffinity(self); - __instanceLock__.lock(); - - // Can't do this for synchronous nodes (using layers that are not _ASDisplayLayer and so we can't control display prevention/cancel) - if (checkFlag(Synchronous) || _flags.displaySuspended == flag) { - __instanceLock__.unlock(); - return; - } - - _flags.displaySuspended = flag; - - self._locked_asyncLayer.displaySuspended = flag; - - ASDisplayNode *supernode = _supernode; - __instanceLock__.unlock(); - - if ([self _implementsDisplay]) { - // Display start and finish methods needs to happen on the main thread - ASPerformBlockOnMainThread(^{ - if (flag) { - [supernode subnodeDisplayDidFinish:self]; - } else { - [supernode subnodeDisplayWillStart:self]; - } - }); - } -} - -#pragma mark <_ASDisplayLayerDelegate> - -- (void)willDisplayAsyncLayer:(_ASDisplayLayer *)layer asynchronously:(BOOL)asynchronously -{ - // Subclass hook. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [self displayWillStart]; -#pragma clang diagnostic pop - - [self displayWillStartAsynchronously:asynchronously]; -} - -- (void)didDisplayAsyncLayer:(_ASDisplayLayer *)layer -{ - // Subclass hook. - [self displayDidFinish]; -} - -- (void)displayWillStart {} -- (void)displayWillStartAsynchronously:(BOOL)asynchronously -{ - ASDisplayNodeAssertMainThread(); - - ASDisplayNodeLogEvent(self, @"displayWillStart"); - // in case current node takes longer to display than it's subnodes, treat it as a dependent node - [self _pendingNodeWillDisplay:self]; - - __instanceLock__.lock(); - ASDisplayNode *supernode = _supernode; - __instanceLock__.unlock(); - - [supernode subnodeDisplayWillStart:self]; -} - -- (void)displayDidFinish -{ - ASDisplayNodeAssertMainThread(); - - ASDisplayNodeLogEvent(self, @"displayDidFinish"); - [self _pendingNodeDidDisplay:self]; - - __instanceLock__.lock(); - ASDisplayNode *supernode = _supernode; - __instanceLock__.unlock(); - - [supernode subnodeDisplayDidFinish:self]; -} - -- (void)subnodeDisplayWillStart:(ASDisplayNode *)subnode -{ - // Subclass hook - [self _pendingNodeWillDisplay:subnode]; -} - -- (void)subnodeDisplayDidFinish:(ASDisplayNode *)subnode -{ - // Subclass hook - [self _pendingNodeDidDisplay:subnode]; -} - -#pragma mark - -// We are only the delegate for the layer when we are layer-backed, as UIView performs this function normally -- (id)actionForLayer:(CALayer *)layer forKey:(NSString *)event -{ - if (event == kCAOnOrderIn) { - [self __enterHierarchy]; - } else if (event == kCAOnOrderOut) { - [self __exitHierarchy]; - } - - ASDisplayNodeAssert(_flags.layerBacked, @"We shouldn't get called back here unless we are layer-backed."); - return (id)kCFNull; -} - -#pragma mark - Error Handling - -+ (void)setNonFatalErrorBlock:(ASDisplayNodeNonFatalErrorBlock)nonFatalErrorBlock -{ - if (_nonFatalErrorBlock != nonFatalErrorBlock) { - _nonFatalErrorBlock = [nonFatalErrorBlock copy]; - } -} - -+ (ASDisplayNodeNonFatalErrorBlock)nonFatalErrorBlock -{ - return _nonFatalErrorBlock; -} - -#pragma mark - Converting to and from the Node's Coordinate System - -- (CATransform3D)_transformToAncestor:(ASDisplayNode *)ancestor -{ - CATransform3D transform = CATransform3DIdentity; - ASDisplayNode *currentNode = self; - while (currentNode.supernode) { - if (currentNode == ancestor) { - return transform; - } - - CGPoint anchorPoint = currentNode.anchorPoint; - CGRect bounds = currentNode.bounds; - CGPoint position = currentNode.position; - CGPoint origin = CGPointMake(position.x - bounds.size.width * anchorPoint.x, - position.y - bounds.size.height * anchorPoint.y); - - transform = CATransform3DTranslate(transform, origin.x, origin.y, 0); - transform = CATransform3DTranslate(transform, -bounds.origin.x, -bounds.origin.y, 0); - currentNode = currentNode.supernode; - } - return transform; -} - -static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNode *referenceNode, ASDisplayNode *targetNode) -{ - ASDisplayNode *ancestor = ASDisplayNodeFindClosestCommonAncestor(referenceNode, targetNode); - - // Transform into global (away from reference coordinate space) - CATransform3D transformToGlobal = [referenceNode _transformToAncestor:ancestor]; - - // Transform into local (via inverse transform from target to ancestor) - CATransform3D transformToLocal = CATransform3DInvert([targetNode _transformToAncestor:ancestor]); - - return CATransform3DConcat(transformToGlobal, transformToLocal); -} - -- (CGPoint)convertPoint:(CGPoint)point fromNode:(ASDisplayNode *)node -{ - ASDisplayNodeAssertThreadAffinity(self); - - /** - * When passed node=nil, all methods in this family use the UIView-style - * behavior – that is, convert from/to window coordinates if there's a window, - * otherwise return the point untransformed. - */ - if (node == nil && self.nodeLoaded) { - CALayer *layer = self.layer; - if (UIWindow *window = ASFindWindowOfLayer(layer)) { - return [layer convertPoint:point fromLayer:window.layer]; - } else { - return point; - } - } - - // Get root node of the accessible node hierarchy, if node not specified - node = node ? : ASDisplayNodeUltimateParentOfNode(self); - - // Calculate transform to map points between coordinate spaces - CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(node, self); - CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); - ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); - - // Apply to point - return CGPointApplyAffineTransform(point, flattenedTransform); -} - -- (CGPoint)convertPoint:(CGPoint)point toNode:(ASDisplayNode *)node -{ - ASDisplayNodeAssertThreadAffinity(self); - - if (node == nil && self.nodeLoaded) { - CALayer *layer = self.layer; - if (UIWindow *window = ASFindWindowOfLayer(layer)) { - return [layer convertPoint:point toLayer:window.layer]; - } else { - return point; - } - } - - // Get root node of the accessible node hierarchy, if node not specified - node = node ? : ASDisplayNodeUltimateParentOfNode(self); - - // Calculate transform to map points between coordinate spaces - CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(self, node); - CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); - ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); - - // Apply to point - return CGPointApplyAffineTransform(point, flattenedTransform); -} - -- (CGRect)convertRect:(CGRect)rect fromNode:(ASDisplayNode *)node -{ - ASDisplayNodeAssertThreadAffinity(self); - - if (node == nil && self.nodeLoaded) { - CALayer *layer = self.layer; - if (UIWindow *window = ASFindWindowOfLayer(layer)) { - return [layer convertRect:rect fromLayer:window.layer]; - } else { - return rect; - } - } - - // Get root node of the accessible node hierarchy, if node not specified - node = node ? : ASDisplayNodeUltimateParentOfNode(self); - - // Calculate transform to map points between coordinate spaces - CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(node, self); - CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); - ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); - - // Apply to rect - return CGRectApplyAffineTransform(rect, flattenedTransform); -} - -- (CGRect)convertRect:(CGRect)rect toNode:(ASDisplayNode *)node -{ - ASDisplayNodeAssertThreadAffinity(self); - - if (node == nil && self.nodeLoaded) { - CALayer *layer = self.layer; - if (UIWindow *window = ASFindWindowOfLayer(layer)) { - return [layer convertRect:rect toLayer:window.layer]; - } else { - return rect; - } - } - - // Get root node of the accessible node hierarchy, if node not specified - node = node ? : ASDisplayNodeUltimateParentOfNode(self); - - // Calculate transform to map points between coordinate spaces - CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(self, node); - CGAffineTransform flattenedTransform = CATransform3DGetAffineTransform(nodeTransform); - ASDisplayNodeAssertTrue(CATransform3DIsAffine(nodeTransform)); - - // Apply to rect - return CGRectApplyAffineTransform(rect, flattenedTransform); -} - -#pragma mark - Managing the Node Hierarchy - -ASDISPLAYNODE_INLINE bool shouldDisableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASDisplayNode *to) { - if (!from || !to) return NO; - if (from.isSynchronous) return NO; - if (to.isSynchronous) return NO; - if (from.isInHierarchy != to.isInHierarchy) return NO; - return YES; -} - -/// Returns incremented value of i if i is not NSNotFound -ASDISPLAYNODE_INLINE NSInteger incrementIfFound(NSInteger i) { - return i == NSNotFound ? NSNotFound : i + 1; -} - -/// Returns if a node is a member of a rasterized tree -ASDISPLAYNODE_INLINE BOOL canUseViewAPI(ASDisplayNode *node, ASDisplayNode *subnode) { - return (subnode.isLayerBacked == NO && node.isLayerBacked == NO); -} - -/// Returns if node is a member of a rasterized tree -ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { - return (node.rasterizesSubtree || (node.hierarchyState & ASHierarchyStateRasterized)); -} - -// NOTE: This method must be dealloc-safe (should not retain self). -- (ASDisplayNode *)supernode -{ -#if CHECK_LOCKING_SAFETY - if (__instanceLock__.locked()) { - NSLog(@"WARNING: Accessing supernode while holding recursive instance lock of this node is worrisome. It's likely that you will soon try to acquire the supernode's lock, and this can easily cause deadlocks."); - } -#endif - - ASDN::MutexLocker l(__instanceLock__); - return _supernode; -} - -- (void)_setSupernode:(ASDisplayNode *)newSupernode -{ - BOOL supernodeDidChange = NO; - ASDisplayNode *oldSupernode = nil; - { - ASDN::MutexLocker l(__instanceLock__); - if (_supernode != newSupernode) { - oldSupernode = _supernode; // Access supernode properties outside of lock to avoid remote chance of deadlock, - // in case supernode implementation must access one of our properties. - _supernode = newSupernode; - supernodeDidChange = YES; - } - } - - if (supernodeDidChange) { - ASDisplayNodeLogEvent(self, @"supernodeDidChange: %@, oldValue = %@", ASObjectDescriptionMakeTiny(newSupernode), ASObjectDescriptionMakeTiny(oldSupernode)); - // Hierarchy state - ASHierarchyState stateToEnterOrExit = (newSupernode ? newSupernode.hierarchyState - : oldSupernode.hierarchyState); - - // Rasterized state - BOOL parentWasOrIsRasterized = (newSupernode ? newSupernode.rasterizesSubtree - : oldSupernode.rasterizesSubtree); - if (parentWasOrIsRasterized) { - stateToEnterOrExit |= ASHierarchyStateRasterized; - } - if (newSupernode) { - [self enterHierarchyState:stateToEnterOrExit]; - - // If a node was added to a supernode, the supernode could be in a layout pending state. All of the hierarchy state - // properties related to the transition need to be copied over as well as propagated down the subtree. - // This is especially important as with automatic subnode management, adding subnodes can happen while a transition - // is in fly - if (ASHierarchyStateIncludesLayoutPending(stateToEnterOrExit)) { - int32_t pendingTransitionId = newSupernode->_pendingTransitionID; - if (pendingTransitionId != ASLayoutElementContextInvalidTransitionID) { - { - _pendingTransitionID = pendingTransitionId; - - // Propagate down the new pending transition id - ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { - node->_pendingTransitionID = pendingTransitionId; - }); - } - } - } - - // Now that we have a supernode, propagate its traits to self. - ASTraitCollectionPropagateDown(self, newSupernode.primitiveTraitCollection); - - } else { - // If a node will be removed from the supernode it should go out from the layout pending state to remove all - // layout pending state related properties on the node - stateToEnterOrExit |= ASHierarchyStateLayoutPending; - - [self exitHierarchyState:stateToEnterOrExit]; - - // We only need to explicitly exit hierarchy here if we were rasterized. - // Otherwise we will exit the hierarchy when our view/layer does so - // which has some nice carry-over machinery to handle cases where we are removed from a hierarchy - // and then added into it again shortly after. - __instanceLock__.lock(); - BOOL isInHierarchy = _flags.isInHierarchy; - __instanceLock__.unlock(); - - if (parentWasOrIsRasterized && isInHierarchy) { - [self __exitHierarchy]; - } - } - } -} - -- (NSArray *)subnodes -{ - ASDN::MutexLocker l(__instanceLock__); - if (_cachedSubnodes == nil) { - _cachedSubnodes = [_subnodes copy]; - } else { - ASDisplayNodeAssert(ASObjectIsEqual(_cachedSubnodes, _subnodes), @"Expected _subnodes and _cachedSubnodes to have the same contents."); - } - return _cachedSubnodes ?: @[]; -} - -/* - * Central private helper method that should eventually be called if submethods add, insert or replace subnodes - * This method is called with thread affinity and without lock held. - * - * @param subnode The subnode to insert - * @param subnodeIndex The index in _subnodes to insert it - * @param viewSublayerIndex The index in layer.sublayers (not view.subviews) at which to insert the view (use if we can use the view API) otherwise pass NSNotFound - * @param sublayerIndex The index in layer.sublayers at which to insert the layer (use if either parent or subnode is layer-backed) otherwise pass NSNotFound - * @param oldSubnode Remove this subnode before inserting; ok to be nil if no removal is desired - */ -- (void)_insertSubnode:(ASDisplayNode *)subnode atSubnodeIndex:(NSInteger)subnodeIndex sublayerIndex:(NSInteger)sublayerIndex andRemoveSubnode:(ASDisplayNode *)oldSubnode -{ - ASDisplayNodeAssertThreadAffinity(self); - ASAssertUnlocked(__instanceLock__); - - as_log_verbose(ASNodeLog(), "Insert subnode %@ at index %zd of %@ and remove subnode %@", subnode, subnodeIndex, self, oldSubnode); - - if (subnode == nil || subnode == self) { - ASDisplayNodeFailAssert(@"Cannot insert a nil subnode or self as subnode"); - return; - } - - if (subnodeIndex == NSNotFound) { - ASDisplayNodeFailAssert(@"Try to insert node on an index that was not found"); - return; - } - - if (self.layerBacked && !subnode.layerBacked) { - ASDisplayNodeFailAssert(@"Cannot add a view-backed node as a subnode of a layer-backed node. Supernode: %@, subnode: %@", self, subnode); - return; - } - - BOOL isRasterized = subtreeIsRasterized(self); - if (isRasterized && subnode.nodeLoaded) { - ASDisplayNodeFailAssert(@"Cannot add loaded node %@ to rasterized subtree of node %@", ASObjectDescriptionMakeTiny(subnode), ASObjectDescriptionMakeTiny(self)); - return; - } - - __instanceLock__.lock(); - NSUInteger subnodesCount = _subnodes.count; - __instanceLock__.unlock(); - if (subnodeIndex > subnodesCount || subnodeIndex < 0) { - ASDisplayNodeFailAssert(@"Cannot insert a subnode at index %ld. Count is %ld", (long)subnodeIndex, (long)subnodesCount); - return; - } - - // Disable appearance methods during move between supernodes, but make sure we restore their state after we do our thing - ASDisplayNode *oldParent = subnode.supernode; - BOOL disableNotifications = shouldDisableNotificationsForMovingBetweenParents(oldParent, self); - if (disableNotifications) { - [subnode __incrementVisibilityNotificationsDisabled]; - } - - [subnode _removeFromSupernode]; - [oldSubnode _removeFromSupernode]; - - __instanceLock__.lock(); - if (_subnodes == nil) { - _subnodes = [[NSMutableArray alloc] init]; - } - [_subnodes insertObject:subnode atIndex:subnodeIndex]; - _cachedSubnodes = nil; - __instanceLock__.unlock(); - - // This call will apply our .hierarchyState to the new subnode. - // If we are a managed hierarchy, as in ASCellNode trees, it will also apply our .interfaceState. - [subnode _setSupernode:self]; - - // If this subnode will be rasterized, enter hierarchy if needed - // TODO: Move this into _setSupernode: ? - if (isRasterized) { - if (self.inHierarchy) { - [subnode __enterHierarchy]; - } - } else if (self.nodeLoaded) { - // If not rasterizing, and node is loaded insert the subview/sublayer now. - [self _insertSubnodeSubviewOrSublayer:subnode atIndex:sublayerIndex]; - } // Otherwise we will insert subview/sublayer when we get loaded - - ASDisplayNodeAssert(disableNotifications == shouldDisableNotificationsForMovingBetweenParents(oldParent, self), @"Invariant violated"); - if (disableNotifications) { - [subnode __decrementVisibilityNotificationsDisabled]; - } -} - -/* - * Inserts the view or layer of the given node at the given index - * - * @param subnode The subnode to insert - * @param idx The index in _view.subviews or _layer.sublayers at which to insert the subnode.view or - * subnode.layer of the subnode - */ -- (void)_insertSubnodeSubviewOrSublayer:(ASDisplayNode *)subnode atIndex:(NSInteger)idx -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(self.nodeLoaded, @"_insertSubnodeSubviewOrSublayer:atIndex: should never be called before our own view is created"); - - ASDisplayNodeAssert(idx != NSNotFound, @"Try to insert node on an index that was not found"); - if (idx == NSNotFound) { - return; - } - - // Because the view and layer can only be created and destroyed on Main, that is also the only thread - // where the view and layer can change. We can avoid locking. - - // If we can use view API, do. Due to an apple bug, -insertSubview:atIndex: actually wants a LAYER index, - // which we pass in. - if (canUseViewAPI(self, subnode)) { - [_view insertSubview:subnode.view atIndex:idx]; - } else { - [_layer insertSublayer:subnode.layer atIndex:(unsigned int)idx]; - } -} - -- (void)addSubnode:(ASDisplayNode *)subnode -{ - ASDisplayNodeLogEvent(self, @"addSubnode: %@ with automaticallyManagesSubnodes: %@", - subnode, self.automaticallyManagesSubnodes ? @"YES" : @"NO"); - [self _addSubnode:subnode]; -} - -- (void)_addSubnode:(ASDisplayNode *)subnode -{ - ASDisplayNodeAssertThreadAffinity(self); - - ASDisplayNodeAssert(subnode, @"Cannot insert a nil subnode"); - - // Don't add if it's already a subnode - ASDisplayNode *oldParent = subnode.supernode; - if (!subnode || subnode == self || oldParent == self) { - return; - } - - NSUInteger subnodesIndex; - NSUInteger sublayersIndex; - { - ASDN::MutexLocker l(__instanceLock__); - subnodesIndex = _subnodes.count; - sublayersIndex = _layer.sublayers.count; - } - - [self _insertSubnode:subnode atSubnodeIndex:subnodesIndex sublayerIndex:sublayersIndex andRemoveSubnode:nil]; -} - -- (void)_addSubnodeViewsAndLayers -{ - ASDisplayNodeAssertMainThread(); - - TIME_SCOPED(_debugTimeToAddSubnodeViews); - - for (ASDisplayNode *node in self.subnodes) { - [self _addSubnodeSubviewOrSublayer:node]; - } -} - -- (void)_addSubnodeSubviewOrSublayer:(ASDisplayNode *)subnode -{ - ASDisplayNodeAssertMainThread(); - - // Due to a bug in Apple's framework we have to use the layer index to insert a subview - // so just use the count of the sublayers to add the subnode - NSInteger idx = _layer.sublayers.count; // No locking is needed as it's main thread only - [self _insertSubnodeSubviewOrSublayer:subnode atIndex:idx]; -} - -- (void)replaceSubnode:(ASDisplayNode *)oldSubnode withSubnode:(ASDisplayNode *)replacementSubnode -{ - ASDisplayNodeLogEvent(self, @"replaceSubnode: %@ withSubnode: %@ with automaticallyManagesSubnodes: %@", - oldSubnode, replacementSubnode, self.automaticallyManagesSubnodes ? @"YES" : @"NO"); - [self _replaceSubnode:oldSubnode withSubnode:replacementSubnode]; -} - -- (void)_replaceSubnode:(ASDisplayNode *)oldSubnode withSubnode:(ASDisplayNode *)replacementSubnode -{ - ASDisplayNodeAssertThreadAffinity(self); - - if (replacementSubnode == nil) { - ASDisplayNodeFailAssert(@"Invalid subnode to replace"); - return; - } - - if (oldSubnode.supernode != self) { - ASDisplayNodeFailAssert(@"Old Subnode to replace must be a subnode"); - return; - } - - ASDisplayNodeAssert(!(self.nodeLoaded && !oldSubnode.nodeLoaded), @"We have view loaded, but child node does not."); - - NSInteger subnodeIndex; - NSInteger sublayerIndex = NSNotFound; - { - ASDN::MutexLocker l(__instanceLock__); - ASDisplayNodeAssert(_subnodes, @"You should have subnodes if you have a subnode"); - - subnodeIndex = [_subnodes indexOfObjectIdenticalTo:oldSubnode]; - - // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the - // hierarchy and none of this could possibly work. - if (subtreeIsRasterized(self) == NO) { - if (_layer) { - sublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:oldSubnode.layer]; - ASDisplayNodeAssert(sublayerIndex != NSNotFound, @"Somehow oldSubnode's supernode is self, yet we could not find it in our layers to replace"); - if (sublayerIndex == NSNotFound) { - return; - } - } - } - } - - [self _insertSubnode:replacementSubnode atSubnodeIndex:subnodeIndex sublayerIndex:sublayerIndex andRemoveSubnode:oldSubnode]; -} - -- (void)insertSubnode:(ASDisplayNode *)subnode belowSubnode:(ASDisplayNode *)below -{ - ASDisplayNodeLogEvent(self, @"insertSubnode: %@ belowSubnode: %@ with automaticallyManagesSubnodes: %@", - subnode, below, self.automaticallyManagesSubnodes ? @"YES" : @"NO"); - [self _insertSubnode:subnode belowSubnode:below]; -} - -- (void)_insertSubnode:(ASDisplayNode *)subnode belowSubnode:(ASDisplayNode *)below -{ - ASDisplayNodeAssertThreadAffinity(self); - ASAssertUnlocked(__instanceLock__); - - if (subnode == nil) { - ASDisplayNodeFailAssert(@"Cannot insert a nil subnode"); - return; - } - - if (below.supernode != self) { - ASDisplayNodeFailAssert(@"Node to insert below must be a subnode"); - return; - } - - NSInteger belowSubnodeIndex; - NSInteger belowSublayerIndex = NSNotFound; - { - ASDN::MutexLocker l(__instanceLock__); - ASDisplayNodeAssert(_subnodes, @"You should have subnodes if you have a subnode"); - - belowSubnodeIndex = [_subnodes indexOfObjectIdenticalTo:below]; - - // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the - // hierarchy and none of this could possibly work. - if (subtreeIsRasterized(self) == NO) { - if (_layer) { - belowSublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:below.layer]; - ASDisplayNodeAssert(belowSublayerIndex != NSNotFound, @"Somehow below's supernode is self, yet we could not find it in our layers to reference"); - if (belowSublayerIndex == NSNotFound) - return; - } - - ASDisplayNodeAssert(belowSubnodeIndex != NSNotFound, @"Couldn't find above in subnodes"); - - // If the subnode is already in the subnodes array / sublayers and it's before the below node, removing it to - // insert it will mess up our calculation - if (subnode.supernode == self) { - NSInteger currentIndexInSubnodes = [_subnodes indexOfObjectIdenticalTo:subnode]; - if (currentIndexInSubnodes < belowSubnodeIndex) { - belowSubnodeIndex--; - } - if (_layer) { - NSInteger currentIndexInSublayers = [_layer.sublayers indexOfObjectIdenticalTo:subnode.layer]; - if (currentIndexInSublayers < belowSublayerIndex) { - belowSublayerIndex--; - } - } - } - } - } - - ASDisplayNodeAssert(belowSubnodeIndex != NSNotFound, @"Couldn't find below in subnodes"); - - [self _insertSubnode:subnode atSubnodeIndex:belowSubnodeIndex sublayerIndex:belowSublayerIndex andRemoveSubnode:nil]; -} - -- (void)insertSubnode:(ASDisplayNode *)subnode aboveSubnode:(ASDisplayNode *)above -{ - ASDisplayNodeLogEvent(self, @"insertSubnode: %@ abodeSubnode: %@ with automaticallyManagesSubnodes: %@", - subnode, above, self.automaticallyManagesSubnodes ? @"YES" : @"NO"); - [self _insertSubnode:subnode aboveSubnode:above]; -} - -- (void)_insertSubnode:(ASDisplayNode *)subnode aboveSubnode:(ASDisplayNode *)above -{ - ASDisplayNodeAssertThreadAffinity(self); - ASAssertUnlocked(__instanceLock__); - - if (subnode == nil) { - ASDisplayNodeFailAssert(@"Cannot insert a nil subnode"); - return; - } - - if (above.supernode != self) { - ASDisplayNodeFailAssert(@"Node to insert above must be a subnode"); - return; - } - - NSInteger aboveSubnodeIndex; - NSInteger aboveSublayerIndex = NSNotFound; - { - ASDN::MutexLocker l(__instanceLock__); - ASDisplayNodeAssert(_subnodes, @"You should have subnodes if you have a subnode"); - - aboveSubnodeIndex = [_subnodes indexOfObjectIdenticalTo:above]; - - // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the - // hierarchy and none of this could possibly work. - if (subtreeIsRasterized(self) == NO) { - if (_layer) { - aboveSublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:above.layer]; - ASDisplayNodeAssert(aboveSublayerIndex != NSNotFound, @"Somehow above's supernode is self, yet we could not find it in our layers to replace"); - if (aboveSublayerIndex == NSNotFound) - return; - } - - ASDisplayNodeAssert(aboveSubnodeIndex != NSNotFound, @"Couldn't find above in subnodes"); - - // If the subnode is already in the subnodes array / sublayers and it's before the below node, removing it to - // insert it will mess up our calculation - if (subnode.supernode == self) { - NSInteger currentIndexInSubnodes = [_subnodes indexOfObjectIdenticalTo:subnode]; - if (currentIndexInSubnodes <= aboveSubnodeIndex) { - aboveSubnodeIndex--; - } - if (_layer) { - NSInteger currentIndexInSublayers = [_layer.sublayers indexOfObjectIdenticalTo:subnode.layer]; - if (currentIndexInSublayers <= aboveSublayerIndex) { - aboveSublayerIndex--; - } - } - } - } - } - - [self _insertSubnode:subnode atSubnodeIndex:incrementIfFound(aboveSubnodeIndex) sublayerIndex:incrementIfFound(aboveSublayerIndex) andRemoveSubnode:nil]; -} - -- (void)insertSubnode:(ASDisplayNode *)subnode atIndex:(NSInteger)idx -{ - ASDisplayNodeLogEvent(self, @"insertSubnode: %@ atIndex: %td with automaticallyManagesSubnodes: %@", - subnode, idx, self.automaticallyManagesSubnodes ? @"YES" : @"NO"); - [self _insertSubnode:subnode atIndex:idx]; -} - -- (void)_insertSubnode:(ASDisplayNode *)subnode atIndex:(NSInteger)idx -{ - ASDisplayNodeAssertThreadAffinity(self); - ASAssertUnlocked(__instanceLock__); - - if (subnode == nil) { - ASDisplayNodeFailAssert(@"Cannot insert a nil subnode"); - return; - } - - NSInteger sublayerIndex = NSNotFound; - { - ASDN::MutexLocker l(__instanceLock__); - - if (idx > _subnodes.count || idx < 0) { - ASDisplayNodeFailAssert(@"Cannot insert a subnode at index %ld. Count is %ld", (long)idx, (long)_subnodes.count); - return; - } - - // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the - // hierarchy and none of this could possibly work. - if (subtreeIsRasterized(self) == NO) { - // Account for potentially having other subviews - if (_layer && idx == 0) { - sublayerIndex = 0; - } else if (_layer) { - ASDisplayNode *positionInRelationTo = (_subnodes.count > 0 && idx > 0) ? _subnodes[idx - 1] : nil; - if (positionInRelationTo) { - sublayerIndex = incrementIfFound([_layer.sublayers indexOfObjectIdenticalTo:positionInRelationTo.layer]); - } - } - } - } - - [self _insertSubnode:subnode atSubnodeIndex:idx sublayerIndex:sublayerIndex andRemoveSubnode:nil]; -} - -- (void)_removeSubnode:(ASDisplayNode *)subnode -{ - ASDisplayNodeAssertThreadAffinity(self); - ASAssertUnlocked(__instanceLock__); - - // Don't call self.supernode here because that will retain/autorelease the supernode. This method -_removeSupernode: is often called while tearing down a node hierarchy, and the supernode in question might be in the middle of its -dealloc. The supernode is never messaged, only compared by value, so this is safe. - // The particular issue that triggers this edge case is when a node calls -removeFromSupernode on a subnode from within its own -dealloc method. - if (!subnode || subnode.supernode != self) { - return; - } - - __instanceLock__.lock(); - [_subnodes removeObjectIdenticalTo:subnode]; - _cachedSubnodes = nil; - __instanceLock__.unlock(); - - [subnode _setSupernode:nil]; -} - -- (void)removeFromSupernode -{ - ASDisplayNodeLogEvent(self, @"removeFromSupernode with automaticallyManagesSubnodes: %@", - self.automaticallyManagesSubnodes ? @"YES" : @"NO"); - [self _removeFromSupernode]; -} - -- (void)_removeFromSupernode -{ - ASDisplayNodeAssertThreadAffinity(self); - ASAssertUnlocked(__instanceLock__); - - __instanceLock__.lock(); - __weak ASDisplayNode *supernode = _supernode; - __weak UIView *view = _view; - __weak CALayer *layer = _layer; - __instanceLock__.unlock(); - - [self _removeFromSupernode:supernode view:view layer:layer]; -} - -- (void)_removeFromSupernodeIfEqualTo:(ASDisplayNode *)supernode -{ - ASDisplayNodeAssertThreadAffinity(self); - ASAssertUnlocked(__instanceLock__); - - __instanceLock__.lock(); - - // Only remove if supernode is still the expected supernode - if (!ASObjectIsEqual(_supernode, supernode)) { - __instanceLock__.unlock(); - return; - } - - __weak UIView *view = _view; - __weak CALayer *layer = _layer; - __instanceLock__.unlock(); - - [self _removeFromSupernode:supernode view:view layer:layer]; -} - -- (void)_removeFromSupernode:(ASDisplayNode *)supernode view:(UIView *)view layer:(CALayer *)layer -{ - // Note: we continue even if supernode is nil to ensure view/layer are removed from hierarchy. - - if (supernode != nil) { - as_log_verbose(ASNodeLog(), "Remove %@ from supernode %@", self, supernode); - } - - // Clear supernode's reference to us before removing the view from the hierarchy, as _ASDisplayView - // will trigger us to clear our _supernode pointer in willMoveToSuperview:nil. - // This may result in removing the last strong reference, triggering deallocation after this method. - [supernode _removeSubnode:self]; - - if (view != nil) { - [view removeFromSuperview]; - } else if (layer != nil) { - [layer removeFromSuperlayer]; - } -} - -#pragma mark - Visibility API - -- (BOOL)__visibilityNotificationsDisabled -{ - // Currently, this method is only used by the testing infrastructure to verify this internal feature. - ASDN::MutexLocker l(__instanceLock__); - return _flags.visibilityNotificationsDisabled > 0; -} - -- (BOOL)__selfOrParentHasVisibilityNotificationsDisabled -{ - ASDN::MutexLocker l(__instanceLock__); - return (_hierarchyState & ASHierarchyStateTransitioningSupernodes); -} - -- (void)__incrementVisibilityNotificationsDisabled -{ - __instanceLock__.lock(); - const size_t maxVisibilityIncrement = (1ULL< 0, @"Can't decrement past 0"); - if (_flags.visibilityNotificationsDisabled > 0) { - _flags.visibilityNotificationsDisabled--; - } - BOOL visibilityNotificationsDisabled = (_flags.visibilityNotificationsDisabled == 0); - __instanceLock__.unlock(); - - if (visibilityNotificationsDisabled) { - // Must have just transitioned from 1 to 0. Notify all subnodes that we are no longer in a disabled state. - // FIXME: This system should be revisited when refactoring and consolidating the implementation of the - // addSubnode: and insertSubnode:... methods. As implemented, though logically irrelevant for expected use cases, - // multiple nodes in the subtree below may have a non-zero visibilityNotification count and still have - // the ASHierarchyState bit cleared (the only value checked when reading this state). - [self exitHierarchyState:ASHierarchyStateTransitioningSupernodes]; - } -} - -#pragma mark - Placeholder - -- (void)_locked_layoutPlaceholderIfNecessary -{ - ASAssertLocked(__instanceLock__); - if ([self _locked_shouldHavePlaceholderLayer]) { - [self _locked_setupPlaceholderLayerIfNeeded]; - } - // Update the placeholderLayer size in case the node size has changed since the placeholder was added. - _placeholderLayer.frame = self.threadSafeBounds; -} - -- (BOOL)_locked_shouldHavePlaceholderLayer -{ - ASAssertLocked(__instanceLock__); - return (_placeholderEnabled && [self _implementsDisplay]); -} - -- (void)_locked_setupPlaceholderLayerIfNeeded -{ - ASDisplayNodeAssertMainThread(); - ASAssertLocked(__instanceLock__); - - if (!_placeholderLayer) { - _placeholderLayer = [CALayer layer]; - // do not set to CGFLOAT_MAX in the case that something needs to be overtop the placeholder - _placeholderLayer.zPosition = 9999.0; - } - - if (_placeholderLayer.contents == nil) { - if (!_placeholderImage) { - _placeholderImage = [self placeholderImage]; - } - if (_placeholderImage) { - BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(_placeholderImage.capInsets, UIEdgeInsetsZero); - if (stretchable) { - ASDisplayNodeSetResizableContents(_placeholderLayer, _placeholderImage); - } else { - _placeholderLayer.contentsScale = self.contentsScale; - _placeholderLayer.contents = (id)_placeholderImage.CGImage; - } - } - } -} - -- (UIImage *)placeholderImage -{ - // Subclass hook - return nil; -} - -- (BOOL)placeholderShouldPersist -{ - // Subclass hook - return NO; -} - -#pragma mark - Hierarchy State - -- (BOOL)isInHierarchy -{ - ASDN::MutexLocker l(__instanceLock__); - return _flags.isInHierarchy; -} - -- (void)__enterHierarchy -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"Should not cause recursive __enterHierarchy"); - ASAssertUnlocked(__instanceLock__); - ASDisplayNodeLogEvent(self, @"enterHierarchy"); - - // Profiling has shown that locking this method is beneficial, so each of the property accesses don't have to lock and unlock. - __instanceLock__.lock(); - - if (!_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { - _flags.isEnteringHierarchy = YES; - _flags.isInHierarchy = YES; - - // Don't call -willEnterHierarchy while holding __instanceLock__. - // This method and subsequent ones (i.e -interfaceState and didEnter(.*)State) - // don't expect that they are called while the lock is being held. - // More importantly, didEnter(.*)State methods are meant to be overriden by clients. - // And so they can potentially walk up the node tree and cause deadlocks, or do expensive tasks and cause the lock to be held for too long. - __instanceLock__.unlock(); - [self willEnterHierarchy]; - for (ASDisplayNode *subnode in self.subnodes) { - [subnode __enterHierarchy]; - } - __instanceLock__.lock(); - - _flags.isEnteringHierarchy = NO; - - // If we don't have contents finished drawing by the time we are on screen, immediately add the placeholder (if it is enabled and we do have something to draw). - if (self.contents == nil && [self _implementsDisplay]) { - CALayer *layer = self.layer; - [layer setNeedsDisplay]; - - if ([self _locked_shouldHavePlaceholderLayer]) { - [CATransaction begin]; - [CATransaction setDisableActions:YES]; - [self _locked_setupPlaceholderLayerIfNeeded]; - _placeholderLayer.opacity = 1.0; - [CATransaction commit]; - [layer addSublayer:_placeholderLayer]; - } - } - } - - __instanceLock__.unlock(); - - [self didEnterHierarchy]; -} - -- (void)__exitHierarchy -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"Should not cause recursive __exitHierarchy"); - ASAssertUnlocked(__instanceLock__); - ASDisplayNodeLogEvent(self, @"exitHierarchy"); - - // Profiling has shown that locking this method is beneficial, so each of the property accesses don't have to lock and unlock. - __instanceLock__.lock(); - - if (_flags.isInHierarchy && !_flags.visibilityNotificationsDisabled && ![self __selfOrParentHasVisibilityNotificationsDisabled]) { - _flags.isExitingHierarchy = YES; - _flags.isInHierarchy = NO; - - // Don't call -didExitHierarchy while holding __instanceLock__. - // This method and subsequent ones (i.e -interfaceState and didExit(.*)State) - // don't expect that they are called while the lock is being held. - // More importantly, didExit(.*)State methods are meant to be overriden by clients. - // And so they can potentially walk up the node tree and cause deadlocks, or do expensive tasks and cause the lock to be held for too long. - __instanceLock__.unlock(); - [self didExitHierarchy]; - for (ASDisplayNode *subnode in self.subnodes) { - [subnode __exitHierarchy]; - } - __instanceLock__.lock(); - - _flags.isExitingHierarchy = NO; - } - - __instanceLock__.unlock(); -} - -- (void)enterHierarchyState:(ASHierarchyState)hierarchyState -{ - if (hierarchyState == ASHierarchyStateNormal) { - return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. - } - - ASDisplayNodePerformBlockOnEveryNode(nil, self, NO, ^(ASDisplayNode *node) { - node.hierarchyState |= hierarchyState; - }); -} - -- (void)exitHierarchyState:(ASHierarchyState)hierarchyState -{ - if (hierarchyState == ASHierarchyStateNormal) { - return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. - } - ASDisplayNodePerformBlockOnEveryNode(nil, self, NO, ^(ASDisplayNode *node) { - node.hierarchyState &= (~hierarchyState); - }); -} - -- (ASHierarchyState)hierarchyState -{ - ASDN::MutexLocker l(__instanceLock__); - return _hierarchyState; -} - -- (void)setHierarchyState:(ASHierarchyState)newState -{ - ASHierarchyState oldState = ASHierarchyStateNormal; - { - ASDN::MutexLocker l(__instanceLock__); - if (_hierarchyState == newState) { - return; - } - oldState = _hierarchyState; - _hierarchyState = newState; - } - - // Entered rasterization state. - if (newState & ASHierarchyStateRasterized) { - ASDisplayNodeAssert(checkFlag(Synchronous) == NO, @"Node created using -initWithViewBlock:/-initWithLayerBlock: cannot be added to subtree of node with subtree rasterization enabled. Node: %@", self); - } - - // Entered or exited range managed state. - if ((newState & ASHierarchyStateRangeManaged) != (oldState & ASHierarchyStateRangeManaged)) { - if (newState & ASHierarchyStateRangeManaged) { - [self enterInterfaceState:self.supernode.pendingInterfaceState]; - } else { - // The case of exiting a range-managed state should be fairly rare. Adding or removing the node - // to a view hierarchy will cause its interfaceState to be either fully set or unset (all fields), - // but because we might be about to be added to a view hierarchy, exiting the interface state now - // would cause inefficient churn. The tradeoff is that we may not clear contents / fetched data - // for nodes that are removed from a managed state and then retained but not used (bad idea anyway!) - } - } - - if ((newState & ASHierarchyStateLayoutPending) != (oldState & ASHierarchyStateLayoutPending)) { - if (newState & ASHierarchyStateLayoutPending) { - // Entering layout pending state - } else { - // Leaving layout pending state, reset related properties - ASDN::MutexLocker l(__instanceLock__); - _pendingTransitionID = ASLayoutElementContextInvalidTransitionID; - _pendingLayoutTransition = nil; - } - } - - ASDisplayNodeLogEvent(self, @"setHierarchyState: %@", NSStringFromASHierarchyStateChange(oldState, newState)); - as_log_verbose(ASNodeLog(), "%s%@ %@", sel_getName(_cmd), NSStringFromASHierarchyStateChange(oldState, newState), self); -} - -- (void)willEnterHierarchy -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(_flags.isEnteringHierarchy, @"You should never call -willEnterHierarchy directly. Appearance is automatically managed by ASDisplayNode"); - ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - ASAssertUnlocked(__instanceLock__); - - if (![self supportsRangeManagedInterfaceState]) { - self.interfaceState = ASInterfaceStateInHierarchy; - } else if (ASCATransactionQueue.sharedQueue.isEnabled) { - __instanceLock__.lock(); - ASInterfaceState state = _preExitingInterfaceState; - _preExitingInterfaceState = ASInterfaceStateNone; - __instanceLock__.unlock(); - // Layer thrash happened, revert to before exiting. - if (state != ASInterfaceStateNone) { - self.interfaceState = state; - } - } -} - -- (void)didEnterHierarchy { - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"You should never call -didEnterHierarchy directly. Appearance is automatically managed by ASDisplayNode"); - ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - ASDisplayNodeAssert(_flags.isInHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - ASAssertUnlocked(__instanceLock__); -} - -- (void)didExitHierarchy -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(_flags.isExitingHierarchy, @"You should never call -didExitHierarchy directly. Appearance is automatically managed by ASDisplayNode"); - ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - ASAssertUnlocked(__instanceLock__); - - // This case is important when tearing down hierarchies. We must deliver a visibileStateDidChange:NO callback, as part our API guarantee that this method can be used for - // things like data analytics about user content viewing. We cannot call the method in the dealloc as any incidental retain operations in client code would fail. - // Additionally, it may be that a Standard UIView which is containing us is moving between hierarchies, and we should not send the call if we will be re-added in the - // same runloop. Strategy: strong reference (might be the last!), wait one runloop, and confirm we are still outside the hierarchy (both layer-backed and view-backed). - // TODO: This approach could be optimized by only performing the dispatch for root elements + recursively apply the interface state change. This would require a closer - // integration with _ASDisplayLayer to ensure that the superlayer pointer has been cleared by this stage (to check if we are root or not), or a different delegate call. - -#if !ENABLE_NEW_EXIT_HIERARCHY_BEHAVIOR - if (![self supportsRangeManagedInterfaceState]) { - self.interfaceState = ASInterfaceStateNone; - return; - } -#endif - if (ASInterfaceStateIncludesVisible(self.pendingInterfaceState)) { - void(^exitVisibleInterfaceState)(void) = ^{ - // This block intentionally retains self. - __instanceLock__.lock(); - unsigned isStillInHierarchy = _flags.isInHierarchy; - BOOL isVisible = ASInterfaceStateIncludesVisible(_pendingInterfaceState); - ASInterfaceState newState = (_pendingInterfaceState & ~ASInterfaceStateVisible); - // layer may be thrashed, we need to remember the state so we can reset if it enters in same runloop later. - _preExitingInterfaceState = _pendingInterfaceState; - __instanceLock__.unlock(); - if (!isStillInHierarchy && isVisible) { -#if ENABLE_NEW_EXIT_HIERARCHY_BEHAVIOR - if (![self supportsRangeManagedInterfaceState]) { - newState = ASInterfaceStateNone; - } -#endif - self.interfaceState = newState; - } - }; - - if (!ASCATransactionQueue.sharedQueue.enabled) { - dispatch_async(dispatch_get_main_queue(), exitVisibleInterfaceState); - } else { - exitVisibleInterfaceState(); - } - } -} - -#pragma mark - Interface State - -/** - * We currently only set interface state on nodes in table/collection views. For other nodes, if they are - * in the hierarchy we enable all ASInterfaceState types with `ASInterfaceStateInHierarchy`, otherwise `None`. - */ -- (BOOL)supportsRangeManagedInterfaceState -{ - ASDN::MutexLocker l(__instanceLock__); - return ASHierarchyStateIncludesRangeManaged(_hierarchyState); -} - -- (void)enterInterfaceState:(ASInterfaceState)interfaceState -{ - if (interfaceState == ASInterfaceStateNone) { - return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. - } - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { - node.interfaceState |= interfaceState; - }); -} - -- (void)exitInterfaceState:(ASInterfaceState)interfaceState -{ - if (interfaceState == ASInterfaceStateNone) { - return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. - } - ASDisplayNodeLogEvent(self, @"%s %@", sel_getName(_cmd), NSStringFromASInterfaceState(interfaceState)); - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { - node.interfaceState &= (~interfaceState); - }); -} - -- (void)recursivelySetInterfaceState:(ASInterfaceState)newInterfaceState -{ - as_activity_create_for_scope("Recursively set interface state"); - - // Instead of each node in the recursion assuming it needs to schedule itself for display, - // setInterfaceState: skips this when handling range-managed nodes (our whole subtree has this set). - // If our range manager intends for us to be displayed right now, and didn't before, get started! - BOOL shouldScheduleDisplay = [self supportsRangeManagedInterfaceState] && [self shouldScheduleDisplayWithNewInterfaceState:newInterfaceState]; - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) { - node.interfaceState = newInterfaceState; - }); - if (shouldScheduleDisplay) { - [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; - } -} - -- (ASInterfaceState)interfaceState -{ - ASDN::MutexLocker l(__instanceLock__); - return _interfaceState; -} - -- (void)setInterfaceState:(ASInterfaceState)newState -{ - if (!ASCATransactionQueue.sharedQueue.enabled) { - [self applyPendingInterfaceState:newState]; - } else { - ASDN::MutexLocker l(__instanceLock__); - if (_pendingInterfaceState != newState) { - _pendingInterfaceState = newState; - [[ASCATransactionQueue sharedQueue] enqueue:self]; - } - } -} - -- (ASInterfaceState)pendingInterfaceState -{ - ASDN::MutexLocker l(__instanceLock__); - return _pendingInterfaceState; -} - -- (void)applyPendingInterfaceState:(ASInterfaceState)newPendingState -{ - //This method is currently called on the main thread. The assert has been added here because all of the - //did(Enter|Exit)(Display|Visible|Preload)State methods currently guarantee calling on main. - ASDisplayNodeAssertMainThread(); - - // This method manages __instanceLock__ itself, to ensure the lock is not held while didEnter/Exit(.*)State methods are called, thus avoid potential deadlocks - ASAssertUnlocked(__instanceLock__); - - ASInterfaceState oldState = ASInterfaceStateNone; - ASInterfaceState newState = ASInterfaceStateNone; - { - ASDN::MutexLocker l(__instanceLock__); - // newPendingState will not be used when ASCATransactionQueue is enabled - // and use _pendingInterfaceState instead for interfaceState update. - if (!ASCATransactionQueue.sharedQueue.enabled) { - _pendingInterfaceState = newPendingState; - } - oldState = _interfaceState; - newState = _pendingInterfaceState; - if (newState == oldState) { - return; - } - _interfaceState = newState; - _preExitingInterfaceState = ASInterfaceStateNone; - } - - // It should never be possible for a node to be visible but not be allowed / expected to display. - ASDisplayNodeAssertFalse(ASInterfaceStateIncludesVisible(newState) && !ASInterfaceStateIncludesDisplay(newState)); - - // TODO: Trigger asynchronous measurement if it is not already cached or being calculated. - // if ((newState & ASInterfaceStateMeasureLayout) != (oldState & ASInterfaceStateMeasureLayout)) { - // } - - // For the Preload and Display ranges, we don't want to call -clear* if not being managed by a range controller. - // Otherwise we get flashing behavior from normal UIKit manipulations like navigation controller push / pop. - // Still, the interfaceState should be updated to the current state of the node; just don't act on the transition. - - // Entered or exited data loading state. - BOOL nowPreload = ASInterfaceStateIncludesPreload(newState); - BOOL wasPreload = ASInterfaceStateIncludesPreload(oldState); - - if (nowPreload != wasPreload) { - if (nowPreload) { - [self didEnterPreloadState]; - } else { - // We don't want to call -didExitPreloadState on nodes that aren't being managed by a range controller. - // Otherwise we get flashing behavior from normal UIKit manipulations like navigation controller push / pop. - if ([self supportsRangeManagedInterfaceState]) { - [self didExitPreloadState]; - } - } - } - - // Entered or exited contents rendering state. - BOOL nowDisplay = ASInterfaceStateIncludesDisplay(newState); - BOOL wasDisplay = ASInterfaceStateIncludesDisplay(oldState); - - if (nowDisplay != wasDisplay) { - if ([self supportsRangeManagedInterfaceState]) { - if (nowDisplay) { - // Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here. - [self setDisplaySuspended:NO]; - } else { - [self setDisplaySuspended:YES]; - //schedule clear contents on next runloop - dispatch_async(dispatch_get_main_queue(), ^{ - __instanceLock__.lock(); - ASInterfaceState interfaceState = _interfaceState; - __instanceLock__.unlock(); - if (ASInterfaceStateIncludesDisplay(interfaceState) == NO) { - [self clearContents]; - } - }); - } - } else { - // NOTE: This case isn't currently supported as setInterfaceState: isn't exposed externally, and all - // internal use cases are range-managed. When a node is visible, don't mess with display - CA will start it. - if (!ASInterfaceStateIncludesVisible(newState)) { - // Check _implementsDisplay purely for efficiency - it's faster even than calling -asyncLayer. - if ([self _implementsDisplay]) { - if (nowDisplay) { - [ASDisplayNode scheduleNodeForRecursiveDisplay:self]; - } else { - [[self asyncLayer] cancelAsyncDisplay]; - //schedule clear contents on next runloop - dispatch_async(dispatch_get_main_queue(), ^{ - __instanceLock__.lock(); - ASInterfaceState interfaceState = _interfaceState; - __instanceLock__.unlock(); - if (ASInterfaceStateIncludesDisplay(interfaceState) == NO) { - [self clearContents]; - } - }); - } - } - } - } - - if (nowDisplay) { - [self didEnterDisplayState]; - } else { - [self didExitDisplayState]; - } - } - - // Became visible or invisible. When range-managed, this represents literal visibility - at least one pixel - // is onscreen. If not range-managed, we can't guarantee more than the node being present in an onscreen window. - BOOL nowVisible = ASInterfaceStateIncludesVisible(newState); - BOOL wasVisible = ASInterfaceStateIncludesVisible(oldState); - - if (nowVisible != wasVisible) { - if (nowVisible) { - [self didEnterVisibleState]; - } else { - [self didExitVisibleState]; - } - } - - // Log this change, unless it's just the node going from {} -> {Measure} because that change happens - // for all cell nodes and it isn't currently meaningful. - BOOL measureChangeOnly = ((oldState | newState) == ASInterfaceStateMeasureLayout); - if (!measureChangeOnly) { - as_log_verbose(ASNodeLog(), "%s %@ %@", sel_getName(_cmd), NSStringFromASInterfaceStateChange(oldState, newState), self); - } - - ASDisplayNodeLogEvent(self, @"interfaceStateDidChange: %@", NSStringFromASInterfaceStateChange(oldState, newState)); - [self interfaceStateDidChange:newState fromState:oldState]; -} - -- (void)prepareForCATransactionCommit -{ - // Apply _pendingInterfaceState actual _interfaceState, note that ASInterfaceStateNone is not used. - [self applyPendingInterfaceState:ASInterfaceStateNone]; -} - -- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState -{ - // Subclass hook - ASAssertUnlocked(__instanceLock__); - ASDisplayNodeAssertMainThread(); - [self enumerateInterfaceStateDelegates:^(id del) { - [del interfaceStateDidChange:newState fromState:oldState]; - }]; -} - -- (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState -{ - BOOL willDisplay = ASInterfaceStateIncludesDisplay(newInterfaceState); - BOOL nowDisplay = ASInterfaceStateIncludesDisplay(self.interfaceState); - return willDisplay && (willDisplay != nowDisplay); -} - -- (void)addInterfaceStateDelegate:(id )interfaceStateDelegate -{ - ASDN::MutexLocker l(__instanceLock__); - _hasHadInterfaceStateDelegates = YES; - for (int i = 0; i < AS_MAX_INTERFACE_STATE_DELEGATES; i++) { - if (_interfaceStateDelegates[i] == nil) { - _interfaceStateDelegates[i] = interfaceStateDelegate; - return; - } - } - ASDisplayNodeFailAssert(@"Exceeded interface state delegate limit: %d", AS_MAX_INTERFACE_STATE_DELEGATES); -} - -- (void)removeInterfaceStateDelegate:(id )interfaceStateDelegate -{ - ASDN::MutexLocker l(__instanceLock__); - for (int i = 0; i < AS_MAX_INTERFACE_STATE_DELEGATES; i++) { - if (_interfaceStateDelegates[i] == interfaceStateDelegate) { - _interfaceStateDelegates[i] = nil; - break; - } - } -} - -- (BOOL)isVisible -{ - ASDN::MutexLocker l(__instanceLock__); - return ASInterfaceStateIncludesVisible(_interfaceState); -} - -- (void)didEnterVisibleState -{ - // subclass override - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - [self enumerateInterfaceStateDelegates:^(id del) { - [del didEnterVisibleState]; - }]; -#if AS_ENABLE_TIPS - [ASTipsController.shared nodeDidAppear:self]; -#endif -} - -- (void)didExitVisibleState -{ - // subclass override - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - [self enumerateInterfaceStateDelegates:^(id del) { - [del didExitVisibleState]; - }]; -} - -- (BOOL)isInDisplayState -{ - ASDN::MutexLocker l(__instanceLock__); - return ASInterfaceStateIncludesDisplay(_interfaceState); -} - -- (void)didEnterDisplayState -{ - // subclass override - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - [self enumerateInterfaceStateDelegates:^(id del) { - [del didEnterDisplayState]; - }]; -} - -- (void)didExitDisplayState -{ - // subclass override - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - [self enumerateInterfaceStateDelegates:^(id del) { - [del didExitDisplayState]; - }]; -} - -- (BOOL)isInPreloadState -{ - ASDN::MutexLocker l(__instanceLock__); - return ASInterfaceStateIncludesPreload(_interfaceState); -} - -- (void)setNeedsPreload -{ - if (self.isInPreloadState) { - [self recursivelyPreload]; - } -} - -- (void)recursivelyPreload -{ - ASPerformBlockOnMainThread(^{ - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode * _Nonnull node) { - [node didEnterPreloadState]; - }); - }); -} - -- (void)recursivelyClearPreloadedData -{ - ASPerformBlockOnMainThread(^{ - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode * _Nonnull node) { - [node didExitPreloadState]; - }); - }); -} - -- (void)didEnterPreloadState -{ - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - - // If this node has ASM enabled and is not yet visible, force a layout pass to apply its applicable pending layout, if any, - // so that its subnodes are inserted/deleted and start preloading right away. - // - // - If it has an up-to-date layout (and subnodes), calling -layoutIfNeeded will be fast. - // - // - If it doesn't have a calculated or pending layout that fits its current bounds, a measurement pass will occur - // (see -__layout and -_u_measureNodeWithBoundsIfNecessary:). This scenario is uncommon, - // and running a measurement pass here is a fine trade-off because preloading any time after this point would be late. - if (self.automaticallyManagesSubnodes) { - [self layoutIfNeeded]; - } - [self enumerateInterfaceStateDelegates:^(id del) { - [del didEnterPreloadState]; - }]; -} - -- (void)didExitPreloadState -{ - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - [self enumerateInterfaceStateDelegates:^(id del) { - [del didExitPreloadState]; - }]; -} - -- (void)clearContents -{ - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - - ASDN::MutexLocker l(__instanceLock__); - if (_flags.canClearContentsOfLayer) { - // No-op if these haven't been created yet, as that guarantees they don't have contents that needs to be released. - _layer.contents = nil; - } - - _placeholderLayer.contents = nil; - _placeholderImage = nil; -} - -- (void)recursivelyClearContents -{ - ASPerformBlockOnMainThread(^{ - ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode * _Nonnull node) { - [node clearContents]; - }); - }); -} - -- (void)enumerateInterfaceStateDelegates:(void (NS_NOESCAPE ^)(id))block -{ - ASAssertUnlocked(__instanceLock__); - - id dels[AS_MAX_INTERFACE_STATE_DELEGATES]; - int count = 0; - { - ASLockScopeSelf(); - // Fast path for non-delegating nodes. - if (!_hasHadInterfaceStateDelegates) { - return; - } - - for (int i = 0; i < AS_MAX_INTERFACE_STATE_DELEGATES; i++) { - if ((dels[count] = _interfaceStateDelegates[i])) { - count++; - } - } - } - for (int i = 0; i < count; i++) { - block(dels[i]); - } -} - -#pragma mark - Gesture Recognizing - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - // Subclass hook -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -{ - // Subclass hook -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - // Subclass hook -} - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event -{ - // Subclass hook -} - -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer -{ - // This method is only implemented on UIView on iOS 6+. - ASDisplayNodeAssertMainThread(); - - // No locking needed as it's main thread only - UIView *view = _view; - if (view == nil) { - return YES; - } - - // If we reach the base implementation, forward up the view hierarchy. - UIView *superview = view.superview; - return [superview gestureRecognizerShouldBegin:gestureRecognizer]; -} - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - return [_view hitTest:point withEvent:event]; -} - -- (void)setHitTestSlop:(UIEdgeInsets)hitTestSlop -{ - ASDN::MutexLocker l(__instanceLock__); - _hitTestSlop = hitTestSlop; -} - -- (UIEdgeInsets)hitTestSlop -{ - ASDN::MutexLocker l(__instanceLock__); - return _hitTestSlop; -} - -- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - UIEdgeInsets slop = self.hitTestSlop; - if (_view && UIEdgeInsetsEqualToEdgeInsets(slop, UIEdgeInsetsZero)) { - // Safer to use UIView's -pointInside:withEvent: if we can. - return [_view pointInside:point withEvent:event]; - } else { - return CGRectContainsPoint(UIEdgeInsetsInsetRect(self.bounds, slop), point); - } -} - - -#pragma mark - Pending View State - -- (void)_locked_applyPendingStateToViewOrLayer -{ - ASDisplayNodeAssertMainThread(); - ASAssertLocked(__instanceLock__); - ASDisplayNodeAssert(self.nodeLoaded, @"must have a view or layer"); - - TIME_SCOPED(_debugTimeToApplyPendingState); - - // If no view/layer properties were set before the view/layer were created, _pendingViewState will be nil and the default values - // for the view/layer are still valid. - [self _locked_applyPendingViewState]; - - if (_flags.displaySuspended) { - self._locked_asyncLayer.displaySuspended = YES; - } - if (!_flags.displaysAsynchronously) { - self._locked_asyncLayer.displaysAsynchronously = NO; - } -} - -- (void)applyPendingViewState -{ - ASDisplayNodeAssertMainThread(); - ASAssertUnlocked(__instanceLock__); - - ASDN::MutexLocker l(__instanceLock__); - // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout - // but automatic subnode management would require us to modify the node tree - // in the background on a loaded node, which isn't currently supported. - if (_pendingViewState.hasSetNeedsLayout) { - // Need to unlock before calling setNeedsLayout to avoid deadlocks. - // MutexUnlocker will re-lock at the end of scope. - ASDN::MutexUnlocker u(__instanceLock__); - [self __setNeedsLayout]; - } - - [self _locked_applyPendingViewState]; -} - -- (void)_locked_applyPendingViewState -{ - ASDisplayNodeAssertMainThread(); - ASAssertLocked(__instanceLock__); - ASDisplayNodeAssert([self _locked_isNodeLoaded], @"Expected node to be loaded before applying pending state."); - - if (_flags.layerBacked) { - [_pendingViewState applyToLayer:_layer]; - } else { - BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandling(checkFlag(Synchronous), _flags.layerBacked); - [_pendingViewState applyToView:_view withSpecialPropertiesHandling:specialPropertiesHandling]; - } - - // _ASPendingState objects can add up very quickly when adding - // many nodes. This is especially an issue in large collection views - // and table views. This needs to be weighed against the cost of - // reallocing a _ASPendingState. So in range managed nodes we - // delete the pending state, otherwise we just clear it. - if (ASHierarchyStateIncludesRangeManaged(_hierarchyState)) { - _pendingViewState = nil; - } else { - [_pendingViewState clearChanges]; - } -} - -// This method has proved helpful in a few rare scenarios, similar to a category extension on UIView, but assumes knowledge of _ASDisplayView. -// It's considered private API for now and its use should not be encouraged. -- (ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass checkViewHierarchy:(BOOL)checkViewHierarchy -{ - ASDisplayNode *supernode = self.supernode; - while (supernode) { - if ([supernode isKindOfClass:supernodeClass]) - return supernode; - supernode = supernode.supernode; - } - if (!checkViewHierarchy) { - return nil; - } - - UIView *view = self.view.superview; - while (view) { - ASDisplayNode *viewNode = ((_ASDisplayView *)view).asyncdisplaykit_node; - if (viewNode) { - if ([viewNode isKindOfClass:supernodeClass]) - return viewNode; - } - - view = view.superview; - } - - return nil; -} - -#pragma mark - Performance Measurement - -- (void)setMeasurementOptions:(ASDisplayNodePerformanceMeasurementOptions)measurementOptions -{ - ASDN::MutexLocker l(__instanceLock__); - _measurementOptions = measurementOptions; -} - -- (ASDisplayNodePerformanceMeasurementOptions)measurementOptions -{ - ASDN::MutexLocker l(__instanceLock__); - return _measurementOptions; -} - -- (ASDisplayNodePerformanceMeasurements)performanceMeasurements -{ - ASDN::MutexLocker l(__instanceLock__); - ASDisplayNodePerformanceMeasurements measurements = { .layoutSpecNumberOfPasses = -1, .layoutSpecTotalTime = NAN, .layoutComputationNumberOfPasses = -1, .layoutComputationTotalTime = NAN }; - if (_measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec) { - measurements.layoutSpecNumberOfPasses = _layoutSpecNumberOfPasses; - measurements.layoutSpecTotalTime = _layoutSpecTotalTime; - } - if (_measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutComputation) { - measurements.layoutComputationNumberOfPasses = _layoutComputationNumberOfPasses; - measurements.layoutComputationTotalTime = _layoutComputationTotalTime; - } - return measurements; -} - -#pragma mark - Accessibility - -- (void)setIsAccessibilityContainer:(BOOL)isAccessibilityContainer -{ - ASDN::MutexLocker l(__instanceLock__); - _isAccessibilityContainer = isAccessibilityContainer; -} - -- (BOOL)isAccessibilityContainer -{ - ASDN::MutexLocker l(__instanceLock__); - return _isAccessibilityContainer; -} - -#pragma mark - Debugging (Private) - -#if ASEVENTLOG_ENABLE -- (ASEventLog *)eventLog -{ - return _eventLog; -} -#endif - -- (NSMutableArray *)propertiesForDescription -{ - NSMutableArray *result = [NSMutableArray array]; - ASPushMainThreadAssertionsDisabled(); - - NSString *debugName = self.debugName; - if (debugName.length > 0) { - [result addObject:@{ (id)kCFNull : ASStringWithQuotesIfMultiword(debugName) }]; - } - - NSString *axId = self.accessibilityIdentifier; - if (axId.length > 0) { - [result addObject:@{ (id)kCFNull : ASStringWithQuotesIfMultiword(axId) }]; - } - - ASPopMainThreadAssertionsDisabled(); - return result; -} - -- (NSMutableArray *)propertiesForDebugDescription -{ - NSMutableArray *result = [NSMutableArray array]; - - if (self.debugName.length > 0) { - [result addObject:@{ @"debugName" : ASStringWithQuotesIfMultiword(self.debugName)}]; - } - if (self.accessibilityIdentifier.length > 0) { - [result addObject:@{ @"axId": ASStringWithQuotesIfMultiword(self.accessibilityIdentifier) }]; - } - - CGRect windowFrame = [self _frameInWindow]; - if (CGRectIsNull(windowFrame) == NO) { - [result addObject:@{ @"frameInWindow" : [NSValue valueWithCGRect:windowFrame] }]; - } - - // Attempt to find view controller. - // Note that the convenience method asdk_associatedViewController has an assertion - // that it's run on main. Since this is a debug method, let's bypass the assertion - // and run up the chain ourselves. - if (_view != nil) { - for (UIResponder *responder in [_view asdk_responderChainEnumerator]) { - UIViewController *vc = ASDynamicCast(responder, UIViewController); - if (vc) { - [result addObject:@{ @"viewController" : ASObjectDescriptionMakeTiny(vc) }]; - break; - } - } - } - - if (_view != nil) { - [result addObject:@{ @"alpha" : @(_view.alpha) }]; - [result addObject:@{ @"frame" : [NSValue valueWithCGRect:_view.frame] }]; - } else if (_layer != nil) { - [result addObject:@{ @"alpha" : @(_layer.opacity) }]; - [result addObject:@{ @"frame" : [NSValue valueWithCGRect:_layer.frame] }]; - } else if (_pendingViewState != nil) { - [result addObject:@{ @"alpha" : @(_pendingViewState.alpha) }]; - [result addObject:@{ @"frame" : [NSValue valueWithCGRect:_pendingViewState.frame] }]; - } -#ifndef MINIMAL_ASDK - // Check supernode so that if we are a cell node we don't find self. - ASCellNode *cellNode = [self supernodeOfClass:[ASCellNode class] includingSelf:NO]; - if (cellNode != nil) { - [result addObject:@{ @"cellNode" : ASObjectDescriptionMakeTiny(cellNode) }]; - } -#endif - - [result addObject:@{ @"interfaceState" : NSStringFromASInterfaceState(self.interfaceState)} ]; - - if (_view != nil) { - [result addObject:@{ @"view" : ASObjectDescriptionMakeTiny(_view) }]; - } else if (_layer != nil) { - [result addObject:@{ @"layer" : ASObjectDescriptionMakeTiny(_layer) }]; - } else if (_viewClass != nil) { - [result addObject:@{ @"viewClass" : _viewClass }]; - } else if (_layerClass != nil) { - [result addObject:@{ @"layerClass" : _layerClass }]; - } else if (_viewBlock != nil) { - [result addObject:@{ @"viewBlock" : _viewBlock }]; - } else if (_layerBlock != nil) { - [result addObject:@{ @"layerBlock" : _layerBlock }]; - } - -#if TIME_DISPLAYNODE_OPS - NSString *creationTypeString = [NSString stringWithFormat:@"cr8:%.2lfms dl:%.2lfms ap:%.2lfms ad:%.2lfms", 1000 * _debugTimeToCreateView, 1000 * _debugTimeForDidLoad, 1000 * _debugTimeToApplyPendingState, 1000 * _debugTimeToAddSubnodeViews]; - [result addObject:@{ @"creationTypeString" : creationTypeString }]; -#endif - - return result; -} - -- (NSString *)description -{ - return ASObjectDescriptionMake(self, [self propertiesForDescription]); -} - -- (NSString *)debugDescription -{ - ASPushMainThreadAssertionsDisabled(); - let result = ASObjectDescriptionMake(self, [self propertiesForDebugDescription]); - ASPopMainThreadAssertionsDisabled(); - return result; -} - -// This should only be called for debugging. It's not thread safe and it doesn't assert. -// NOTE: Returns CGRectNull if the node isn't in a hierarchy. -- (CGRect)_frameInWindow -{ - if (self.isNodeLoaded == NO || self.isInHierarchy == NO) { - return CGRectNull; - } - - if (self.layerBacked) { - CALayer *rootLayer = _layer; - CALayer *nextLayer = nil; - while ((nextLayer = rootLayer.superlayer) != nil) { - rootLayer = nextLayer; - } - - return [_layer convertRect:self.threadSafeBounds toLayer:rootLayer]; - } else { - return [_view convertRect:self.threadSafeBounds toView:nil]; - } -} - -#pragma mark - Trait Collection Hooks - -- (void)asyncTraitCollectionDidChange -{ - // Subclass override -} -@end - -#pragma mark - ASDisplayNode (Debugging) - -@implementation ASDisplayNode (Debugging) - -+ (void)setShouldStoreUnflattenedLayouts:(BOOL)shouldStore -{ - storesUnflattenedLayouts.store(shouldStore); -} - -+ (BOOL)shouldStoreUnflattenedLayouts -{ - return storesUnflattenedLayouts.load(); -} - -- (ASLayout *)unflattenedCalculatedLayout -{ - ASDN::MutexLocker l(__instanceLock__); - return _unflattenedLayout; -} - -- (NSString *)displayNodeRecursiveDescription -{ - return [self _recursiveDescriptionHelperWithIndent:@""]; -} - -- (NSString *)_recursiveDescriptionHelperWithIndent:(NSString *)indent -{ - NSMutableString *subtree = [[[indent stringByAppendingString:self.debugDescription] stringByAppendingString:@"\n"] mutableCopy]; - for (ASDisplayNode *n in self.subnodes) { - [subtree appendString:[n _recursiveDescriptionHelperWithIndent:[indent stringByAppendingString:@" | "]]]; - } - return subtree; -} - -- (NSString *)detailedLayoutDescription -{ - ASPushMainThreadAssertionsDisabled(); - ASDN::MutexLocker l(__instanceLock__); - let props = [[NSMutableArray alloc] init]; - - [props addObject:@{ @"layoutVersion": @(_layoutVersion.load()) }]; - [props addObject:@{ @"bounds": [NSValue valueWithCGRect:self.bounds] }]; - - if (_calculatedDisplayNodeLayout.layout) { - [props addObject:@{ @"calculatedLayout": _calculatedDisplayNodeLayout.layout }]; - [props addObject:@{ @"calculatedVersion": @(_calculatedDisplayNodeLayout.version) }]; - [props addObject:@{ @"calculatedConstrainedSize" : NSStringFromASSizeRange(_calculatedDisplayNodeLayout.constrainedSize) }]; - if (_calculatedDisplayNodeLayout.requestedLayoutFromAbove) { - [props addObject:@{ @"calculatedRequestedLayoutFromAbove": @"YES" }]; - } - } - if (_pendingDisplayNodeLayout.layout) { - [props addObject:@{ @"pendingLayout": _pendingDisplayNodeLayout.layout }]; - [props addObject:@{ @"pendingVersion": @(_pendingDisplayNodeLayout.version) }]; - [props addObject:@{ @"pendingConstrainedSize" : NSStringFromASSizeRange(_pendingDisplayNodeLayout.constrainedSize) }]; - if (_pendingDisplayNodeLayout.requestedLayoutFromAbove) { - [props addObject:@{ @"pendingRequestedLayoutFromAbove": (id)kCFNull }]; - } - } - - ASPopMainThreadAssertionsDisabled(); - return ASObjectDescriptionMake(self, props); -} - -@end - -#pragma mark - ASDisplayNode UIKit / CA Categories - -// We use associated objects as a last resort if our view is not a _ASDisplayView ie it doesn't have the _node ivar to write to - -static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; - -@implementation UIView (ASDisplayNodeInternal) - -- (void)setAsyncdisplaykit_node:(ASDisplayNode *)node -{ - ASWeakProxy *weakProxy = [ASWeakProxy weakProxyWithTarget:node]; - objc_setAssociatedObject(self, ASDisplayNodeAssociatedNodeKey, weakProxy, OBJC_ASSOCIATION_RETAIN); // Weak reference to avoid cycle, since the node retains the view. -} - -- (ASDisplayNode *)asyncdisplaykit_node -{ - ASWeakProxy *weakProxy = objc_getAssociatedObject(self, ASDisplayNodeAssociatedNodeKey); - return weakProxy.target; -} - -@end - -@implementation CALayer (ASDisplayNodeInternal) - -- (void)setAsyncdisplaykit_node:(ASDisplayNode *)node -{ - ASWeakProxy *weakProxy = [ASWeakProxy weakProxyWithTarget:node]; - objc_setAssociatedObject(self, ASDisplayNodeAssociatedNodeKey, weakProxy, OBJC_ASSOCIATION_RETAIN); // Weak reference to avoid cycle, since the node retains the layer. -} - -- (ASDisplayNode *)asyncdisplaykit_node -{ - ASWeakProxy *weakProxy = objc_getAssociatedObject(self, ASDisplayNodeAssociatedNodeKey); - return weakProxy.target; -} - -@end - -@implementation UIView (AsyncDisplayKit) - -- (void)addSubnode:(ASDisplayNode *)subnode -{ - if (subnode.layerBacked) { - // Call -addSubnode: so that we use the asyncdisplaykit_node path if possible. - [self.layer addSubnode:subnode]; - } else { - ASDisplayNode *selfNode = self.asyncdisplaykit_node; - if (selfNode) { - [selfNode addSubnode:subnode]; - } else { - if (subnode.supernode) { - [subnode removeFromSupernode]; - } - [self addSubview:subnode.view]; - } - } -} - -@end - -@implementation CALayer (AsyncDisplayKit) - -- (void)addSubnode:(ASDisplayNode *)subnode -{ - ASDisplayNode *selfNode = self.asyncdisplaykit_node; - if (selfNode) { - [selfNode addSubnode:subnode]; - } else { - if (subnode.supernode) { - [subnode removeFromSupernode]; - } - [self addSublayer:subnode.layer]; - } -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNodeCornerLayerDelegate.h b/submodules/AsyncDisplayKit/Source/ASDisplayNodeCornerLayerDelegate.h deleted file mode 100644 index cb95e9fbf6..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNodeCornerLayerDelegate.h +++ /dev/null @@ -1,13 +0,0 @@ -// -// ASDisplayNodeCornerLayerDelegate.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 - -@interface ASDisplayNodeCornerLayerDelegate : NSObject -@end diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNodeCornerLayerDelegate.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNodeCornerLayerDelegate.mm deleted file mode 100644 index 42838baba1..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNodeCornerLayerDelegate.mm +++ /dev/null @@ -1,19 +0,0 @@ -// -// ASDisplayNodeCornerLayerDelegate.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 "ASDisplayNodeCornerLayerDelegate.h" - -@implementation ASDisplayNodeCornerLayerDelegate - -- (id)actionForLayer:(CALayer *)layer forKey:(NSString *)event -{ - return (id)kCFNull; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNodeTipState.h b/submodules/AsyncDisplayKit/Source/ASDisplayNodeTipState.h deleted file mode 100644 index 1e1ef20726..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNodeTipState.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// ASDisplayNodeTipState.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 -#import - -@class ASDisplayNode, ASTipNode; - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASDisplayNodeTipState : NSObject - -- (instancetype)initWithNode:(ASDisplayNode *)node NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; - -/// Unsafe because once the node is deallocated, we will not be able to access the tip state. -@property (nonatomic, unsafe_unretained, readonly) ASDisplayNode *node; - -/// Main-thread-only. -@property (nonatomic, nullable) ASTipNode *tipNode; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNodeTipState.mm b/submodules/AsyncDisplayKit/Source/ASDisplayNodeTipState.mm deleted file mode 100644 index b5a4231998..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNodeTipState.mm +++ /dev/null @@ -1,25 +0,0 @@ -// -// ASDisplayNodeTipState.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 "ASDisplayNodeTipState.h" - -@interface ASDisplayNodeTipState () -@end - -@implementation ASDisplayNodeTipState - -- (instancetype)initWithNode:(ASDisplayNode *)node -{ - if (self = [super init]) { - _node = node; - } - return self; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASIGListAdapterBasedDataSource.h b/submodules/AsyncDisplayKit/Source/ASIGListAdapterBasedDataSource.h deleted file mode 100644 index 5be2185f2f..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASIGListAdapterBasedDataSource.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// ASIGListAdapterBasedDataSource.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 - -#if AS_IG_LIST_KIT - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASIGListAdapterBasedDataSource : NSObject - -- (instancetype)initWithListAdapter:(IGListAdapter *)listAdapter collectionDelegate:(nullable id)collectionDelegate; - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASIGListAdapterBasedDataSource.mm b/submodules/AsyncDisplayKit/Source/ASIGListAdapterBasedDataSource.mm deleted file mode 100644 index 3cf77afc0a..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASIGListAdapterBasedDataSource.mm +++ /dev/null @@ -1,364 +0,0 @@ -// -// ASIGListAdapterBasedDataSource.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 - -#if AS_IG_LIST_KIT - -#import "ASIGListAdapterBasedDataSource.h" -#import -#import - -typedef IGListSectionController ASIGSectionController; - -/// The optional methods that a class implements from ASSectionController. -/// Note: Bitfields are not supported by NSValue so we can't use them. -typedef struct { - BOOL sizeRangeForItem; - BOOL shouldBatchFetch; - BOOL beginBatchFetchWithContext; -} ASSectionControllerOverrides; - -/// The optional methods that a class implements from ASSupplementaryNodeSource. -/// Note: Bitfields are not supported by NSValue so we can't use them. -typedef struct { - BOOL sizeRangeForSupplementary; -} ASSupplementarySourceOverrides; - -@protocol ASIGSupplementaryNodeSource -@end - -@interface ASIGListAdapterBasedDataSource () -@property (nonatomic, weak, readonly) IGListAdapter *listAdapter; -@property (nonatomic, readonly) id delegate; -@property (nonatomic, readonly) id dataSource; -@property (nonatomic, weak, readonly) id collectionDelegate; - -/** - * The section controller that we will forward beginBatchFetchWithContext: to. - * Since shouldBatchFetch: is called on main, we capture the last section controller in there, - * and then we use it and clear it in beginBatchFetchWithContext: (on default queue). - * - * It is safe to use it without a lock in this limited way, since those two methods will - * never execute in parallel. - */ -@property (nonatomic, weak) ASIGSectionController *sectionControllerForBatchFetching; -@end - -@implementation ASIGListAdapterBasedDataSource - -- (instancetype)initWithListAdapter:(IGListAdapter *)listAdapter collectionDelegate:(nullable id)collectionDelegate -{ - if (self = [super init]) { -#if IG_LIST_COLLECTION_VIEW - [ASIGListAdapterBasedDataSource setASCollectionViewSuperclass]; -#endif - [ASIGListAdapterBasedDataSource configureUpdater:listAdapter.updater]; - - ASDisplayNodeAssert([listAdapter conformsToProtocol:@protocol(UICollectionViewDataSource)], @"Expected IGListAdapter to conform to UICollectionViewDataSource."); - ASDisplayNodeAssert([listAdapter conformsToProtocol:@protocol(UICollectionViewDelegateFlowLayout)], @"Expected IGListAdapter to conform to UICollectionViewDelegateFlowLayout."); - _listAdapter = listAdapter; - _collectionDelegate = collectionDelegate; - } - return self; -} - -- (id)dataSource -{ - return (id)_listAdapter; -} - -- (id)delegate -{ - return (id)_listAdapter; -} - -#pragma mark - ASCollectionDelegate - -- (void)collectionNode:(ASCollectionNode *)collectionNode didSelectItemAtIndexPath:(NSIndexPath *)indexPath -{ - [self.delegate collectionView:collectionNode.view didSelectItemAtIndexPath:indexPath]; -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - [self.delegate scrollViewDidScroll:scrollView]; -} - -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView -{ - [self.delegate scrollViewWillBeginDragging:scrollView]; -} - -- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset -{ - // IGListAdapter doesn't implement scrollViewWillEndDragging yet (pending pull request), so we need this check for now. Doesn't hurt to have it anyways :) - if ([self.delegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { - [self.delegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; - } -} - -- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate -{ - [self.delegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; -} - -- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView -{ - [self.delegate scrollViewDidEndDecelerating:scrollView]; -} - -- (BOOL)shouldBatchFetchForCollectionNode:(ASCollectionNode *)collectionNode -{ - if ([_collectionDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionNode:)]) { - return [_collectionDelegate shouldBatchFetchForCollectionNode:collectionNode]; - } - - NSInteger sectionCount = [self numberOfSectionsInCollectionNode:collectionNode]; - if (sectionCount == 0) { - return NO; - } - - // If they implement shouldBatchFetch, call it. Otherwise, just say YES if they implement beginBatchFetch. - ASIGSectionController *ctrl = [self sectionControllerForSection:sectionCount - 1]; - ASSectionControllerOverrides o = [ASIGListAdapterBasedDataSource overridesForSectionControllerClass:ctrl.class]; - BOOL result = (o.shouldBatchFetch ? [ctrl shouldBatchFetch] : o.beginBatchFetchWithContext); - if (result) { - self.sectionControllerForBatchFetching = ctrl; - } - return result; -} - -- (void)collectionNode:(ASCollectionNode *)collectionNode willBeginBatchFetchWithContext:(ASBatchContext *)context -{ - if ([_collectionDelegate respondsToSelector:@selector(collectionNode:willBeginBatchFetchWithContext:)]) { - [_collectionDelegate collectionNode:collectionNode willBeginBatchFetchWithContext:context]; - return; - } - - ASIGSectionController *ctrl = self.sectionControllerForBatchFetching; - self.sectionControllerForBatchFetching = nil; - [ctrl beginBatchFetchWithContext:context]; -} - -/** - * Note: It is not documented that ASCollectionNode will forward these UIKit delegate calls if they are implemented. - * It is not considered harmful to do so, and adding them to documentation will confuse most users, who should - * instead using the ASCollectionDelegate callbacks. - */ -#pragma mark - ASCollectionDelegateInterop - -- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath -{ - [self.delegate collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath]; -} - -- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath -{ - [self.delegate collectionView:collectionView didEndDisplayingCell:cell forItemAtIndexPath:indexPath]; -} - -#pragma mark - ASCollectionDelegateFlowLayout - -- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForHeaderInSection:(NSInteger)section -{ - id src = [self supplementaryElementSourceForSection:section]; - if ([ASIGListAdapterBasedDataSource overridesForSupplementarySourceClass:[src class]].sizeRangeForSupplementary) { - return [src sizeRangeForSupplementaryElementOfKind:UICollectionElementKindSectionHeader atIndex:0]; - } else { - return ASSizeRangeZero; - } -} - -- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForFooterInSection:(NSInteger)section -{ - id src = [self supplementaryElementSourceForSection:section]; - if ([ASIGListAdapterBasedDataSource overridesForSupplementarySourceClass:[src class]].sizeRangeForSupplementary) { - return [src sizeRangeForSupplementaryElementOfKind:UICollectionElementKindSectionFooter atIndex:0]; - } else { - return ASSizeRangeZero; - } -} - -- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section -{ - return [self.delegate collectionView:collectionView layout:collectionViewLayout insetForSectionAtIndex:section]; -} - -- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section -{ - return [self.delegate collectionView:collectionView layout:collectionViewLayout minimumLineSpacingForSectionAtIndex:section]; -} - -- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section -{ - return [self.delegate collectionView:collectionView layout:collectionViewLayout minimumInteritemSpacingForSectionAtIndex:section]; -} - -#pragma mark - ASCollectionDataSource - -- (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section -{ - return [self.dataSource collectionView:collectionNode.view numberOfItemsInSection:section]; -} - -- (NSInteger)numberOfSectionsInCollectionNode:(ASCollectionNode *)collectionNode -{ - return [self.dataSource numberOfSectionsInCollectionView:collectionNode.view]; -} - -- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASIGSectionController *ctrl = [self sectionControllerForSection:indexPath.section]; - ASDisplayNodeAssert([ctrl respondsToSelector:@selector(nodeBlockForItemAtIndex:)], @"Expected section controller to respond to to %@. Controller: %@", NSStringFromSelector(@selector(nodeBlockForItemAtIndex:)), ctrl); - return [ctrl nodeBlockForItemAtIndex:indexPath.item]; -} - -- (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASIGSectionController *ctrl = [self sectionControllerForSection:indexPath.section]; - ASDisplayNodeAssert([ctrl respondsToSelector:@selector(nodeForItemAtIndex:)], @"Expected section controller to respond to to %@. Controller: %@", NSStringFromSelector(@selector(nodeForItemAtIndex:)), ctrl); - return [ctrl nodeForItemAtIndex:indexPath.item]; -} - -- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode constrainedSizeForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASIGSectionController *ctrl = [self sectionControllerForSection:indexPath.section]; - if ([ASIGListAdapterBasedDataSource overridesForSectionControllerClass:ctrl.class].sizeRangeForItem) { - return [ctrl sizeRangeForItemAtIndex:indexPath.item]; - } else { - return ASSizeRangeUnconstrained; - } -} - -- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - id ctrl = [self supplementaryElementSourceForSection:indexPath.section]; - ASDisplayNodeAssert([ctrl respondsToSelector:@selector(nodeBlockForSupplementaryElementOfKind:atIndex:)], @"Expected section controller to respond to to %@. Controller: %@", NSStringFromSelector(@selector(nodeBlockForSupplementaryElementOfKind:atIndex:)), ctrl); - return [ctrl nodeBlockForSupplementaryElementOfKind:kind atIndex:indexPath.item]; -} - -- (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - id ctrl = [self supplementaryElementSourceForSection:indexPath.section]; - ASDisplayNodeAssert([ctrl respondsToSelector:@selector(nodeForSupplementaryElementOfKind:atIndex:)], @"Expected section controller to respond to to %@. Controller: %@", NSStringFromSelector(@selector(nodeForSupplementaryElementOfKind:atIndex:)), ctrl); - return [ctrl nodeForSupplementaryElementOfKind:kind atIndex:indexPath.item]; -} - -- (NSArray *)collectionNode:(ASCollectionNode *)collectionNode supplementaryElementKindsInSection:(NSInteger)section -{ - return [[self supplementaryElementSourceForSection:section] supportedElementKinds]; -} - -#pragma mark - ASCollectionDataSourceInterop - -- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - return [self.dataSource collectionView:collectionView cellForItemAtIndexPath:indexPath]; -} - -- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - return [self.dataSource collectionView:collectionView viewForSupplementaryElementOfKind:kind atIndexPath:indexPath]; -} - -+ (BOOL)dequeuesCellsForNodeBackedItems -{ - return YES; -} - -#pragma mark - Helpers - -- (id)supplementaryElementSourceForSection:(NSInteger)section -{ - ASIGSectionController *ctrl = [self sectionControllerForSection:section]; - id src = (id)ctrl.supplementaryViewSource; - ASDisplayNodeAssert(src == nil || [src conformsToProtocol:@protocol(ASSupplementaryNodeSource)], @"Supplementary view source should conform to %@", NSStringFromProtocol(@protocol(ASSupplementaryNodeSource))); - return src; -} - -- (ASIGSectionController *)sectionControllerForSection:(NSInteger)section -{ - id object = [_listAdapter objectAtSection:section]; - ASIGSectionController *ctrl = (ASIGSectionController *)[_listAdapter sectionControllerForObject:object]; - ASDisplayNodeAssert([ctrl conformsToProtocol:@protocol(ASSectionController)], @"Expected section controller to conform to %@. Controller: %@", NSStringFromProtocol(@protocol(ASSectionController)), ctrl); - return ctrl; -} - -/// If needed, set ASCollectionView's superclass to IGListCollectionView (IGListKit < 3.0). -#if IG_LIST_COLLECTION_VIEW -+ (void)setASCollectionViewSuperclass -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - class_setSuperclass([ASCollectionView class], [IGListCollectionView class]); - }); -#pragma clang diagnostic pop -} -#endif - -/// Ensure updater won't call reloadData on us. -+ (void)configureUpdater:(id)updater -{ - // Cast to NSObject will be removed after https://github.com/Instagram/IGListKit/pull/435 - if ([(id)updater isKindOfClass:[IGListAdapterUpdater class]]) { - [(IGListAdapterUpdater *)updater setAllowsBackgroundReloading:NO]; - } else { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSLog(@"WARNING: Use of non-%@ updater with AsyncDisplayKit is discouraged. Updater: %@", NSStringFromClass([IGListAdapterUpdater class]), updater); - }); - } -} - -+ (ASSupplementarySourceOverrides)overridesForSupplementarySourceClass:(Class)c -{ - static NSCache *cache; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - cache = [[NSCache alloc] init]; - }); - NSValue *obj = [cache objectForKey:c]; - ASSupplementarySourceOverrides o; - if (obj == nil) { - o.sizeRangeForSupplementary = [c instancesRespondToSelector:@selector(sizeRangeForSupplementaryElementOfKind:atIndex:)]; - obj = [NSValue valueWithBytes:&o objCType:@encode(ASSupplementarySourceOverrides)]; - [cache setObject:obj forKey:c]; - } else { - [obj getValue:&o]; - } - return o; -} - -+ (ASSectionControllerOverrides)overridesForSectionControllerClass:(Class)c -{ - static NSCache *cache; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - cache = [[NSCache alloc] init]; - }); - NSValue *obj = [cache objectForKey:c]; - ASSectionControllerOverrides o; - if (obj == nil) { - o.sizeRangeForItem = [c instancesRespondToSelector:@selector(sizeRangeForItemAtIndex:)]; - o.beginBatchFetchWithContext = [c instancesRespondToSelector:@selector(beginBatchFetchWithContext:)]; - o.shouldBatchFetch = [c instancesRespondToSelector:@selector(shouldBatchFetch)]; - obj = [NSValue valueWithBytes:&o objCType:@encode(ASSectionControllerOverrides)]; - [cache setObject:obj forKey:c]; - } else { - [obj getValue:&o]; - } - return o; -} - -@end - -#endif // AS_IG_LIST_KIT diff --git a/submodules/AsyncDisplayKit/Source/ASImageNode+AnimatedImage.mm b/submodules/AsyncDisplayKit/Source/ASImageNode+AnimatedImage.mm deleted file mode 100644 index 10af9b7c4f..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASImageNode+AnimatedImage.mm +++ /dev/null @@ -1,416 +0,0 @@ -// -// 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 - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import "Private/ASInternalHelpers.h" -#import -#import -#import - -#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 )animatedImage -{ - ASLockScopeSelf(); - [self _locked_setAnimatedImage:animatedImage]; -} - -- (void)_locked_setAnimatedImage:(id )animatedImage -{ - ASAssertLocked(__instanceLock__); - - if (ASObjectIsEqual(_animatedImage, animatedImage) && (animatedImage == nil || animatedImage.playbackReady)) { - return; - } - - __block id 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 )newAnimatedImage previousAnimatedImage:(id )previousAnimatedImage -{ - // Subclass hook should not be called with the lock held - ASAssertUnlocked(__instanceLock__); - - // Subclasses may override -} - -- (id )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 diff --git a/submodules/AsyncDisplayKit/Source/ASImageNode+AnimatedImagePrivate.h b/submodules/AsyncDisplayKit/Source/ASImageNode+AnimatedImagePrivate.h deleted file mode 100644 index 528b39beb3..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASImageNode+AnimatedImagePrivate.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// ASImageNode+AnimatedImagePrivate.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 - -#define ASAnimatedImageDefaultRunLoopMode NSRunLoopCommonModes - -@interface ASImageNode () -{ -#ifndef MINIMAL_ASDK - AS::Mutex _displayLinkLock; - id _animatedImage; - BOOL _animatedImagePaused; - NSString *_animatedImageRunLoopMode; - CADisplayLink *_displayLink; - NSUInteger _lastSuccessfulFrameIndex; - - //accessed on main thread only - CFTimeInterval _playHead; - NSUInteger _playedLoops; -#endif -} - -@property (nonatomic) CFTimeInterval lastDisplayLinkFire; - -@end - -#ifndef MINIMAL_ASDK -@interface ASImageNode (AnimatedImagePrivate) - -- (void)_locked_setAnimatedImage:(id )animatedImage; - -@end - -@interface ASImageNode (AnimatedImageInvalidation) - -- (void)invalidateAnimatedImage; - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASImageNode+CGExtras.h b/submodules/AsyncDisplayKit/Source/ASImageNode+CGExtras.h deleted file mode 100644 index 01b546443b..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASImageNode+CGExtras.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// ASImageNode+CGExtras.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 -#import - -/** - @abstract Decides how to scale and crop an image to fit in the provided size, while not wasting memory by upscaling images - @param sourceImageSize The size of the encoded image. - @param boundsSize The bounds in which the image will be displayed. - @param contentMode The mode that defines how image will be scaled and cropped to fit. Supported values are UIViewContentModeScaleToAspectFill and UIViewContentModeScaleToAspectFit. - @param cropRect A rectangle that is to be featured by the cropped image. 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 contentMode will be used to determine the drawRect's size, and only the cropRect's origin will be used for positioning. - @param forceUpscaling A boolean that indicates you would *not* like the backing size to be downscaled if the image is smaller than the destination size. Setting this to YES will result in higher memory usage when images are smaller than their destination. - @param forcedSize A CGSize, that if non-CGSizeZero, indicates that the backing size should be forcedSize and not calculated based on boundsSize. - @discussion If the image is smaller than the size and UIViewContentModeScaleToAspectFill is specified, we suggest the input size so it will be efficiently upscaled on the GPU by the displaying layer at composite time. - */ -AS_EXTERN void ASCroppedImageBackingSizeAndDrawRectInBounds(CGSize sourceImageSize, - CGSize boundsSize, - UIViewContentMode contentMode, - CGRect cropRect, - BOOL forceUpscaling, - CGSize forcedSize, - CGSize *outBackingSize, - CGRect *outDrawRect - ); diff --git a/submodules/AsyncDisplayKit/Source/ASImageNode+CGExtras.mm b/submodules/AsyncDisplayKit/Source/ASImageNode+CGExtras.mm deleted file mode 100644 index 40b331fb32..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASImageNode+CGExtras.mm +++ /dev/null @@ -1,123 +0,0 @@ -// -// ASImageNode+CGExtras.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 "ASImageNode+CGExtras.h" - -#import - -// TODO rewrite these to be closer to the intended use -- take UIViewContentMode as param, CGRect destinationBounds, CGSize sourceSize. -static CGSize _ASSizeFillWithAspectRatio(CGFloat aspectRatio, CGSize constraints); -static CGSize _ASSizeFitWithAspectRatio(CGFloat aspectRatio, CGSize constraints); - -static CGSize _ASSizeFillWithAspectRatio(CGFloat sizeToScaleAspectRatio, CGSize destinationSize) -{ - CGFloat destinationAspectRatio = destinationSize.width / destinationSize.height; - if (sizeToScaleAspectRatio > destinationAspectRatio) { - return CGSizeMake(destinationSize.height * sizeToScaleAspectRatio, destinationSize.height); - } else { - return CGSizeMake(destinationSize.width, round(destinationSize.width / sizeToScaleAspectRatio)); - } -} - -static CGSize _ASSizeFitWithAspectRatio(CGFloat aspectRatio, CGSize constraints) -{ - CGFloat constraintAspectRatio = constraints.width / constraints.height; - if (aspectRatio > constraintAspectRatio) { - return CGSizeMake(constraints.width, constraints.width / aspectRatio); - } else { - return CGSizeMake(constraints.height * aspectRatio, constraints.height); - } -} - -void ASCroppedImageBackingSizeAndDrawRectInBounds(CGSize sourceImageSize, - CGSize boundsSize, - UIViewContentMode contentMode, - CGRect cropRect, - BOOL forceUpscaling, - CGSize forcedSize, - CGSize *outBackingSize, - CGRect *outDrawRect - ) -{ - - size_t destinationWidth = boundsSize.width; - size_t destinationHeight = boundsSize.height; - - // Often, an image is too low resolution to completely fill the width and height provided. - // Per the API contract as commented in the header, we will adjust input parameters (destinationWidth, destinationHeight) to ensure that the image is not upscaled on the CPU. - CGFloat boundsAspectRatio = (CGFloat)destinationWidth / (CGFloat)destinationHeight; - - CGSize scaledSizeForImage = sourceImageSize; - BOOL cropToRectDimensions = !CGRectIsEmpty(cropRect); - - if (cropToRectDimensions) { - scaledSizeForImage = CGSizeMake(boundsSize.width / cropRect.size.width, boundsSize.height / cropRect.size.height); - } else { - if (contentMode == UIViewContentModeScaleAspectFill) - scaledSizeForImage = _ASSizeFillWithAspectRatio(boundsAspectRatio, sourceImageSize); - else if (contentMode == UIViewContentModeScaleAspectFit) - scaledSizeForImage = _ASSizeFitWithAspectRatio(boundsAspectRatio, sourceImageSize); - } - - // If fitting the desired aspect ratio to the image size actually results in a larger buffer, use the input values. - // However, if there is a pixel savings (e.g. we would have to upscale the image), override the function arguments. - if (CGSizeEqualToSize(CGSizeZero, forcedSize) == NO) { - destinationWidth = (size_t)round(forcedSize.width); - destinationHeight = (size_t)round(forcedSize.height); - } else if (forceUpscaling == NO && (scaledSizeForImage.width * scaledSizeForImage.height) < (destinationWidth * destinationHeight)) { - destinationWidth = (size_t)round(scaledSizeForImage.width); - destinationHeight = (size_t)round(scaledSizeForImage.height); - if (destinationWidth == 0 || destinationHeight == 0) { - *outBackingSize = CGSizeZero; - *outDrawRect = CGRectZero; - return; - } - } - - // Figure out the scaled size within the destination bounds. - CGFloat sourceImageAspectRatio = sourceImageSize.width / sourceImageSize.height; - CGSize scaledSizeForDestination = CGSizeMake(destinationWidth, destinationHeight); - - if (cropToRectDimensions) { - scaledSizeForDestination = CGSizeMake(boundsSize.width / cropRect.size.width, boundsSize.height / cropRect.size.height); - } else { - if (contentMode == UIViewContentModeScaleAspectFill) - scaledSizeForDestination = _ASSizeFillWithAspectRatio(sourceImageAspectRatio, scaledSizeForDestination); - else if (contentMode == UIViewContentModeScaleAspectFit) - scaledSizeForDestination = _ASSizeFitWithAspectRatio(sourceImageAspectRatio, scaledSizeForDestination); - } - - // Figure out the rectangle into which to draw the image. - CGRect drawRect = CGRectZero; - if (cropToRectDimensions) { - drawRect = CGRectMake(-cropRect.origin.x * scaledSizeForDestination.width, - -cropRect.origin.y * scaledSizeForDestination.height, - scaledSizeForDestination.width, - scaledSizeForDestination.height); - } else { - // We want to obey the origin of cropRect in aspect-fill mode. - if (contentMode == UIViewContentModeScaleAspectFill) { - drawRect = CGRectMake(((destinationWidth - scaledSizeForDestination.width) * cropRect.origin.x), - ((destinationHeight - scaledSizeForDestination.height) * cropRect.origin.y), - scaledSizeForDestination.width, - scaledSizeForDestination.height); - - } - // And otherwise just center it. - else { - drawRect = CGRectMake(((destinationWidth - scaledSizeForDestination.width) / 2.0), - ((destinationHeight - scaledSizeForDestination.height) / 2.0), - scaledSizeForDestination.width, - scaledSizeForDestination.height); - } - } - - *outDrawRect = drawRect; - *outBackingSize = CGSizeMake(destinationWidth, destinationHeight); -} diff --git a/submodules/AsyncDisplayKit/Source/ASImageNode+Private.h b/submodules/AsyncDisplayKit/Source/ASImageNode+Private.h deleted file mode 100644 index 8de78c8784..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASImageNode+Private.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// ASImageNode+Private.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 - -@interface ASImageNode (Private) - -- (void)_locked_setImage:(UIImage *)image; -- (UIImage *)_locked_Image; - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASImageNode.h b/submodules/AsyncDisplayKit/Source/ASImageNode.h deleted file mode 100644 index 902591c4f6..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASImageNode.h +++ /dev/null @@ -1,219 +0,0 @@ -// -// 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 -#import - -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 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 )newAnimatedImage previousAnimatedImage:(nullable id )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 - * - * @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 - * - * @return An ASImageNode image modification block. - */ -AS_EXTERN asimagenode_modification_block_t ASImageNodeTintColorModificationBlock(UIColor *color); - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASImageNode.mm b/submodules/AsyncDisplayKit/Source/ASImageNode.mm deleted file mode 100644 index 8cc4037700..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASImageNode.mm +++ /dev/null @@ -1,784 +0,0 @@ -// -// 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 "ASImageNode.h" - -#import - -#import -#import -#import -#import "Private/ASDisplayNode+FrameworkPrivate.h" -#import -#import -#import -#import -#import -#import -#import -#import -#import "Private/ASInternalHelpers.h" -#import -#import -#import -#import -#import - -// TODO: It would be nice to remove this dependency; it's the only subclass using more than +FrameworkSubclasses.h -#import - -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)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 *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 *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; - }; -} diff --git a/submodules/AsyncDisplayKit/Source/ASImageNode.mm.orig b/submodules/AsyncDisplayKit/Source/ASImageNode.mm.orig deleted file mode 100644 index 0106295a55..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASImageNode.mm.orig +++ /dev/null @@ -1,791 +0,0 @@ -// -// 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 - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -// TODO: It would be nice to remove this dependency; it's the only subclass using more than +FrameworkSubclasses.h -#import - -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)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 *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 *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; - }; -} diff --git a/submodules/AsyncDisplayKit/Source/ASLayerBackingTipProvider.h b/submodules/AsyncDisplayKit/Source/ASLayerBackingTipProvider.h deleted file mode 100644 index 0cc3d03b61..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASLayerBackingTipProvider.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// ASLayerBackingTipProvider.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 "ASTipProvider.h" -#import - -#if AS_ENABLE_TIPS - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASLayerBackingTipProvider : ASTipProvider - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/ASLayerBackingTipProvider.mm b/submodules/AsyncDisplayKit/Source/ASLayerBackingTipProvider.mm deleted file mode 100644 index 19d1850ab7..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASLayerBackingTipProvider.mm +++ /dev/null @@ -1,45 +0,0 @@ -// -// ASLayerBackingTipProvider.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 "ASLayerBackingTipProvider.h" - -#if AS_ENABLE_TIPS - -#import -#import -#import -#import -#import - -@implementation ASLayerBackingTipProvider - -- (ASTip *)tipForNode:(ASDisplayNode *)node -{ - // Already layer-backed. - if (node.layerBacked) { - return nil; - } - - // TODO: Avoid revisiting nodes we already visited - ASDisplayNode *failNode = ASDisplayNodeFindFirstNode(node, ^BOOL(ASDisplayNode * _Nonnull node) { - return !node.supportsLayerBacking; - }); - if (failNode != nil) { - return nil; - } - - ASTip *result = [[ASTip alloc] initWithNode:node - kind:ASTipKindEnableLayerBacking - format:@"Enable layer backing to improve performance"]; - return result; -} - -@end - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/ASLayout+IGListKit.h b/submodules/AsyncDisplayKit/Source/ASLayout+IGListKit.h deleted file mode 100644 index 6f6753da2c..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASLayout+IGListKit.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// ASLayout+IGListKit.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#if AS_IG_LIST_KIT -#import -#import -@interface ASLayout(IGListKit) -@end - -#endif // AS_IG_LIST_KIT diff --git a/submodules/AsyncDisplayKit/Source/ASLayout+IGListKit.mm b/submodules/AsyncDisplayKit/Source/ASLayout+IGListKit.mm deleted file mode 100644 index f9cc139e9c..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASLayout+IGListKit.mm +++ /dev/null @@ -1,30 +0,0 @@ -// -// ASLayout+IGListKit.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// -#import -#if AS_IG_LIST_KIT -#import "ASLayout+IGListKit.h" - -@interface ASLayout() { -@public - id _layoutElement; -} -@end - -@implementation ASLayout(IGListKit) - -- (id )diffIdentifier -{ - return self->_layoutElement; -} - -- (BOOL)isEqualToDiffableObject:(id )other -{ - return [self isEqual:other]; -} -@end -#endif // AS_IG_LIST_KIT diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutElement.mm.orig b/submodules/AsyncDisplayKit/Source/ASLayoutElement.mm.orig deleted file mode 100644 index 3a17293bc9..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASLayoutElement.mm.orig +++ /dev/null @@ -1,869 +0,0 @@ -// -// ASLayoutElement.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 -#import -#import -#import -#import -#import -#import - -#import -#include - -#if YOGA - #import YOGA_HEADER_PATH - #import -#endif - -#pragma mark - ASLayoutElementContext - -@implementation ASLayoutElementContext - -- (instancetype)init -{ - if (self = [super init]) { - _transitionID = ASLayoutElementContextDefaultTransitionID; - } - return self; -} - -@end - -CGFloat const ASLayoutElementParentDimensionUndefined = NAN; -CGSize const ASLayoutElementParentSizeUndefined = {ASLayoutElementParentDimensionUndefined, ASLayoutElementParentDimensionUndefined}; - -int32_t const ASLayoutElementContextInvalidTransitionID = 0; -int32_t const ASLayoutElementContextDefaultTransitionID = ASLayoutElementContextInvalidTransitionID + 1; - -<<<<<<< HEAD -#ifdef MINIMAL_ASDK -static ASLayoutElementContext *mainThreadTlsContext = nil; - -static ASLayoutElementContext *get_tls_context() { - if ([NSThread isMainThread]) { - return mainThreadTlsContext; - } else { - return [NSThread currentThread].threadDictionary[@"ASDK_tls_context"]; - } -} - -static void set_tls_context(ASLayoutElementContext *value) { - if ([NSThread isMainThread]) { - mainThreadTlsContext = value; - } else { - if (value != nil) { - [NSThread currentThread].threadDictionary[@"ASDK_tls_context"] = value; - } else { - [[NSThread currentThread].threadDictionary removeObjectForKey:@"ASDK_tls_context"]; - } - } -} -#else -======= -#if AS_TLS_AVAILABLE - ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e -static _Thread_local __unsafe_unretained ASLayoutElementContext *tls_context; -#endif - -void ASLayoutElementPushContext(ASLayoutElementContext *context) -{ -#ifdef MINIMAL_ASDK - // NOTE: It would be easy to support nested contexts – just use an NSMutableArray here. - ASDisplayNodeCAssertNil(get_tls_context(), @"Nested ASLayoutElementContexts aren't supported."); - - ; - set_tls_context(context); -#else - // NOTE: It would be easy to support nested contexts – just use an NSMutableArray here. - ASDisplayNodeCAssertNil(tls_context, @"Nested ASLayoutElementContexts aren't supported."); - - tls_context = (__bridge ASLayoutElementContext *)(__bridge_retained CFTypeRef)context; -#endif -} - -ASLayoutElementContext *ASLayoutElementGetCurrentContext() -{ - // Don't retain here. Caller will retain if it wants to! - return get_tls_context(); -} - -void ASLayoutElementPopContext() -{ -#ifdef MINIMAL_ASDK - ASDisplayNodeCAssertNotNil(get_tls_context(), @"Attempt to pop context when there wasn't a context!"); - set_tls_context(nil); -#else - ASDisplayNodeCAssertNotNil(tls_context, @"Attempt to pop context when there wasn't a context!"); - CFRelease((__bridge CFTypeRef)tls_context); - tls_context = nil; -#endif -} - -#else - -static pthread_key_t ASLayoutElementContextKey() { - static pthread_key_t k; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - pthread_key_create(&k, NULL); - }); - return k; -} -void ASLayoutElementPushContext(ASLayoutElementContext *context) -{ - // NOTE: It would be easy to support nested contexts – just use an NSMutableArray here. - ASDisplayNodeCAssertNil(pthread_getspecific(ASLayoutElementContextKey()), @"Nested ASLayoutElementContexts aren't supported."); - - let cfCtx = (__bridge_retained CFTypeRef)context; - pthread_setspecific(ASLayoutElementContextKey(), cfCtx); -} - -ASLayoutElementContext *ASLayoutElementGetCurrentContext() -{ - // Don't retain here. Caller will retain if it wants to! - let ctxPtr = pthread_getspecific(ASLayoutElementContextKey()); - return (__bridge ASLayoutElementContext *)ctxPtr; -} - -void ASLayoutElementPopContext() -{ - let ctx = (CFTypeRef)pthread_getspecific(ASLayoutElementContextKey()); - ASDisplayNodeCAssertNotNil(ctx, @"Attempt to pop context when there wasn't a context!"); - CFRelease(ctx); - pthread_setspecific(ASLayoutElementContextKey(), NULL); -} - -#endif // AS_TLS_AVAILABLE - -#pragma mark - ASLayoutElementStyle - -NSString * const ASLayoutElementStyleWidthProperty = @"ASLayoutElementStyleWidthProperty"; -NSString * const ASLayoutElementStyleMinWidthProperty = @"ASLayoutElementStyleMinWidthProperty"; -NSString * const ASLayoutElementStyleMaxWidthProperty = @"ASLayoutElementStyleMaxWidthProperty"; - -NSString * const ASLayoutElementStyleHeightProperty = @"ASLayoutElementStyleHeightProperty"; -NSString * const ASLayoutElementStyleMinHeightProperty = @"ASLayoutElementStyleMinHeightProperty"; -NSString * const ASLayoutElementStyleMaxHeightProperty = @"ASLayoutElementStyleMaxHeightProperty"; - -NSString * const ASLayoutElementStyleSpacingBeforeProperty = @"ASLayoutElementStyleSpacingBeforeProperty"; -NSString * const ASLayoutElementStyleSpacingAfterProperty = @"ASLayoutElementStyleSpacingAfterProperty"; -NSString * const ASLayoutElementStyleFlexGrowProperty = @"ASLayoutElementStyleFlexGrowProperty"; -NSString * const ASLayoutElementStyleFlexShrinkProperty = @"ASLayoutElementStyleFlexShrinkProperty"; -NSString * const ASLayoutElementStyleFlexBasisProperty = @"ASLayoutElementStyleFlexBasisProperty"; -NSString * const ASLayoutElementStyleAlignSelfProperty = @"ASLayoutElementStyleAlignSelfProperty"; -NSString * const ASLayoutElementStyleAscenderProperty = @"ASLayoutElementStyleAscenderProperty"; -NSString * const ASLayoutElementStyleDescenderProperty = @"ASLayoutElementStyleDescenderProperty"; - -NSString * const ASLayoutElementStyleLayoutPositionProperty = @"ASLayoutElementStyleLayoutPositionProperty"; - -#if YOGA -NSString * const ASYogaFlexWrapProperty = @"ASLayoutElementStyleLayoutFlexWrapProperty"; -NSString * const ASYogaFlexDirectionProperty = @"ASYogaFlexDirectionProperty"; -NSString * const ASYogaDirectionProperty = @"ASYogaDirectionProperty"; -NSString * const ASYogaSpacingProperty = @"ASYogaSpacingProperty"; -NSString * const ASYogaJustifyContentProperty = @"ASYogaJustifyContentProperty"; -NSString * const ASYogaAlignItemsProperty = @"ASYogaAlignItemsProperty"; -NSString * const ASYogaPositionTypeProperty = @"ASYogaPositionTypeProperty"; -NSString * const ASYogaPositionProperty = @"ASYogaPositionProperty"; -NSString * const ASYogaMarginProperty = @"ASYogaMarginProperty"; -NSString * const ASYogaPaddingProperty = @"ASYogaPaddingProperty"; -NSString * const ASYogaBorderProperty = @"ASYogaBorderProperty"; -NSString * const ASYogaAspectRatioProperty = @"ASYogaAspectRatioProperty"; -#endif - -#define ASLayoutElementStyleSetSizeWithScope(x) \ - __instanceLock__.lock(); \ - ASLayoutElementSize newSize = _size.load(); \ - { x } \ - _size.store(newSize); \ - __instanceLock__.unlock(); - -#define ASLayoutElementStyleCallDelegate(propertyName)\ -do {\ - [self propertyDidChange:propertyName];\ - [_delegate style:self propertyDidChange:propertyName];\ -} while(0) - -@implementation ASLayoutElementStyle { - ASDN::RecursiveMutex __instanceLock__; - ASLayoutElementStyleExtensions _extensions; - - std::atomic _size; - std::atomic _spacingBefore; - std::atomic _spacingAfter; - std::atomic _flexGrow; - std::atomic _flexShrink; - std::atomic _flexBasis; - std::atomic _alignSelf; - std::atomic _ascender; - std::atomic _descender; - std::atomic _layoutPosition; - -#if YOGA - YGNodeRef _yogaNode; - std::atomic _flexWrap; - std::atomic _flexDirection; - std::atomic _direction; - std::atomic _justifyContent; - std::atomic _alignItems; - std::atomic _positionType; - std::atomic _position; - std::atomic _margin; - std::atomic _padding; - std::atomic _border; - std::atomic _aspectRatio; -#endif -} - -@dynamic width, height, minWidth, maxWidth, minHeight, maxHeight; -@dynamic preferredSize, minSize, maxSize, preferredLayoutSize, minLayoutSize, maxLayoutSize; - -#pragma mark - Lifecycle - -- (instancetype)initWithDelegate:(id)delegate -{ - self = [self init]; - if (self) { - _delegate = delegate; - } - return self; -} - -- (instancetype)init -{ - self = [super init]; - if (self) { - _size = ASLayoutElementSizeMake(); - } - return self; -} - -ASSynthesizeLockingMethodsWithMutex(__instanceLock__) - -#pragma mark - ASLayoutElementStyleSize - -- (ASLayoutElementSize)size -{ - return _size.load(); -} - -- (void)setSize:(ASLayoutElementSize)size -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize = size; - }); - // No CallDelegate method as ASLayoutElementSize is currently internal. -} - -#pragma mark - ASLayoutElementStyleSizeForwarding - -- (ASDimension)width -{ - return _size.load().width; -} - -- (void)setWidth:(ASDimension)width -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.width = width; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleWidthProperty); -} - -- (ASDimension)height -{ - return _size.load().height; -} - -- (void)setHeight:(ASDimension)height -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.height = height; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleHeightProperty); -} - -- (ASDimension)minWidth -{ - return _size.load().minWidth; -} - -- (void)setMinWidth:(ASDimension)minWidth -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.minWidth = minWidth; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinWidthProperty); -} - -- (ASDimension)maxWidth -{ - return _size.load().maxWidth; -} - -- (void)setMaxWidth:(ASDimension)maxWidth -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.maxWidth = maxWidth; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxWidthProperty); -} - -- (ASDimension)minHeight -{ - return _size.load().minHeight; -} - -- (void)setMinHeight:(ASDimension)minHeight -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.minHeight = minHeight; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinHeightProperty); -} - -- (ASDimension)maxHeight -{ - return _size.load().maxHeight; -} - -- (void)setMaxHeight:(ASDimension)maxHeight -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.maxHeight = maxHeight; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxHeightProperty); -} - - -#pragma mark - ASLayoutElementStyleSizeHelpers - -- (void)setPreferredSize:(CGSize)preferredSize -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.width = ASDimensionMakeWithPoints(preferredSize.width); - newSize.height = ASDimensionMakeWithPoints(preferredSize.height); - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleWidthProperty); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleHeightProperty); -} - -- (CGSize)preferredSize -{ - ASLayoutElementSize size = _size.load(); - if (size.width.unit == ASDimensionUnitFraction) { - NSCAssert(NO, @"Cannot get preferredSize of element with fractional width. Width: %@.", NSStringFromASDimension(size.width)); - return CGSizeZero; - } - - if (size.height.unit == ASDimensionUnitFraction) { - NSCAssert(NO, @"Cannot get preferredSize of element with fractional height. Height: %@.", NSStringFromASDimension(size.height)); - return CGSizeZero; - } - - return CGSizeMake(size.width.value, size.height.value); -} - -- (void)setMinSize:(CGSize)minSize -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.minWidth = ASDimensionMakeWithPoints(minSize.width); - newSize.minHeight = ASDimensionMakeWithPoints(minSize.height); - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinWidthProperty); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinHeightProperty); -} - -- (void)setMaxSize:(CGSize)maxSize -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.maxWidth = ASDimensionMakeWithPoints(maxSize.width); - newSize.maxHeight = ASDimensionMakeWithPoints(maxSize.height); - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxWidthProperty); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxHeightProperty); -} - -- (ASLayoutSize)preferredLayoutSize -{ - ASLayoutElementSize size = _size.load(); - return ASLayoutSizeMake(size.width, size.height); -} - -- (void)setPreferredLayoutSize:(ASLayoutSize)preferredLayoutSize -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.width = preferredLayoutSize.width; - newSize.height = preferredLayoutSize.height; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleWidthProperty); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleHeightProperty); -} - -- (ASLayoutSize)minLayoutSize -{ - ASLayoutElementSize size = _size.load(); - return ASLayoutSizeMake(size.minWidth, size.minHeight); -} - -- (void)setMinLayoutSize:(ASLayoutSize)minLayoutSize -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.minWidth = minLayoutSize.width; - newSize.minHeight = minLayoutSize.height; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinWidthProperty); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMinHeightProperty); -} - -- (ASLayoutSize)maxLayoutSize -{ - ASLayoutElementSize size = _size.load(); - return ASLayoutSizeMake(size.maxWidth, size.maxHeight); -} - -- (void)setMaxLayoutSize:(ASLayoutSize)maxLayoutSize -{ - ASLayoutElementStyleSetSizeWithScope({ - newSize.maxWidth = maxLayoutSize.width; - newSize.maxHeight = maxLayoutSize.height; - }); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxWidthProperty); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxHeightProperty); -} - -#pragma mark - ASStackLayoutElement - -- (void)setSpacingBefore:(CGFloat)spacingBefore -{ - _spacingBefore.store(spacingBefore); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleSpacingBeforeProperty); -} - -- (CGFloat)spacingBefore -{ - return _spacingBefore.load(); -} - -- (void)setSpacingAfter:(CGFloat)spacingAfter -{ - _spacingAfter.store(spacingAfter); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleSpacingAfterProperty); -} - -- (CGFloat)spacingAfter -{ - return _spacingAfter.load(); -} - -- (void)setFlexGrow:(CGFloat)flexGrow -{ - _flexGrow.store(flexGrow); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleFlexGrowProperty); -} - -- (CGFloat)flexGrow -{ - return _flexGrow.load(); -} - -- (void)setFlexShrink:(CGFloat)flexShrink -{ - _flexShrink.store(flexShrink); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleFlexShrinkProperty); -} - -- (CGFloat)flexShrink -{ - return _flexShrink.load(); -} - -- (void)setFlexBasis:(ASDimension)flexBasis -{ - _flexBasis.store(flexBasis); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleFlexBasisProperty); -} - -- (ASDimension)flexBasis -{ - return _flexBasis.load(); -} - -- (void)setAlignSelf:(ASStackLayoutAlignSelf)alignSelf -{ - _alignSelf.store(alignSelf); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleAlignSelfProperty); -} - -- (ASStackLayoutAlignSelf)alignSelf -{ - return _alignSelf.load(); -} - -- (void)setAscender:(CGFloat)ascender -{ - _ascender.store(ascender); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleAscenderProperty); -} - -- (CGFloat)ascender -{ - return _ascender.load(); -} - -- (void)setDescender:(CGFloat)descender -{ - _descender.store(descender); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleDescenderProperty); -} - -- (CGFloat)descender -{ - return _descender.load(); -} - -#pragma mark - ASAbsoluteLayoutElement - -- (void)setLayoutPosition:(CGPoint)layoutPosition -{ - _layoutPosition.store(layoutPosition); - ASLayoutElementStyleCallDelegate(ASLayoutElementStyleLayoutPositionProperty); -} - -- (CGPoint)layoutPosition -{ - return _layoutPosition.load(); -} - -#pragma mark - Extensions - -- (void)setLayoutOptionExtensionBool:(BOOL)value atIndex:(int)idx -{ - NSCAssert(idx < kMaxLayoutElementBoolExtensions, @"Setting index outside of max bool extensions space"); - - ASDN::MutexLocker l(__instanceLock__); - _extensions.boolExtensions[idx] = value; -} - -- (BOOL)layoutOptionExtensionBoolAtIndex:(int)idx\ -{ - NSCAssert(idx < kMaxLayoutElementBoolExtensions, @"Accessing index outside of max bool extensions space"); - - ASDN::MutexLocker l(__instanceLock__); - return _extensions.boolExtensions[idx]; -} - -- (void)setLayoutOptionExtensionInteger:(NSInteger)value atIndex:(int)idx -{ - NSCAssert(idx < kMaxLayoutElementStateIntegerExtensions, @"Setting index outside of max integer extensions space"); - - ASDN::MutexLocker l(__instanceLock__); - _extensions.integerExtensions[idx] = value; -} - -- (NSInteger)layoutOptionExtensionIntegerAtIndex:(int)idx -{ - NSCAssert(idx < kMaxLayoutElementStateIntegerExtensions, @"Accessing index outside of max integer extensions space"); - - ASDN::MutexLocker l(__instanceLock__); - return _extensions.integerExtensions[idx]; -} - -- (void)setLayoutOptionExtensionEdgeInsets:(UIEdgeInsets)value atIndex:(int)idx -{ - NSCAssert(idx < kMaxLayoutElementStateEdgeInsetExtensions, @"Setting index outside of max edge insets extensions space"); - - ASDN::MutexLocker l(__instanceLock__); - _extensions.edgeInsetsExtensions[idx] = value; -} - -- (UIEdgeInsets)layoutOptionExtensionEdgeInsetsAtIndex:(int)idx -{ - NSCAssert(idx < kMaxLayoutElementStateEdgeInsetExtensions, @"Accessing index outside of max edge insets extensions space"); - - ASDN::MutexLocker l(__instanceLock__); - return _extensions.edgeInsetsExtensions[idx]; -} - -#pragma mark - Debugging - -- (NSString *)description -{ - return ASObjectDescriptionMake(self, [self propertiesForDescription]); -} - -- (NSMutableArray *)propertiesForDescription -{ - NSMutableArray *result = [NSMutableArray array]; - - if ((self.minLayoutSize.width.unit != ASDimensionUnitAuto || - self.minLayoutSize.height.unit != ASDimensionUnitAuto)) { - [result addObject:@{ @"minLayoutSize" : NSStringFromASLayoutSize(self.minLayoutSize) }]; - } - - if ((self.preferredLayoutSize.width.unit != ASDimensionUnitAuto || - self.preferredLayoutSize.height.unit != ASDimensionUnitAuto)) { - [result addObject:@{ @"preferredSize" : NSStringFromASLayoutSize(self.preferredLayoutSize) }]; - } - - if ((self.maxLayoutSize.width.unit != ASDimensionUnitAuto || - self.maxLayoutSize.height.unit != ASDimensionUnitAuto)) { - [result addObject:@{ @"maxLayoutSize" : NSStringFromASLayoutSize(self.maxLayoutSize) }]; - } - - if (self.alignSelf != ASStackLayoutAlignSelfAuto) { - [result addObject:@{ @"alignSelf" : [@[@"ASStackLayoutAlignSelfAuto", - @"ASStackLayoutAlignSelfStart", - @"ASStackLayoutAlignSelfEnd", - @"ASStackLayoutAlignSelfCenter", - @"ASStackLayoutAlignSelfStretch"] objectAtIndex:self.alignSelf] }]; - } - - if (self.ascender != 0) { - [result addObject:@{ @"ascender" : @(self.ascender) }]; - } - - if (self.descender != 0) { - [result addObject:@{ @"descender" : @(self.descender) }]; - } - - if (ASDimensionEqualToDimension(self.flexBasis, ASDimensionAuto) == NO) { - [result addObject:@{ @"flexBasis" : NSStringFromASDimension(self.flexBasis) }]; - } - - if (self.flexGrow != 0) { - [result addObject:@{ @"flexGrow" : @(self.flexGrow) }]; - } - - if (self.flexShrink != 0) { - [result addObject:@{ @"flexShrink" : @(self.flexShrink) }]; - } - - if (self.spacingAfter != 0) { - [result addObject:@{ @"spacingAfter" : @(self.spacingAfter) }]; - } - - if (self.spacingBefore != 0) { - [result addObject:@{ @"spacingBefore" : @(self.spacingBefore) }]; - } - - if (CGPointEqualToPoint(self.layoutPosition, CGPointZero) == NO) { - [result addObject:@{ @"layoutPosition" : [NSValue valueWithCGPoint:self.layoutPosition] }]; - } - - return result; -} - -- (void)propertyDidChange:(NSString *)propertyName -{ -#if YOGA - /* TODO(appleguy): STYLE SETTER METHODS LEFT TO IMPLEMENT - void YGNodeStyleSetOverflow(YGNodeRef node, YGOverflow overflow); - void YGNodeStyleSetFlex(YGNodeRef node, float flex); - */ - - if (_yogaNode == NULL) { - return; - } - // Because the NSStrings used to identify each property are const, use efficient pointer comparison. - if (propertyName == ASLayoutElementStyleWidthProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, Width, self.width); - } - else if (propertyName == ASLayoutElementStyleMinWidthProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, MinWidth, self.minWidth); - } - else if (propertyName == ASLayoutElementStyleMaxWidthProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, MaxWidth, self.maxWidth); - } - else if (propertyName == ASLayoutElementStyleHeightProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, Height, self.height); - } - else if (propertyName == ASLayoutElementStyleMinHeightProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, MinHeight, self.minHeight); - } - else if (propertyName == ASLayoutElementStyleMaxHeightProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, MaxHeight, self.maxHeight); - } - else if (propertyName == ASLayoutElementStyleFlexGrowProperty) { - YGNodeStyleSetFlexGrow(_yogaNode, self.flexGrow); - } - else if (propertyName == ASLayoutElementStyleFlexShrinkProperty) { - YGNodeStyleSetFlexShrink(_yogaNode, self.flexShrink); - } - else if (propertyName == ASLayoutElementStyleFlexBasisProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, FlexBasis, self.flexBasis); - } - else if (propertyName == ASLayoutElementStyleAlignSelfProperty) { - YGNodeStyleSetAlignSelf(_yogaNode, yogaAlignSelf(self.alignSelf)); - } - else if (propertyName == ASYogaFlexWrapProperty) { - YGNodeStyleSetFlexWrap(_yogaNode, self.flexWrap); - } - else if (propertyName == ASYogaFlexDirectionProperty) { - YGNodeStyleSetFlexDirection(_yogaNode, yogaFlexDirection(self.flexDirection)); - } - else if (propertyName == ASYogaDirectionProperty) { - YGNodeStyleSetDirection(_yogaNode, self.direction); - } - else if (propertyName == ASYogaJustifyContentProperty) { - YGNodeStyleSetJustifyContent(_yogaNode, yogaJustifyContent(self.justifyContent)); - } - else if (propertyName == ASYogaAlignItemsProperty) { - ASStackLayoutAlignItems alignItems = self.alignItems; - if (alignItems != ASStackLayoutAlignItemsNotSet) { - YGNodeStyleSetAlignItems(_yogaNode, yogaAlignItems(alignItems)); - } - } - else if (propertyName == ASYogaPositionTypeProperty) { - YGNodeStyleSetPositionType(_yogaNode, self.positionType); - } - else if (propertyName == ASYogaPositionProperty) { - ASEdgeInsets position = self.position; - YGEdge edge = YGEdgeLeft; - for (int i = 0; i < YGEdgeAll + 1; ++i) { - YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(_yogaNode, Position, dimensionForEdgeWithEdgeInsets(edge, position), edge); - edge = (YGEdge)(edge + 1); - } - } - else if (propertyName == ASYogaMarginProperty) { - ASEdgeInsets margin = self.margin; - YGEdge edge = YGEdgeLeft; - for (int i = 0; i < YGEdgeAll + 1; ++i) { - YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(_yogaNode, Margin, dimensionForEdgeWithEdgeInsets(edge, margin), edge); - edge = (YGEdge)(edge + 1); - } - } - else if (propertyName == ASYogaPaddingProperty) { - ASEdgeInsets padding = self.padding; - YGEdge edge = YGEdgeLeft; - for (int i = 0; i < YGEdgeAll + 1; ++i) { - YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(_yogaNode, Padding, dimensionForEdgeWithEdgeInsets(edge, padding), edge); - edge = (YGEdge)(edge + 1); - } - } - else if (propertyName == ASYogaBorderProperty) { - ASEdgeInsets border = self.border; - YGEdge edge = YGEdgeLeft; - for (int i = 0; i < YGEdgeAll + 1; ++i) { - YGNODE_STYLE_SET_FLOAT_WITH_EDGE(_yogaNode, Border, dimensionForEdgeWithEdgeInsets(edge, border), edge); - edge = (YGEdge)(edge + 1); - } - } - else if (propertyName == ASYogaAspectRatioProperty) { - CGFloat aspectRatio = self.aspectRatio; - if (aspectRatio > FLT_EPSILON && aspectRatio < CGFLOAT_MAX / 2.0) { - YGNodeStyleSetAspectRatio(_yogaNode, aspectRatio); - } - } -#endif -} - -#pragma mark - Yoga Flexbox Properties - -#if YOGA - -+ (void)initialize -{ - [super initialize]; - YGConfigSetPointScaleFactor(YGConfigGetDefault(), ASScreenScale()); - // Yoga recommends using Web Defaults for all new projects. This will be enabled for Texture very soon. - //YGConfigSetUseWebDefaults(YGConfigGetDefault(), true); -} - -- (YGNodeRef)yogaNode -{ - return _yogaNode; -} - -- (YGNodeRef)yogaNodeCreateIfNeeded -{ - if (_yogaNode == NULL) { - _yogaNode = YGNodeNew(); - } - return _yogaNode; -} - -- (void)destroyYogaNode -{ - if (_yogaNode != NULL) { - // Release the __bridge_retained Context object. - ASLayoutElementYogaUpdateMeasureFunc(_yogaNode, nil); - YGNodeFree(_yogaNode); - _yogaNode = NULL; - } -} - -- (void)dealloc -{ - [self destroyYogaNode]; -} - -- (YGWrap)flexWrap { return _flexWrap.load(); } -- (ASStackLayoutDirection)flexDirection { return _flexDirection.load(); } -- (YGDirection)direction { return _direction.load(); } -- (ASStackLayoutJustifyContent)justifyContent { return _justifyContent.load(); } -- (ASStackLayoutAlignItems)alignItems { return _alignItems.load(); } -- (YGPositionType)positionType { return _positionType.load(); } -- (ASEdgeInsets)position { return _position.load(); } -- (ASEdgeInsets)margin { return _margin.load(); } -- (ASEdgeInsets)padding { return _padding.load(); } -- (ASEdgeInsets)border { return _border.load(); } -- (CGFloat)aspectRatio { return _aspectRatio.load(); } - -- (void)setFlexWrap:(YGWrap)flexWrap { - _flexWrap.store(flexWrap); - ASLayoutElementStyleCallDelegate(ASYogaFlexWrapProperty); -} -- (void)setFlexDirection:(ASStackLayoutDirection)flexDirection { - _flexDirection.store(flexDirection); - ASLayoutElementStyleCallDelegate(ASYogaFlexDirectionProperty); -} -- (void)setDirection:(YGDirection)direction { - _direction.store(direction); - ASLayoutElementStyleCallDelegate(ASYogaDirectionProperty); -} -- (void)setJustifyContent:(ASStackLayoutJustifyContent)justify { - _justifyContent.store(justify); - ASLayoutElementStyleCallDelegate(ASYogaJustifyContentProperty); -} -- (void)setAlignItems:(ASStackLayoutAlignItems)alignItems { - _alignItems.store(alignItems); - ASLayoutElementStyleCallDelegate(ASYogaAlignItemsProperty); -} -- (void)setPositionType:(YGPositionType)positionType { - _positionType.store(positionType); - ASLayoutElementStyleCallDelegate(ASYogaPositionTypeProperty); -} -- (void)setPosition:(ASEdgeInsets)position { - _position.store(position); - ASLayoutElementStyleCallDelegate(ASYogaPositionProperty); -} -- (void)setMargin:(ASEdgeInsets)margin { - _margin.store(margin); - ASLayoutElementStyleCallDelegate(ASYogaMarginProperty); -} -- (void)setPadding:(ASEdgeInsets)padding { - _padding.store(padding); - ASLayoutElementStyleCallDelegate(ASYogaPaddingProperty); -} -- (void)setBorder:(ASEdgeInsets)border { - _border.store(border); - ASLayoutElementStyleCallDelegate(ASYogaBorderProperty); -} -- (void)setAspectRatio:(CGFloat)aspectRatio { - _aspectRatio.store(aspectRatio); - ASLayoutElementStyleCallDelegate(ASYogaAspectRatioProperty); -} - -#endif /* YOGA */ - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASMapNode.h b/submodules/AsyncDisplayKit/Source/ASMapNode.h deleted file mode 100644 index 9ddc2b6ffc..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASMapNode.h +++ /dev/null @@ -1,92 +0,0 @@ -// -// 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 -#import - -#if TARGET_OS_IOS && AS_USE_MAPKIT -#import -#import - -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.

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).

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.

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 mapDelegate; - -/** - * @abstract The annotations to display on the map. - */ -@property (copy) NSArray> *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 annotation, CGPoint *centerOffset); - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASMapNode.h.orig b/submodules/AsyncDisplayKit/Source/ASMapNode.h.orig deleted file mode 100644 index 9ae005aa43..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASMapNode.h.orig +++ /dev/null @@ -1,99 +0,0 @@ -// -// 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 -#import - -#if TARGET_OS_IOS && AS_USE_MAPKIT ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e -#import -#import - -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.

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).

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.

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 mapDelegate; - -/** - * @abstract The annotations to display on the map. - */ -@property (copy) NSArray> *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 annotation, CGPoint *centerOffset); - -@end - -NS_ASSUME_NONNULL_END - -#endif - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASMapNode.mm b/submodules/AsyncDisplayKit/Source/ASMapNode.mm deleted file mode 100644 index 2c5a580b4b..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASMapNode.mm +++ /dev/null @@ -1,442 +0,0 @@ -// -// ASMapNode.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 "ASMapNode.h" - -#if TARGET_OS_IOS && AS_USE_MAPKIT - -#import - -#import -#import -#import -#import -#import "Private/ASInternalHelpers.h" -#import -#import - -@interface ASMapNode() -{ - MKMapSnapshotter *_snapshotter; - BOOL _snapshotAfterLayout; - NSArray *_annotations; -} -@end - -@implementation ASMapNode - -@synthesize needsMapReloadOnBoundsChange = _needsMapReloadOnBoundsChange; -@synthesize mapDelegate = _mapDelegate; -@synthesize options = _options; -@synthesize liveMap = _liveMap; -@synthesize showAnnotationsOptions = _showAnnotationsOptions; - -#pragma mark - Lifecycle -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - self.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); - self.clipsToBounds = YES; - self.userInteractionEnabled = YES; - - _needsMapReloadOnBoundsChange = YES; - _liveMap = NO; - _annotations = @[]; - _showAnnotationsOptions = ASMapNodeShowAnnotationsOptionsIgnored; - return self; -} - -- (void)didLoad -{ - [super didLoad]; - if (self.isLiveMap) { - [self addLiveMap]; - } -} - -- (void)dealloc -{ - [self destroySnapshotter]; -} - -- (void)setLayerBacked:(BOOL)layerBacked -{ - ASDisplayNodeAssert(!self.isLiveMap, @"ASMapNode can not be layer backed whilst .liveMap = YES, set .liveMap = NO to use layer backing."); - [super setLayerBacked:layerBacked]; -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - ASPerformBlockOnMainThread(^{ - if (self.isLiveMap) { - [self addLiveMap]; - } else { - [self takeSnapshot]; - } - }); -} - -- (void)didExitPreloadState -{ - [super didExitPreloadState]; - ASPerformBlockOnMainThread(^{ - if (self.isLiveMap) { - [self removeLiveMap]; - } - }); -} - -#pragma mark - Settings - -- (BOOL)isLiveMap -{ - ASLockScopeSelf(); - return _liveMap; -} - -- (void)setLiveMap:(BOOL)liveMap -{ - ASDisplayNodeAssert(!self.isLayerBacked, @"ASMapNode can not use the interactive map feature whilst .isLayerBacked = YES, set .layerBacked = NO to use the interactive map feature."); - ASLockScopeSelf(); - if (liveMap == _liveMap) { - return; - } - _liveMap = liveMap; - if (self.nodeLoaded) { - liveMap ? [self addLiveMap] : [self removeLiveMap]; - } -} - -- (BOOL)needsMapReloadOnBoundsChange -{ - ASLockScopeSelf(); - return _needsMapReloadOnBoundsChange; -} - -- (void)setNeedsMapReloadOnBoundsChange:(BOOL)needsMapReloadOnBoundsChange -{ - ASLockScopeSelf(); - _needsMapReloadOnBoundsChange = needsMapReloadOnBoundsChange; -} - -- (MKMapSnapshotOptions *)options -{ - ASLockScopeSelf(); - if (!_options) { - _options = [[MKMapSnapshotOptions alloc] init]; - _options.region = MKCoordinateRegionForMapRect(MKMapRectWorld); - CGSize calculatedSize = self.calculatedSize; - if (!CGSizeEqualToSize(calculatedSize, CGSizeZero)) { - _options.size = calculatedSize; - } - } - return _options; -} - -- (void)setOptions:(MKMapSnapshotOptions *)options -{ - ASLockScopeSelf(); - if (!_options || ![options isEqual:_options]) { - _options = options; - if (self.isLiveMap) { - [self applySnapshotOptions]; - } else if (_snapshotter) { - [self destroySnapshotter]; - [self takeSnapshot]; - } - } -} - -- (MKCoordinateRegion)region -{ - return self.options.region; -} - -- (void)setRegion:(MKCoordinateRegion)region -{ - MKMapSnapshotOptions * options = [self.options copy]; - options.region = region; - self.options = options; -} - -- (id)mapDelegate -{ - return ASLockedSelf(_mapDelegate); -} - -- (void)setMapDelegate:(id)mapDelegate { - ASLockScopeSelf(); - _mapDelegate = mapDelegate; - - if (_mapView) { - ASDisplayNodeAssertMainThread(); - _mapView.delegate = mapDelegate; - } -} - -#pragma mark - Snapshotter - -- (void)takeSnapshot -{ - // If our size is zero, we want to avoid calling a default sized snapshot. Set _snapshotAfterLayout to YES - // so if layout changes in the future, we'll try snapshotting again. - ASLayout *layout = self.calculatedLayout; - if (layout == nil || CGSizeEqualToSize(CGSizeZero, layout.size)) { - _snapshotAfterLayout = YES; - return; - } - - _snapshotAfterLayout = NO; - - if (!_snapshotter) { - [self setUpSnapshotter]; - } - - if (_snapshotter.isLoading) { - return; - } - - __weak __typeof__(self) weakSelf = self; - [_snapshotter startWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) - completionHandler:^(MKMapSnapshot *snapshot, NSError *error) { - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - if (!error) { - UIImage *image = snapshot.image; - NSArray *annotations = strongSelf.annotations; - if (annotations.count > 0) { - // Only create a graphics context if we have annotations to draw. - // The MKMapSnapshotter is currently not capable of rendering annotations automatically. - - CGRect finalImageRect = CGRectMake(0, 0, image.size.width, image.size.height); - - ASGraphicsBeginImageContextWithOptions(image.size, YES, image.scale); - [image drawAtPoint:CGPointZero]; - - UIImage *pinImage; - CGPoint pinCenterOffset = CGPointZero; - - // Get a standard annotation view pin if there is no custom annotation block. - if (!strongSelf.imageForStaticMapAnnotationBlock) { - pinImage = [strongSelf.class defaultPinImageWithCenterOffset:&pinCenterOffset]; - } - - for (id annotation in annotations) { - if (strongSelf.imageForStaticMapAnnotationBlock) { - // Get custom annotation image from custom annotation block. - pinImage = strongSelf.imageForStaticMapAnnotationBlock(annotation, &pinCenterOffset); - if (!pinImage) { - // just for case block returned nil, which can happen - pinImage = [strongSelf.class defaultPinImageWithCenterOffset:&pinCenterOffset]; - } - } - - CGPoint point = [snapshot pointForCoordinate:annotation.coordinate]; - if (CGRectContainsPoint(finalImageRect, point)) { - CGSize pinSize = pinImage.size; - point.x -= pinSize.width / 2.0; - point.y -= pinSize.height / 2.0; - point.x += pinCenterOffset.x; - point.y += pinCenterOffset.y; - [pinImage drawAtPoint:point]; - } - } - - image = ASGraphicsGetImageAndEndCurrentContext(); - } - - strongSelf.image = image; - } - }]; -} - -+ (UIImage *)defaultPinImageWithCenterOffset:(CGPoint *)centerOffset NS_RETURNS_RETAINED -{ - static MKAnnotationView *pin; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; - }); - *centerOffset = pin.centerOffset; - return pin.image; -} - -- (void)setUpSnapshotter -{ - _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options]; -} - -- (void)destroySnapshotter -{ - [_snapshotter cancel]; - _snapshotter = nil; -} - -- (void)applySnapshotOptions -{ - MKMapSnapshotOptions *options = self.options; - [_mapView setCamera:options.camera animated:YES]; - [_mapView setRegion:options.region animated:YES]; - [_mapView setMapType:options.mapType]; - _mapView.showsBuildings = options.showsBuildings; - _mapView.showsPointsOfInterest = options.showsPointsOfInterest; -} - -#pragma mark - Actions -- (void)addLiveMap -{ - ASDisplayNodeAssertMainThread(); - if (!_mapView) { - __weak ASMapNode *weakSelf = self; - _mapView = [[MKMapView alloc] initWithFrame:CGRectZero]; - _mapView.delegate = weakSelf.mapDelegate; - [weakSelf applySnapshotOptions]; - [_mapView addAnnotations:_annotations]; - [weakSelf setNeedsLayout]; - [weakSelf.view addSubview:_mapView]; - - ASMapNodeShowAnnotationsOptions showAnnotationsOptions = self.showAnnotationsOptions; - if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { - BOOL const animated = showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsAnimated; - [_mapView showAnnotations:_mapView.annotations animated:animated]; - } - } -} - -- (void)removeLiveMap -{ - [_mapView removeFromSuperview]; - _mapView = nil; -} - -- (NSArray *)annotations -{ - ASLockScopeSelf(); - return _annotations; -} - -- (void)setAnnotations:(NSArray *)annotations -{ - annotations = [annotations copy] ? : @[]; - - ASLockScopeSelf(); - _annotations = annotations; - ASMapNodeShowAnnotationsOptions showAnnotationsOptions = self.showAnnotationsOptions; - if (self.isLiveMap) { - [_mapView removeAnnotations:_mapView.annotations]; - [_mapView addAnnotations:annotations]; - - if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { - BOOL const animated = showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsAnimated; - [_mapView showAnnotations:_mapView.annotations animated:animated]; - } - } else { - if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { - self.region = [self regionToFitAnnotations:annotations]; - } - else { - [self takeSnapshot]; - } - } -} - -- (MKCoordinateRegion)regionToFitAnnotations:(NSArray> *)annotations -{ - if([annotations count] == 0) - return MKCoordinateRegionForMapRect(MKMapRectWorld); - - CLLocationCoordinate2D topLeftCoord = CLLocationCoordinate2DMake(-90, 180); - CLLocationCoordinate2D bottomRightCoord = CLLocationCoordinate2DMake(90, -180); - - for (id annotation in annotations) { - topLeftCoord = CLLocationCoordinate2DMake(std::fmax(topLeftCoord.latitude, annotation.coordinate.latitude), - std::fmin(topLeftCoord.longitude, annotation.coordinate.longitude)); - bottomRightCoord = CLLocationCoordinate2DMake(std::fmin(bottomRightCoord.latitude, annotation.coordinate.latitude), - std::fmax(bottomRightCoord.longitude, annotation.coordinate.longitude)); - } - - MKCoordinateRegion region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.5, - topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.5), - MKCoordinateSpanMake(std::fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 2, - std::fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 2)); - - return region; -} - --(ASMapNodeShowAnnotationsOptions)showAnnotationsOptions { - return ASLockedSelf(_showAnnotationsOptions); -} - --(void)setShowAnnotationsOptions:(ASMapNodeShowAnnotationsOptions)showAnnotationsOptions { - ASLockScopeSelf(); - _showAnnotationsOptions = showAnnotationsOptions; -} - -#pragma mark - Layout -- (void)setSnapshotSizeWithReloadIfNeeded:(CGSize)snapshotSize -{ - if (snapshotSize.height > 0 && snapshotSize.width > 0 && !CGSizeEqualToSize(self.options.size, snapshotSize)) { - _options.size = snapshotSize; - if (_snapshotter) { - [self destroySnapshotter]; - [self takeSnapshot]; - } - } -} - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - // FIXME: Need a better way to allow maps to take up the right amount of space in a layout (sizeRange, etc) - // These fallbacks protect against inheriting a constrainedSize that contains a CGFLOAT_MAX value. - if (!ASIsCGSizeValidForLayout(constrainedSize)) { - //ASDisplayNodeAssert(NO, @"Invalid width or height in ASMapNode"); - constrainedSize = CGSizeZero; - } - [self setSnapshotSizeWithReloadIfNeeded:constrainedSize]; - return constrainedSize; -} - -- (void)calculatedLayoutDidChange -{ - [super calculatedLayoutDidChange]; - - if (_snapshotAfterLayout) { - [self takeSnapshot]; - } -} - -// -layout isn't usually needed over -layoutSpecThatFits, but this way we can avoid a needless node wrapper for MKMapView. -- (void)layout -{ - [super layout]; - if (self.isLiveMap) { - _mapView.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height); - } else { - // If our bounds.size is different from our current snapshot size, then let's request a new image from MKMapSnapshotter. - if (_needsMapReloadOnBoundsChange) { - [self setSnapshotSizeWithReloadIfNeeded:self.bounds.size]; - // FIXME: Adding a check for Preload here seems to cause intermittent map load failures, but shouldn't. - // if (ASInterfaceStateIncludesPreload(self.interfaceState)) { - } - } -} - -- (BOOL)supportsLayerBacking -{ - return NO; -} - -@end -#endif // TARGET_OS_IOS && AS_USE_MAPKIT diff --git a/submodules/AsyncDisplayKit/Source/ASMapNode.mm.orig b/submodules/AsyncDisplayKit/Source/ASMapNode.mm.orig deleted file mode 100644 index 1014f2f32c..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASMapNode.mm.orig +++ /dev/null @@ -1,456 +0,0 @@ -// -// ASMapNode.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 -// - -<<<<<<< HEAD -#ifndef MINIMAL_ASDK - -#import - -#if TARGET_OS_IOS -======= ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e -#import - -#if TARGET_OS_IOS && AS_USE_MAPKIT - -#import - -#import -#import -#import -#import -#import -#import -#import - -@interface ASMapNode() -{ - MKMapSnapshotter *_snapshotter; - BOOL _snapshotAfterLayout; - NSArray *_annotations; -} -@end - -@implementation ASMapNode - -@synthesize needsMapReloadOnBoundsChange = _needsMapReloadOnBoundsChange; -@synthesize mapDelegate = _mapDelegate; -@synthesize options = _options; -@synthesize liveMap = _liveMap; -@synthesize showAnnotationsOptions = _showAnnotationsOptions; - -#pragma mark - Lifecycle -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - self.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); - self.clipsToBounds = YES; - self.userInteractionEnabled = YES; - - _needsMapReloadOnBoundsChange = YES; - _liveMap = NO; - _annotations = @[]; - _showAnnotationsOptions = ASMapNodeShowAnnotationsOptionsIgnored; - return self; -} - -- (void)didLoad -{ - [super didLoad]; - if (self.isLiveMap) { - [self addLiveMap]; - } -} - -- (void)dealloc -{ - [self destroySnapshotter]; -} - -- (void)setLayerBacked:(BOOL)layerBacked -{ - ASDisplayNodeAssert(!self.isLiveMap, @"ASMapNode can not be layer backed whilst .liveMap = YES, set .liveMap = NO to use layer backing."); - [super setLayerBacked:layerBacked]; -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - ASPerformBlockOnMainThread(^{ - if (self.isLiveMap) { - [self addLiveMap]; - } else { - [self takeSnapshot]; - } - }); -} - -- (void)didExitPreloadState -{ - [super didExitPreloadState]; - ASPerformBlockOnMainThread(^{ - if (self.isLiveMap) { - [self removeLiveMap]; - } - }); -} - -#pragma mark - Settings - -- (BOOL)isLiveMap -{ - ASLockScopeSelf(); - return _liveMap; -} - -- (void)setLiveMap:(BOOL)liveMap -{ - ASDisplayNodeAssert(!self.isLayerBacked, @"ASMapNode can not use the interactive map feature whilst .isLayerBacked = YES, set .layerBacked = NO to use the interactive map feature."); - ASLockScopeSelf(); - if (liveMap == _liveMap) { - return; - } - _liveMap = liveMap; - if (self.nodeLoaded) { - liveMap ? [self addLiveMap] : [self removeLiveMap]; - } -} - -- (BOOL)needsMapReloadOnBoundsChange -{ - ASLockScopeSelf(); - return _needsMapReloadOnBoundsChange; -} - -- (void)setNeedsMapReloadOnBoundsChange:(BOOL)needsMapReloadOnBoundsChange -{ - ASLockScopeSelf(); - _needsMapReloadOnBoundsChange = needsMapReloadOnBoundsChange; -} - -- (MKMapSnapshotOptions *)options -{ - ASLockScopeSelf(); - if (!_options) { - _options = [[MKMapSnapshotOptions alloc] init]; - _options.region = MKCoordinateRegionForMapRect(MKMapRectWorld); - CGSize calculatedSize = self.calculatedSize; - if (!CGSizeEqualToSize(calculatedSize, CGSizeZero)) { - _options.size = calculatedSize; - } - } - return _options; -} - -- (void)setOptions:(MKMapSnapshotOptions *)options -{ - ASLockScopeSelf(); - if (!_options || ![options isEqual:_options]) { - _options = options; - if (self.isLiveMap) { - [self applySnapshotOptions]; - } else if (_snapshotter) { - [self destroySnapshotter]; - [self takeSnapshot]; - } - } -} - -- (MKCoordinateRegion)region -{ - return self.options.region; -} - -- (void)setRegion:(MKCoordinateRegion)region -{ - MKMapSnapshotOptions * options = [self.options copy]; - options.region = region; - self.options = options; -} - -- (id)mapDelegate -{ - return ASLockedSelf(_mapDelegate); -} - -- (void)setMapDelegate:(id)mapDelegate { - ASLockScopeSelf(); - _mapDelegate = mapDelegate; - - if (_mapView) { - ASDisplayNodeAssertMainThread(); - _mapView.delegate = mapDelegate; - } -} - -#pragma mark - Snapshotter - -- (void)takeSnapshot -{ - // If our size is zero, we want to avoid calling a default sized snapshot. Set _snapshotAfterLayout to YES - // so if layout changes in the future, we'll try snapshotting again. - ASLayout *layout = self.calculatedLayout; - if (layout == nil || CGSizeEqualToSize(CGSizeZero, layout.size)) { - _snapshotAfterLayout = YES; - return; - } - - _snapshotAfterLayout = NO; - - if (!_snapshotter) { - [self setUpSnapshotter]; - } - - if (_snapshotter.isLoading) { - return; - } - - __weak __typeof__(self) weakSelf = self; - [_snapshotter startWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) - completionHandler:^(MKMapSnapshot *snapshot, NSError *error) { - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - if (!error) { - UIImage *image = snapshot.image; - NSArray *annotations = strongSelf.annotations; - if (annotations.count > 0) { - // Only create a graphics context if we have annotations to draw. - // The MKMapSnapshotter is currently not capable of rendering annotations automatically. - - CGRect finalImageRect = CGRectMake(0, 0, image.size.width, image.size.height); - - ASGraphicsBeginImageContextWithOptions(image.size, YES, image.scale); - [image drawAtPoint:CGPointZero]; - - UIImage *pinImage; - CGPoint pinCenterOffset = CGPointZero; - - // Get a standard annotation view pin if there is no custom annotation block. - if (!strongSelf.imageForStaticMapAnnotationBlock) { - pinImage = [strongSelf.class defaultPinImageWithCenterOffset:&pinCenterOffset]; - } - - for (id annotation in annotations) { - if (strongSelf.imageForStaticMapAnnotationBlock) { - // Get custom annotation image from custom annotation block. - pinImage = strongSelf.imageForStaticMapAnnotationBlock(annotation, &pinCenterOffset); - if (!pinImage) { - // just for case block returned nil, which can happen - pinImage = [strongSelf.class defaultPinImageWithCenterOffset:&pinCenterOffset]; - } - } - - CGPoint point = [snapshot pointForCoordinate:annotation.coordinate]; - if (CGRectContainsPoint(finalImageRect, point)) { - CGSize pinSize = pinImage.size; - point.x -= pinSize.width / 2.0; - point.y -= pinSize.height / 2.0; - point.x += pinCenterOffset.x; - point.y += pinCenterOffset.y; - [pinImage drawAtPoint:point]; - } - } - - image = ASGraphicsGetImageAndEndCurrentContext(); - } - - strongSelf.image = image; - } - }]; -} - -+ (UIImage *)defaultPinImageWithCenterOffset:(CGPoint *)centerOffset NS_RETURNS_RETAINED -{ - static MKAnnotationView *pin; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; - }); - *centerOffset = pin.centerOffset; - return pin.image; -} - -- (void)setUpSnapshotter -{ - _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options]; -} - -- (void)destroySnapshotter -{ - [_snapshotter cancel]; - _snapshotter = nil; -} - -- (void)applySnapshotOptions -{ - MKMapSnapshotOptions *options = self.options; - [_mapView setCamera:options.camera animated:YES]; - [_mapView setRegion:options.region animated:YES]; - [_mapView setMapType:options.mapType]; - _mapView.showsBuildings = options.showsBuildings; - _mapView.showsPointsOfInterest = options.showsPointsOfInterest; -} - -#pragma mark - Actions -- (void)addLiveMap -{ - ASDisplayNodeAssertMainThread(); - if (!_mapView) { - __weak ASMapNode *weakSelf = self; - _mapView = [[MKMapView alloc] initWithFrame:CGRectZero]; - _mapView.delegate = weakSelf.mapDelegate; - [weakSelf applySnapshotOptions]; - [_mapView addAnnotations:_annotations]; - [weakSelf setNeedsLayout]; - [weakSelf.view addSubview:_mapView]; - - ASMapNodeShowAnnotationsOptions showAnnotationsOptions = self.showAnnotationsOptions; - if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { - BOOL const animated = showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsAnimated; - [_mapView showAnnotations:_mapView.annotations animated:animated]; - } - } -} - -- (void)removeLiveMap -{ - [_mapView removeFromSuperview]; - _mapView = nil; -} - -- (NSArray *)annotations -{ - ASLockScopeSelf(); - return _annotations; -} - -- (void)setAnnotations:(NSArray *)annotations -{ - annotations = [annotations copy] ? : @[]; - - ASLockScopeSelf(); - _annotations = annotations; - ASMapNodeShowAnnotationsOptions showAnnotationsOptions = self.showAnnotationsOptions; - if (self.isLiveMap) { - [_mapView removeAnnotations:_mapView.annotations]; - [_mapView addAnnotations:annotations]; - - if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { - BOOL const animated = showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsAnimated; - [_mapView showAnnotations:_mapView.annotations animated:animated]; - } - } else { - if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { - self.region = [self regionToFitAnnotations:annotations]; - } - else { - [self takeSnapshot]; - } - } -} - -- (MKCoordinateRegion)regionToFitAnnotations:(NSArray> *)annotations -{ - if([annotations count] == 0) - return MKCoordinateRegionForMapRect(MKMapRectWorld); - - CLLocationCoordinate2D topLeftCoord = CLLocationCoordinate2DMake(-90, 180); - CLLocationCoordinate2D bottomRightCoord = CLLocationCoordinate2DMake(90, -180); - - for (id annotation in annotations) { - topLeftCoord = CLLocationCoordinate2DMake(std::fmax(topLeftCoord.latitude, annotation.coordinate.latitude), - std::fmin(topLeftCoord.longitude, annotation.coordinate.longitude)); - bottomRightCoord = CLLocationCoordinate2DMake(std::fmin(bottomRightCoord.latitude, annotation.coordinate.latitude), - std::fmax(bottomRightCoord.longitude, annotation.coordinate.longitude)); - } - - MKCoordinateRegion region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.5, - topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.5), - MKCoordinateSpanMake(std::fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 2, - std::fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 2)); - - return region; -} - --(ASMapNodeShowAnnotationsOptions)showAnnotationsOptions { - return ASLockedSelf(_showAnnotationsOptions); -} - --(void)setShowAnnotationsOptions:(ASMapNodeShowAnnotationsOptions)showAnnotationsOptions { - ASLockScopeSelf(); - _showAnnotationsOptions = showAnnotationsOptions; -} - -#pragma mark - Layout -- (void)setSnapshotSizeWithReloadIfNeeded:(CGSize)snapshotSize -{ - if (snapshotSize.height > 0 && snapshotSize.width > 0 && !CGSizeEqualToSize(self.options.size, snapshotSize)) { - _options.size = snapshotSize; - if (_snapshotter) { - [self destroySnapshotter]; - [self takeSnapshot]; - } - } -} - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - // FIXME: Need a better way to allow maps to take up the right amount of space in a layout (sizeRange, etc) - // These fallbacks protect against inheriting a constrainedSize that contains a CGFLOAT_MAX value. - if (!ASIsCGSizeValidForLayout(constrainedSize)) { - //ASDisplayNodeAssert(NO, @"Invalid width or height in ASMapNode"); - constrainedSize = CGSizeZero; - } - [self setSnapshotSizeWithReloadIfNeeded:constrainedSize]; - return constrainedSize; -} - -- (void)calculatedLayoutDidChange -{ - [super calculatedLayoutDidChange]; - - if (_snapshotAfterLayout) { - [self takeSnapshot]; - } -} - -// -layout isn't usually needed over -layoutSpecThatFits, but this way we can avoid a needless node wrapper for MKMapView. -- (void)layout -{ - [super layout]; - if (self.isLiveMap) { - _mapView.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height); - } else { - // If our bounds.size is different from our current snapshot size, then let's request a new image from MKMapSnapshotter. - if (_needsMapReloadOnBoundsChange) { - [self setSnapshotSizeWithReloadIfNeeded:self.bounds.size]; - // FIXME: Adding a check for Preload here seems to cause intermittent map load failures, but shouldn't. - // if (ASInterfaceStateIncludesPreload(self.interfaceState)) { - } - } -} - -- (BOOL)supportsLayerBacking -{ - return NO; -} - -@end -<<<<<<< HEAD -#endif - -#endif -======= -#endif // TARGET_OS_IOS && AS_USE_MAPKIT ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e diff --git a/submodules/AsyncDisplayKit/Source/ASMultiplexImageNode.h b/submodules/AsyncDisplayKit/Source/ASMultiplexImageNode.h deleted file mode 100644 index 42342422a9..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASMultiplexImageNode.h +++ /dev/null @@ -1,271 +0,0 @@ -// -// ASMultiplexImageNode.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 -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASMultiplexImageNodeDelegate; -@protocol ASMultiplexImageNodeDataSource; - -typedef id ASImageIdentifier; - -AS_EXTERN NSString *const ASMultiplexImageNodeErrorDomain; - -/** - * ASMultiplexImageNode error codes. - */ -typedef NS_ENUM(NSUInteger, ASMultiplexImageNodeErrorCode) { - /** - * Indicates that the data source didn't provide a source for an image identifier. - */ - ASMultiplexImageNodeErrorCodeNoSourceForImage = 0, - - /** - * Indicates that the best image identifier changed before a download for a worse identifier began. - */ - ASMultiplexImageNodeErrorCodeBestImageIdentifierChanged, - - /** - * Indicates that the Photos framework returned no image and no error. - * This may happen if the image is in iCloud and the user did not specify `allowsNetworkAccess` - * in their image request. - */ - ASMultiplexImageNodeErrorCodePhotosImageManagerFailedWithoutError, - - /** - * Indicates that the image node could not retrieve the PHAsset for a given asset identifier. - * This typically means that the user has not given Photos framework permissions yet or the asset - * has been removed from the device. - */ - ASMultiplexImageNodeErrorCodePHAssetIsUnavailable -}; - - -/** - * @abstract ASMultiplexImageNode is an image node that can load and display multiple versions of an image. For - * example, it can display a low-resolution version of an image while the high-resolution version is loading. - * - * @discussion ASMultiplexImageNode begins loading images when its resource can either return a UIImage directly, or a URL the image node should load. - */ -@interface ASMultiplexImageNode : ASImageNode - -/** - * @abstract The designated initializer. - * @param cache The object that implements a cache of images for the image node. - * @param downloader The object that implements image downloading for the image node. - * @discussion If `cache` is nil, the receiver will not attempt to retrieve images from a cache before downloading them. - * @return An initialized ASMultiplexImageNode. - */ -- (instancetype)initWithCache:(nullable id)cache downloader:(nullable id)downloader NS_DESIGNATED_INITIALIZER; - -/** - * @abstract The delegate, which must conform to the protocol. - */ -@property (nonatomic, weak) id delegate; - -/** - * @abstract The data source, which must conform to the protocol. - * @discussion This value is required for ASMultiplexImageNode to load images. - */ -@property (nonatomic, weak) id dataSource; - -/** - * @abstract Whether the receiver should download more than just its highest-quality image. Defaults to NO. - * - * @discussion ASMultiplexImageNode immediately loads and displays the first image specified in (its - * highest-quality image). If that image is not immediately available or cached, the node can download and display - * lesser-quality images. Set `downloadsIntermediateImages` to YES to enable this behaviour. - */ -@property (nonatomic) BOOL downloadsIntermediateImages; - -/** - * @abstract An array of identifiers representing various versions of an image for ASMultiplexImageNode to display. - * - * @discussion An identifier can be any object that conforms to NSObject and NSCopying. The array should be in - * decreasing order of image quality -- that is, the first identifier in the array represents the best version. - * - * @see for more information on the image loading process. - */ -@property (nonatomic, copy) NSArray *imageIdentifiers; - -/** - * @abstract Notify the receiver SSAA that its data source has new UIImages or NSURLs available for . - * - * @discussion If a higher-quality image than is currently displayed is now available, it will be loaded. - */ -- (void)reloadImageIdentifierSources; - -/** - * @abstract The identifier for the last image that the receiver loaded, or nil. - * - * @discussion This value may differ from if the image hasn't yet been displayed. - */ -@property (nullable, nonatomic, readonly) ASImageIdentifier loadedImageIdentifier; - -/** - * @abstract The identifier for the image that the receiver is currently displaying, or nil. - */ -@property (nullable, nonatomic, readonly) ASImageIdentifier displayedImageIdentifier; - -/** - * @abstract If the downloader implements progressive image rendering and this value is YES progressive renders of the - * image will be displayed as the image downloads. Regardless of this properties value, progress renders will - * only occur when the node is visible. Defaults to YES. - */ -@property (nonatomic) BOOL shouldRenderProgressImages; - -/** - * @abstract The image manager that this image node should use when requesting images from the Photos framework. If this is `nil` (the default), then `PHImageManager.defaultManager` is used. - - * @see `+[NSURL URLWithAssetLocalIdentifier:targetSize:contentMode:options:]` below. - */ -@property (nullable, nonatomic) PHImageManager *imageManager API_AVAILABLE(ios(8.0), tvos(10.0)); -@end - - -#pragma mark - -/** - * The methods declared by the ASMultiplexImageNodeDelegate protocol allow the adopting delegate to respond to - * notifications such as began, progressed and finished downloading, updated and displayed an image. - */ -@protocol ASMultiplexImageNodeDelegate - -@optional -/** - * @abstract Notification that the image node began downloading an image. - * @param imageNode The sender. - * @param imageIdentifier The identifier for the image that is downloading. - */ -- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode didStartDownloadOfImageWithIdentifier:(id)imageIdentifier; - -/** - * @abstract Notification that the image node's download progressed. - * @param imageNode The sender. - * @param downloadProgress The progress of the download. Value is between 0.0 and 1.0. - * @param imageIdentifier The identifier for the image that is downloading. - */ -- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode - didUpdateDownloadProgress:(CGFloat)downloadProgress - forImageWithIdentifier:(ASImageIdentifier)imageIdentifier; - -/** - * @abstract Notification that the image node's download has finished. - * @param imageNode The sender. - * @param imageIdentifier The identifier for the image that finished downloading. - * @param error The error that occurred while downloading, if one occurred; nil otherwise. - */ -- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode -didFinishDownloadingImageWithIdentifier:(ASImageIdentifier)imageIdentifier - error:(nullable NSError *)error; - -/** - * @abstract Notification that the image node's image was updated. - * @param imageNode The sender. - * @param image The new image, ready for display. - * @param imageIdentifier The identifier for `image`. - * @param previousImage The old, previously-loaded image. - * @param previousImageIdentifier The identifier for `previousImage`. - * @note This method does not indicate that `image` has been displayed. - * @see <[ASMultiplexImageNodeDelegate multiplexImageNode:didDisplayUpdatedImage:withIdentifier:]>. - */ -- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode - didUpdateImage:(nullable UIImage *)image - withIdentifier:(nullable ASImageIdentifier)imageIdentifier - fromImage:(nullable UIImage *)previousImage - withIdentifier:(nullable ASImageIdentifier)previousImageIdentifier; - -/** - * @abstract Notification that the image node displayed a new image. - * @param imageNode The sender. - * @param image The new image, now being displayed. - * @param imageIdentifier The identifier for `image`. - * @discussion This method is only called when `image` changes, and not on subsequent redisplays of the same image. - */ -- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode - didDisplayUpdatedImage:(nullable UIImage *)image - withIdentifier:(nullable ASImageIdentifier)imageIdentifier; - -/** - * @abstract Notification that the image node finished displaying an image. - * @param imageNode The sender. - * @discussion This method is called every time an image is displayed, whether or not it has changed. - */ -- (void)multiplexImageNodeDidFinishDisplay:(ASMultiplexImageNode *)imageNode; - -@end - - -#pragma mark - -/** - * The ASMultiplexImageNodeDataSource protocol is adopted by an object that provides the multiplex image node, - * for each image identifier, an image or a URL the image node should load. - */ -@protocol ASMultiplexImageNodeDataSource - -@optional -/** - * @abstract An image for the specified identifier. - * @param imageNode The sender. - * @param imageIdentifier The identifier for the image that should be returned. - * @discussion If the image is already available to the data source, this method should be used in lieu of providing the - * URL to the image via -multiplexImageNode:URLForImageIdentifier:. - * @return A UIImage corresponding to `imageIdentifier`, or nil if none is available. - */ -- (nullable UIImage *)multiplexImageNode:(ASMultiplexImageNode *)imageNode imageForImageIdentifier:(ASImageIdentifier)imageIdentifier; - -/** - * @abstract An image URL for the specified identifier. - * @param imageNode The sender. - * @param imageIdentifier The identifier for the image that will be downloaded. - * @discussion Supported URLs include HTTP, HTTPS, AssetsLibrary, and FTP URLs as well as Photos framework URLs (see note). - * - * If the image is already available to the data source, it should be provided via <[ASMultiplexImageNodeDataSource - * multiplexImageNode:imageForImageIdentifier:]> instead. - * @return An NSURL for the image identified by `imageIdentifier`, or nil if none is available. - * @see `+[NSURL URLWithAssetLocalIdentifier:targetSize:contentMode:options:]` below. - */ -- (nullable NSURL *)multiplexImageNode:(ASMultiplexImageNode *)imageNode URLForImageIdentifier:(ASImageIdentifier)imageIdentifier; - -/** - * @abstract A PHAsset for the specific asset local identifier - * @param imageNode The sender. - * @param assetLocalIdentifier The local identifier for a PHAsset that this image node is loading. - * - * @discussion This optional method can improve image performance if your data source already has the PHAsset available. - * If this method is not implemented, or returns nil, the image node will request the asset from the Photos framework. - * @note This method may be called from any thread. - * @return A PHAsset corresponding to `assetLocalIdentifier`, or nil if none is available. - */ -- (nullable PHAsset *)multiplexImageNode:(ASMultiplexImageNode *)imageNode assetForLocalIdentifier:(NSString *)assetLocalIdentifier API_AVAILABLE(ios(8.0), tvos(10.0)); -@end - -#pragma mark - -@interface NSURL (ASPhotosFrameworkURLs) - -/** - * @abstract Create an NSURL that specifies an image from the Photos framework. - * - * @discussion When implementing `-multiplexImageNode:URLForImageIdentifier:`, you can return a URL - * created by this method and the image node will attempt to load the image from the Photos framework. - * @note The `synchronous` flag in `options` is ignored. - * @note The `Opportunistic` delivery mode is not supported and will be treated as `HighQualityFormat`. - */ -+ (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier - targetSize:(CGSize)targetSize - contentMode:(PHImageContentMode)contentMode - options:(PHImageRequestOptions *)options NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT API_AVAILABLE(ios(8.0), tvos(10.0)); - -@end - -NS_ASSUME_NONNULL_END -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASMultiplexImageNode.mm b/submodules/AsyncDisplayKit/Source/ASMultiplexImageNode.mm deleted file mode 100644 index beb76ed868..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASMultiplexImageNode.mm +++ /dev/null @@ -1,962 +0,0 @@ -// -// ASMultiplexImageNode.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 - -#if TARGET_OS_IOS && AS_USE_ASSETS_LIBRARY -#import -#endif - -#import -#import -#import -#import -#import -#import "Private/ASInternalHelpers.h" -#import -#import - -#if AS_USE_PHOTOS -#import -#endif - -#if AS_PIN_REMOTE_IMAGE -#import -#else -#import -#endif - -using AS::MutexLocker; - -NSString *const ASMultiplexImageNodeErrorDomain = @"ASMultiplexImageNodeErrorDomain"; - -#if AS_USE_ASSETS_LIBRARY -static NSString *const kAssetsLibraryURLScheme = @"assets-library"; -#endif - -static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; - -/** - @abstract Signature for the block to be performed after an image has loaded. - @param image The image that was loaded, or nil if no image was loaded. - @param imageIdentifier The identifier of the image that was loaded, or nil if no image was loaded. - @param error An error describing why an image couldn't be loaded, if it failed to load; nil otherwise. - */ -typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdentifier, NSError *error); - -@interface ASMultiplexImageNode () -{ -@private - // Core. - id _cache; - - id _downloader; - struct { - unsigned int downloaderImplementsSetProgress:1; - unsigned int downloaderImplementsSetPriority:1; - unsigned int downloaderImplementsDownloadWithPriority:1; - } _downloaderFlags; - - __weak id _delegate; - struct { - unsigned int downloadStart:1; - unsigned int downloadProgress:1; - unsigned int downloadFinish:1; - unsigned int updatedImageDisplayFinish:1; - unsigned int updatedImage:1; - unsigned int displayFinish:1; - } _delegateFlags; - - __weak id _dataSource; - struct { - unsigned int image:1; - unsigned int URL:1; - unsigned int asset:1; - } _dataSourceFlags; - - // Image flags. - BOOL _downloadsIntermediateImages; // Defaults to NO. - AS::Mutex _imageIdentifiersLock; - NSArray *_imageIdentifiers; - id _loadedImageIdentifier; - id _loadingImageIdentifier; - id _displayedImageIdentifier; - __weak NSOperation *_phImageRequestOperation; - - // Networking. - AS::RecursiveMutex _downloadIdentifierLock; - id _downloadIdentifier; - - // Properties - BOOL _shouldRenderProgressImages; - - //set on init only - BOOL _cacheSupportsClearing; -} - -//! @abstract Read-write redeclaration of property declared in ASMultiplexImageNode.h. -@property (nonatomic, copy) id loadedImageIdentifier; - -//! @abstract The image identifier that's being loaded by _loadNextImageWithCompletion:. -@property (nonatomic, copy) id loadingImageIdentifier; - -/** - @abstract Returns the next image identifier that should be downloaded. - @discussion This method obeys and reflects the value of `downloadsIntermediateImages`. - @result The next image identifier, from `_imageIdentifiers`, that should be downloaded, or nil if no image should be downloaded next. - */ -- (id)_nextImageIdentifierToDownload; - -/** - @abstract Returns the best image that is immediately available from our datasource without downloading or hitting the cache. - @param imageIdentifierOut Upon return, the image identifier for the returned image; nil otherwise. - @discussion This method exclusively uses the data source's -multiplexImageNode:imageForIdentifier: method to return images. It does not fetch from the cache or kick off downloading. - @result The best UIImage available immediately; nil if no image is immediately available. - */ -- (UIImage *)_bestImmediatelyAvailableImageFromDataSource:(id *)imageIdentifierOut; - -/** - @abstract Loads and displays the next image in the receiver's loading sequence. - @discussion This method obeys `downloadsIntermediateImages`. This method has no effect if nothing further should be loaded, as indicated by `_nextImageIdentifierToDownload`. This method will load the next image from the data-source, if possible; otherwise, the session's image cache will be queried for the desired image, and as a last resort, the image will be downloaded. - */ -- (void)_loadNextImage; - -/** - @abstract Fetches the image corresponding to the given imageIdentifier from the given URL from the session's image cache. - @param imageIdentifier The identifier for the image to be fetched. May not be nil. - @param imageURL The URL of the image to fetch. May not be nil. - @param completionBlock The block to be performed when the image has been fetched from the cache, if possible. May not be nil. - @discussion This method queries both the session's in-memory and on-disk caches (with preference for the in-memory cache). - */ -- (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock; - -#if TARGET_OS_IOS && AS_USE_ASSETS_LIBRARY -/** - @abstract Loads the image corresponding to the given assetURL from the device's Assets Library. - @param imageIdentifier The identifier for the image to be loaded. May not be nil. - @param assetURL The assets-library URL (e.g., "assets-library://identifier") of the image to load, from ALAsset. May not be nil. - @param completionBlock The block to be performed when the image has been loaded, if possible. May not be nil. - */ -- (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock; -#endif - -#if AS_USE_PHOTOS -/** - @abstract Loads the image corresponding to the given image request from the Photos framework. - @param imageIdentifier The identifier for the image to be loaded. May not be nil. - @param request The photos image request to load. May not be nil. - @param completionBlock The block to be performed when the image has been loaded, if possible. May not be nil. - */ -- (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock API_AVAILABLE(ios(8.0), tvos(10.0)); -#endif - -/** - @abstract Downloads the image corresponding to the given imageIdentifier from the given URL. - @param imageIdentifier The identifier for the image to be downloaded. May not be nil. - @param imageURL The URL of the image to downloaded. May not be nil. - @param completionBlock The block to be performed when the image has been downloaded, if possible. May not be nil. - */ -- (void)_downloadImageWithIdentifier:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image, NSError *error))completionBlock; - -@end - -@implementation ASMultiplexImageNode - -#pragma mark - Getting Started / Tearing Down -- (instancetype)initWithCache:(id)cache downloader:(id)downloader -{ - if (!(self = [super init])) - return nil; - - _cache = (id)cache; - _downloader = (id)downloader; - - _downloaderFlags.downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)]; - _downloaderFlags.downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)]; - _downloaderFlags.downloaderImplementsDownloadWithPriority = [downloader respondsToSelector:@selector(downloadImageWithURL:priority:callbackQueue:downloadProgress:completion:)]; - - _cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)]; - - _shouldRenderProgressImages = YES; - - self.shouldBypassEnsureDisplay = YES; - - return self; -} - -- (instancetype)init -{ -#if AS_PIN_REMOTE_IMAGE - return [self initWithCache:[ASPINRemoteImageDownloader sharedDownloader] downloader:[ASPINRemoteImageDownloader sharedDownloader]]; -#else - return [self initWithCache:nil downloader:[ASBasicImageDownloader sharedImageDownloader]]; -#endif -} - -- (void)dealloc -{ - [_phImageRequestOperation cancel]; -} - -#pragma mark - ASDisplayNode Overrides - -- (void)clearContents -{ - [super clearContents]; // This actually clears the contents, so we need to do this first for our displayedImageIdentifier to be meaningful. - [self _setDisplayedImageIdentifier:nil withImage:nil]; - - // NOTE: We intentionally do not cancel image downloads until `clearPreloadedData`. -} - -- (void)didExitPreloadState -{ - [super didExitPreloadState]; - - [_phImageRequestOperation cancel]; - - [self _setDownloadIdentifier:nil]; - - if (_cacheSupportsClearing && self.loadedImageIdentifier != nil) { - NSURL *URL = [_dataSource multiplexImageNode:self URLForImageIdentifier:self.loadedImageIdentifier]; - if (URL != nil) { - [_cache clearFetchedImageFromCacheWithURL:URL]; - } - } - - // setting this to nil makes the node fetch images the next time its display starts - _loadedImageIdentifier = nil; - [self _setImage:nil]; -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - - [self _loadImageIdentifiers]; -} - -- (void)displayDidFinish -{ - [super displayDidFinish]; - - // We may now be displaying the loaded identifier, if they're different. - UIImage *displayedImage = self.image; - if (displayedImage) { - if (!ASObjectIsEqual(_displayedImageIdentifier, _loadedImageIdentifier)) - [self _setDisplayedImageIdentifier:_loadedImageIdentifier withImage:displayedImage]; - - // Delegateify - if (_delegateFlags.displayFinish) { - if (ASDisplayNodeThreadIsMain()) - [_delegate multiplexImageNodeDidFinishDisplay:self]; - else { - __weak __typeof__(self) weakSelf = self; - dispatch_async(dispatch_get_main_queue(), ^{ - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - [strongSelf.delegate multiplexImageNodeDidFinishDisplay:strongSelf]; - }); - } - } - } -} - -- (BOOL)placeholderShouldPersist -{ - return (self.image == nil && self.animatedImage == nil && self.imageIdentifiers.count > 0); -} - -/* displayWillStartAsynchronously in ASNetworkImageNode has a very similar implementation. Changes here are likely necessary - in ASNetworkImageNode as well. */ -- (void)displayWillStartAsynchronously:(BOOL)asynchronously -{ - [super displayWillStartAsynchronously:asynchronously]; - [self didEnterPreloadState]; - [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityImminent]; -} - -/* didEnterVisibleState / didExitVisibleState in ASNetworkImageNode has a very similar implementation. Changes here are likely necessary - in ASNetworkImageNode as well. */ -- (void)didEnterVisibleState -{ - [super didEnterVisibleState]; - [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityVisible]; - [self _updateProgressImageBlockOnDownloaderIfNeeded]; -} - -- (void)didExitVisibleState -{ - [super didExitVisibleState]; - [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityPreload]; - [self _updateProgressImageBlockOnDownloaderIfNeeded]; -} - -- (void)didExitDisplayState -{ - [super didExitDisplayState]; - if (ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) { - [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityPreload]; - } -} - -#pragma mark - Core - -- (void)setImage:(UIImage *)image -{ - ASDisplayNodeAssert(NO, @"Setting the image directly on an ASMultiplexImageNode is unsafe. It will be cleared in didExitPreloadRange and will have no way to restore in didEnterPreloadRange"); - super.image = image; -} - -- (void)_setImage:(UIImage *)image -{ - super.image = image; -} - -- (void)setDelegate:(id )delegate -{ - if (_delegate == delegate) - return; - - _delegate = delegate; - _delegateFlags.downloadStart = [_delegate respondsToSelector:@selector(multiplexImageNode:didStartDownloadOfImageWithIdentifier:)]; - _delegateFlags.downloadProgress = [_delegate respondsToSelector:@selector(multiplexImageNode:didUpdateDownloadProgress:forImageWithIdentifier:)]; - _delegateFlags.downloadFinish = [_delegate respondsToSelector:@selector(multiplexImageNode:didFinishDownloadingImageWithIdentifier:error:)]; - _delegateFlags.updatedImageDisplayFinish = [_delegate respondsToSelector:@selector(multiplexImageNode:didDisplayUpdatedImage:withIdentifier:)]; - _delegateFlags.updatedImage = [_delegate respondsToSelector:@selector(multiplexImageNode:didUpdateImage:withIdentifier:fromImage:withIdentifier:)]; - _delegateFlags.displayFinish = [_delegate respondsToSelector:@selector(multiplexImageNodeDidFinishDisplay:)]; -} - - -- (void)setDataSource:(id )dataSource -{ - if (_dataSource == dataSource) - return; - - _dataSource = dataSource; - _dataSourceFlags.image = [_dataSource respondsToSelector:@selector(multiplexImageNode:imageForImageIdentifier:)]; - _dataSourceFlags.URL = [_dataSource respondsToSelector:@selector(multiplexImageNode:URLForImageIdentifier:)]; - if (AS_AVAILABLE_IOS_TVOS(9, 10)) { - _dataSourceFlags.asset = [_dataSource respondsToSelector:@selector(multiplexImageNode:assetForLocalIdentifier:)]; - } -} - - -- (void)setShouldRenderProgressImages:(BOOL)shouldRenderProgressImages -{ - [self lock]; - if (shouldRenderProgressImages == _shouldRenderProgressImages) { - [self unlock]; - return; - } - - _shouldRenderProgressImages = shouldRenderProgressImages; - - [self unlock]; - [self _updateProgressImageBlockOnDownloaderIfNeeded]; -} - -- (BOOL)shouldRenderProgressImages -{ - return ASLockedSelf(_shouldRenderProgressImages); -} - -#pragma mark - - -#pragma mark - - -- (NSArray *)imageIdentifiers -{ - MutexLocker l(_imageIdentifiersLock); - return _imageIdentifiers; -} - -- (void)setImageIdentifiers:(NSArray *)imageIdentifiers -{ - { - MutexLocker l(_imageIdentifiersLock); - if (ASObjectIsEqual(_imageIdentifiers, imageIdentifiers)) { - return; - } - - _imageIdentifiers = [[NSArray alloc] initWithArray:imageIdentifiers copyItems:YES]; - } - - [self setNeedsPreload]; -} - -- (void)reloadImageIdentifierSources -{ - // setting this to nil makes the node think it has not downloaded any images - _loadedImageIdentifier = nil; - [self _loadImageIdentifiers]; -} - -#pragma mark - - - -#pragma mark - Core Internal -- (void)_setDisplayedImageIdentifier:(id)displayedImageIdentifier withImage:(UIImage *)image -{ - ASDisplayNodeAssertMainThread(); - - if (ASObjectIsEqual(_displayedImageIdentifier, displayedImageIdentifier)) { - return; - } - - _displayedImageIdentifier = displayedImageIdentifier; - - // Delegateify. - // Note that we're using the params here instead of self.image and _displayedImageIdentifier because those can change before the async block below executes. - if (_delegateFlags.updatedImageDisplayFinish) { - if (ASDisplayNodeThreadIsMain()) - [_delegate multiplexImageNode:self didDisplayUpdatedImage:image withIdentifier:displayedImageIdentifier]; - else { - __weak __typeof__(self) weakSelf = self; - dispatch_async(dispatch_get_main_queue(), ^{ - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - [strongSelf.delegate multiplexImageNode:strongSelf didDisplayUpdatedImage:image withIdentifier:displayedImageIdentifier]; - }); - } - } -} - -- (void)_setDownloadIdentifier:(id)downloadIdentifier -{ - MutexLocker l(_downloadIdentifierLock); - if (ASObjectIsEqual(downloadIdentifier, _downloadIdentifier)) - return; - - if (_downloadIdentifier) { - [_downloader cancelImageDownloadForIdentifier:_downloadIdentifier]; - } - _downloadIdentifier = downloadIdentifier; -} - -#pragma mark - Image Loading Machinery - -- (void)_loadImageIdentifiers -{ - // Grab the best possible image we can load right now. - id bestImmediatelyAvailableImageIdentifier = nil; - UIImage *bestImmediatelyAvailableImage = [self _bestImmediatelyAvailableImageFromDataSource:&bestImmediatelyAvailableImageIdentifier]; - as_log_verbose(ASImageLoadingLog(), "%@ Best immediately available image identifier is %@", self, bestImmediatelyAvailableImageIdentifier); - - // Load it. This kicks off cache fetching/downloading, as appropriate. - [self _finishedLoadingImage:bestImmediatelyAvailableImage forIdentifier:bestImmediatelyAvailableImageIdentifier error:nil]; -} - -- (UIImage *)_bestImmediatelyAvailableImageFromDataSource:(id *)imageIdentifierOut -{ - MutexLocker l(_imageIdentifiersLock); - - // If we don't have any identifiers to load or don't implement the image DS method, bail. - if ([_imageIdentifiers count] == 0 || !_dataSourceFlags.image) { - return nil; - } - - // Grab the best available image from the data source. - UIImage *existingImage = self.image; - for (id imageIdentifier in _imageIdentifiers) { - // If this image is already loaded, don't request it from the data source again because - // the data source may generate a new instance of UIImage that returns NO for isEqual: - // and we'll end up in an infinite loading loop. - UIImage *image = ASObjectIsEqual(imageIdentifier, _loadedImageIdentifier) ? existingImage : [_dataSource multiplexImageNode:self imageForImageIdentifier:imageIdentifier]; - if (image) { - if (imageIdentifierOut) { - *imageIdentifierOut = imageIdentifier; - } - - return image; - } - } - - return nil; -} - -#pragma mark - - -- (void)_updatePriorityOnDownloaderIfNeededWithDefaultPriority:(ASImageDownloaderPriority)defaultPriority -{ - ASAssertUnlocked(_downloadIdentifierLock); - - if (_downloaderFlags.downloaderImplementsSetPriority) { - // Read our interface state before locking so that we don't lock super while holding our lock. - ASInterfaceState interfaceState = self.interfaceState; - MutexLocker l(_downloadIdentifierLock); - - if (_downloadIdentifier != nil) { - ASImageDownloaderPriority priority = defaultPriority; - if (ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) { - priority = ASImageDownloaderPriorityWithInterfaceState(interfaceState); - } - - [_downloader setPriority:priority withDownloadIdentifier:_downloadIdentifier]; - } - } -} - -- (void)_updateProgressImageBlockOnDownloaderIfNeeded -{ - ASAssertUnlocked(_downloadIdentifierLock); - - BOOL shouldRenderProgressImages = self.shouldRenderProgressImages; - - // Read our interface state before locking so that we don't lock super while holding our lock. - ASInterfaceState interfaceState = self.interfaceState; - MutexLocker l(_downloadIdentifierLock); - - if (!_downloaderFlags.downloaderImplementsSetProgress || _downloadIdentifier == nil) { - return; - } - - ASImageDownloaderProgressImage progress = nil; - if (shouldRenderProgressImages && ASInterfaceStateIncludesVisible(interfaceState)) { - __weak __typeof__(self) weakSelf = self; - progress = ^(UIImage * _Nonnull progressImage, CGFloat progress, id _Nullable downloadIdentifier) { - __typeof__(self) strongSelf = weakSelf; - if (strongSelf == nil) { - return; - } - - MutexLocker l(strongSelf->_downloadIdentifierLock); - //Getting a result back for a different download identifier, download must not have been successfully canceled - if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { - return; - } - [strongSelf _setImage:progressImage]; - }; - } - [_downloader setProgressImageBlock:progress callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:_downloadIdentifier]; -} - -- (void)_clearImage -{ - // 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. - UIImage *image = self.image; - CGSize imageSize = image.size; - BOOL shouldReleaseImageOnBackgroundThread = imageSize.width > kMinReleaseImageOnBackgroundSize.width || - imageSize.height > kMinReleaseImageOnBackgroundSize.height; - [self _setImage:nil]; - if (shouldReleaseImageOnBackgroundThread) { - ASPerformBackgroundDeallocation(&image); - } -} - -#pragma mark - -- (id)_nextImageIdentifierToDownload -{ - MutexLocker l(_imageIdentifiersLock); - - // If we've already loaded the best identifier, we've got nothing else to do. - id bestImageIdentifier = _imageIdentifiers.firstObject; - if (!bestImageIdentifier || ASObjectIsEqual(_loadedImageIdentifier, bestImageIdentifier)) { - return nil; - } - - id nextImageIdentifierToDownload = nil; - - // If we're not supposed to download intermediate images, load the best identifier we've got. - if (!_downloadsIntermediateImages) { - nextImageIdentifierToDownload = bestImageIdentifier; - } - // Otherwise, load progressively. - else { - NSUInteger loadedIndex = [_imageIdentifiers indexOfObject:_loadedImageIdentifier]; - - // If nothing has loaded yet, load the worst identifier. - if (loadedIndex == NSNotFound) { - nextImageIdentifierToDownload = [_imageIdentifiers lastObject]; - } - // Otherwise, load the next best identifier (if there is one) - else if (loadedIndex > 0) { - nextImageIdentifierToDownload = _imageIdentifiers[loadedIndex - 1]; - } - } - - return nextImageIdentifierToDownload; -} - -- (void)_loadNextImage -{ - // Determine the next identifier to load (if any). - id nextImageIdentifier = [self _nextImageIdentifierToDownload]; - if (!nextImageIdentifier) { - [self _finishedLoadingImage:nil forIdentifier:nil error:nil]; - return; - } - - as_activity_create_for_scope("Load next image for multiplex image node"); - as_log_verbose(ASImageLoadingLog(), "Loading image for %@ ident: %@", self, nextImageIdentifier); - self.loadingImageIdentifier = nextImageIdentifier; - - __weak __typeof__(self) weakSelf = self; - ASMultiplexImageLoadCompletionBlock finishedLoadingBlock = ^(UIImage *image, id imageIdentifier, NSError *error) { - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - - // Only nil out the loading identifier if the loading identifier hasn't changed. - if (ASObjectIsEqual(strongSelf.loadingImageIdentifier, nextImageIdentifier)) { - strongSelf.loadingImageIdentifier = nil; - } - [strongSelf _finishedLoadingImage:image forIdentifier:imageIdentifier error:error]; - }; - - // Ask our data-source if it's got this image. - if (_dataSourceFlags.image) { - UIImage *image = [_dataSource multiplexImageNode:self imageForImageIdentifier:nextImageIdentifier]; - if (image) { - as_log_verbose(ASImageLoadingLog(), "Acquired image from data source for %@ ident: %@", self, nextImageIdentifier); - finishedLoadingBlock(image, nextImageIdentifier, nil); - return; - } - } - - NSURL *nextImageURL = (_dataSourceFlags.URL) ? [_dataSource multiplexImageNode:self URLForImageIdentifier:nextImageIdentifier] : nil; - // If we fail to get a URL for the image, we have no source and can't proceed. - if (!nextImageURL) { - as_log_error(ASImageLoadingLog(), "Could not acquire URL %@ ident: (%@)", self, nextImageIdentifier); - finishedLoadingBlock(nil, nil, [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodeNoSourceForImage userInfo:nil]); - return; - } - -#if TARGET_OS_IOS && AS_USE_ASSETS_LIBRARY - // If it's an assets-library URL, we need to fetch it from the assets library. - if ([[nextImageURL scheme] isEqualToString:kAssetsLibraryURLScheme]) { - // Load the asset. - [self _loadALAssetWithIdentifier:nextImageIdentifier URL:nextImageURL completion:^(UIImage *downloadedImage, NSError *error) { - as_log_verbose(ASImageLoadingLog(), "Acquired image from assets library for %@ %@", weakSelf, nextImageIdentifier); - finishedLoadingBlock(downloadedImage, nextImageIdentifier, error); - }]; - - return; - } -#endif - -#if AS_USE_PHOTOS - if (AS_AVAILABLE_IOS_TVOS(9, 10)) { - // Likewise, if it's a Photos asset, we need to fetch it accordingly. - if (ASPhotosFrameworkImageRequest *request = [ASPhotosFrameworkImageRequest requestWithURL:nextImageURL]) { - [self _loadPHAssetWithRequest:request identifier:nextImageIdentifier completion:^(UIImage *image, NSError *error) { - as_log_verbose(ASImageLoadingLog(), "Acquired image from Photos for %@ %@", weakSelf, nextImageIdentifier); - finishedLoadingBlock(image, nextImageIdentifier, error); - }]; - - return; - } - } -#endif - - // Otherwise, it's a web URL that we can download. - // First, check the cache. - [self _fetchImageWithIdentifierFromCache:nextImageIdentifier URL:nextImageURL completion:^(UIImage *imageFromCache) { - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - - // If we had a cache-hit, we're done. - if (imageFromCache) { - as_log_verbose(ASImageLoadingLog(), "Acquired image from cache for %@ id: %@ img: %@", strongSelf, nextImageIdentifier, imageFromCache); - finishedLoadingBlock(imageFromCache, nextImageIdentifier, nil); - return; - } - - // If the next image to load has changed, bail. - if (!ASObjectIsEqual([strongSelf _nextImageIdentifierToDownload], nextImageIdentifier)) { - finishedLoadingBlock(nil, nil, [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodeBestImageIdentifierChanged userInfo:nil]); - return; - } - - // Otherwise, we've got to download it. - [strongSelf _downloadImageWithIdentifier:nextImageIdentifier URL:nextImageURL completion:^(UIImage *downloadedImage, NSError *error) { - __typeof__(self) strongSelf = weakSelf; - if (downloadedImage) { - as_log_verbose(ASImageLoadingLog(), "Acquired image from download for %@ id: %@ img: %@", strongSelf, nextImageIdentifier, downloadedImage); - } else { - as_log_error(ASImageLoadingLog(), "Error downloading image for %@ id: %@ err: %@", strongSelf, nextImageIdentifier, error); - } - finishedLoadingBlock(downloadedImage, nextImageIdentifier, error); - }]; - }]; -} -#if TARGET_OS_IOS && AS_USE_ASSETS_LIBRARY -- (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock -{ - ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); - ASDisplayNodeAssertNotNil(assetURL, @"assetURL is required"); - ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); - - // ALAssetsLibrary was replaced in iOS 8 and deprecated in iOS 9. - // We'll drop support very soon. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - ALAssetsLibrary *assetLibrary = [[ALAssetsLibrary alloc] init]; - - [assetLibrary assetForURL:assetURL resultBlock:^(ALAsset *asset) { - ALAssetRepresentation *representation = [asset defaultRepresentation]; - CGImageRef coreGraphicsImage = [representation fullScreenImage]; - - UIImage *downloadedImage = (coreGraphicsImage ? [UIImage imageWithCGImage:coreGraphicsImage] : nil); - completionBlock(downloadedImage, nil); - } failureBlock:^(NSError *error) { - completionBlock(nil, error); - }]; -#pragma clang diagnostic pop -} -#endif - -#if AS_USE_PHOTOS -- (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock -{ - ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); - ASDisplayNodeAssertNotNil(request, @"request is required"); - ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); - - /* - * Locking rationale: - * As of iOS 9, Photos.framework will eventually deadlock if you hit it with concurrent fetch requests. rdar://22984886 - * Concurrent image requests are OK, but metadata requests aren't, so we limit ourselves to one at a time. - */ - static NSLock *phRequestLock; - static NSOperationQueue *phImageRequestQueue; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - phRequestLock = [NSLock new]; - phImageRequestQueue = [NSOperationQueue new]; - phImageRequestQueue.maxConcurrentOperationCount = 10; - phImageRequestQueue.name = @"org.AsyncDisplayKit.MultiplexImageNode.phImageRequestQueue"; - }); - - // Each ASMultiplexImageNode can have max 1 inflight Photos image request operation - [_phImageRequestOperation cancel]; - - __weak __typeof(self) weakSelf = self; - NSOperation *newImageRequestOp = [NSBlockOperation blockOperationWithBlock:^{ - __strong __typeof(weakSelf) strongSelf = weakSelf; - if (strongSelf == nil) { return; } - - PHAsset *imageAsset = nil; - - // Try to get the asset immediately from the data source. - if (_dataSourceFlags.asset) { - imageAsset = [strongSelf.dataSource multiplexImageNode:strongSelf assetForLocalIdentifier:request.assetIdentifier]; - } - - // Fall back to locking and getting the PHAsset. - if (imageAsset == nil) { - [phRequestLock lock]; - // -[PHFetchResult dealloc] plays a role in the deadlock mentioned above, so we make sure the PHFetchResult is deallocated inside the critical section - @autoreleasepool { - imageAsset = [PHAsset fetchAssetsWithLocalIdentifiers:@[request.assetIdentifier] options:nil].firstObject; - } - [phRequestLock unlock]; - } - - if (imageAsset == nil) { - NSError *error = [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodePHAssetIsUnavailable userInfo:nil]; - completionBlock(nil, error); - return; - } - - PHImageRequestOptions *options = [request.options copy]; - - // We don't support opportunistic delivery – one request, one image. - if (options.deliveryMode == PHImageRequestOptionsDeliveryModeOpportunistic) { - options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; - } - - if (options.deliveryMode == PHImageRequestOptionsDeliveryModeHighQualityFormat) { - // Without this flag the result will be delivered on the main queue, which is pointless - // But synchronous -> HighQualityFormat so we only use it if high quality format is specified - options.synchronous = YES; - } - - PHImageManager *imageManager = strongSelf.imageManager ? : PHImageManager.defaultManager; - [imageManager requestImageForAsset:imageAsset targetSize:request.targetSize contentMode:request.contentMode options:options resultHandler:^(UIImage *image, NSDictionary *info) { - NSError *error = info[PHImageErrorKey]; - - if (error == nil && image == nil) { - error = [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodePhotosImageManagerFailedWithoutError userInfo:nil]; - } - - if (NSThread.isMainThread) { - dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ - completionBlock(image, error); - }); - } else { - completionBlock(image, error); - } - }]; - }]; - // If you don't set this, iOS will sometimes infer NSQualityOfServiceUserInteractive and promote the entire queue to that level, damaging system responsiveness - newImageRequestOp.qualityOfService = NSQualityOfServiceUserInitiated; - _phImageRequestOperation = newImageRequestOp; - [phImageRequestQueue addOperation:newImageRequestOp]; -} -#endif - -- (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock -{ - ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); - ASDisplayNodeAssertNotNil(imageURL, @"imageURL is required"); - ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); - - if (_cache) { - [_cache cachedImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() completion:^(id imageContainer) { - completionBlock([imageContainer asdk_image]); - }]; - } - // If we don't have a cache, just fail immediately. - else { - completionBlock(nil); - } -} - -- (void)_downloadImageWithIdentifier:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image, NSError *error))completionBlock -{ - ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); - ASDisplayNodeAssertNotNil(imageURL, @"imageURL is required"); - ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); - - // Delegate (start) - if (_delegateFlags.downloadStart) - [_delegate multiplexImageNode:self didStartDownloadOfImageWithIdentifier:imageIdentifier]; - - __weak __typeof__(self) weakSelf = self; - ASImageDownloaderProgress downloadProgressBlock = NULL; - if (_delegateFlags.downloadProgress) { - downloadProgressBlock = ^(CGFloat progress) { - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - [strongSelf.delegate multiplexImageNode:strongSelf didUpdateDownloadProgress:progress forImageWithIdentifier:imageIdentifier]; - }; - } - - ASImageDownloaderCompletion completion = ^(id imageContainer, NSError *error, id downloadIdentifier, id userInfo) { - // We dereference iVars directly, so we can't have weakSelf going nil on us. - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - - MutexLocker l(strongSelf->_downloadIdentifierLock); - //Getting a result back for a different download identifier, download must not have been successfully canceled - if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { - return; - } - - completionBlock([imageContainer asdk_image], error); - - // Delegateify. - if (strongSelf->_delegateFlags.downloadFinish) - [strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error]; - }; - - // Download! - ASPerformBlockOnBackgroundThread(^{ - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - - dispatch_queue_t callbackQueue = dispatch_get_main_queue(); - - id downloadIdentifier; - if (strongSelf->_downloaderFlags.downloaderImplementsDownloadWithPriority - && ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) { - - /* - Decide a priority based on the current interface state of this node. - It can happen that this method was called when the node entered preload state - but the interface state, at this point, tells us that the node is (going to be) visible, - If that's the case, we jump to a higher priority directly. - */ - ASImageDownloaderPriority priority = ASImageDownloaderPriorityWithInterfaceState(strongSelf.interfaceState); - downloadIdentifier = [strongSelf->_downloader downloadImageWithURL:imageURL - priority:priority - callbackQueue:callbackQueue - downloadProgress:downloadProgressBlock - completion:completion]; - } else { - /* - Kick off a download with default priority. - The actual "default" value is decided by the downloader. - ASBasicImageDownloader and ASPINRemoteImageDownloader both use ASImageDownloaderPriorityImminent - which is mapped to NSURLSessionTaskPriorityDefault. - - This means that preload and display nodes use the same priority - and their requests are put into the same pool. - */ - downloadIdentifier = [strongSelf->_downloader downloadImageWithURL:imageURL - callbackQueue:callbackQueue - downloadProgress:downloadProgressBlock - completion:completion]; - } - - [strongSelf _setDownloadIdentifier:downloadIdentifier]; - [strongSelf _updateProgressImageBlockOnDownloaderIfNeeded]; - }); -} - -#pragma mark - -- (void)_finishedLoadingImage:(UIImage *)image forIdentifier:(id)imageIdentifier error:(NSError *)error -{ - // If we failed to load, we stop the loading process. - // Note that if we bailed before we began downloading because the best identifier changed, we don't bail, but rather just begin loading the best image identifier. - if (error && !([error.domain isEqual:ASMultiplexImageNodeErrorDomain] && error.code == ASMultiplexImageNodeErrorCodeBestImageIdentifierChanged)) - return; - - - _imageIdentifiersLock.lock(); - NSUInteger imageIdentifierCount = [_imageIdentifiers count]; - _imageIdentifiersLock.unlock(); - - // Update our image if we got one, or if we're not supposed to display one at all. - // We explicitly perform this check because our datasource often doesn't give back immediately available images, even though we might have downloaded one already. - // Because we seed this call with bestImmediatelyAvailableImageFromDataSource, we must be careful not to trample an existing image. - if (image || imageIdentifierCount == 0) { - as_log_verbose(ASImageLoadingLog(), "[%p] loaded -> displaying (%@, %@)", self, imageIdentifier, image); - id previousIdentifier = self.loadedImageIdentifier; - UIImage *previousImage = self.image; - - self.loadedImageIdentifier = imageIdentifier; - [self _setImage:image]; - - if (_delegateFlags.updatedImage) { - [_delegate multiplexImageNode:self didUpdateImage:image withIdentifier:imageIdentifier fromImage:previousImage withIdentifier:previousIdentifier]; - } - - } - - // Load our next image, if we have one to load. - if ([self _nextImageIdentifierToDownload]) - [self _loadNextImage]; -} - -@end - -#if AS_USE_PHOTOS -@implementation NSURL (ASPhotosFrameworkURLs) - -+ (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(PHImageRequestOptions *)options NS_RETURNS_RETAINED -{ - ASPhotosFrameworkImageRequest *request = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:assetLocalIdentifier]; - request.options = options; - request.contentMode = contentMode; - request.targetSize = targetSize; - return request.url; -} - -@end -#endif -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASNavigationController.h b/submodules/AsyncDisplayKit/Source/ASNavigationController.h deleted file mode 100644 index 4f7ac12df4..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASNavigationController.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// ASNavigationController.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 - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * ASNavigationController - * - * @discussion ASNavigationController is a drop in replacement for UINavigationController - * which improves memory efficiency by implementing the @c ASManagesChildVisibilityDepth protocol. - * You can use ASNavigationController with regular UIViewControllers, as well as ASViewControllers. - * It is safe to subclass or use even where AsyncDisplayKit is not adopted. - * - * @see ASManagesChildVisibilityDepth - */ -@interface ASNavigationController : UINavigationController - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASNavigationController.mm b/submodules/AsyncDisplayKit/Source/ASNavigationController.mm deleted file mode 100644 index c92ddd7d27..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASNavigationController.mm +++ /dev/null @@ -1,115 +0,0 @@ -// -// ASNavigationController.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 -#import -#import - -@implementation ASNavigationController -{ - BOOL _parentManagesVisibilityDepth; - NSInteger _visibilityDepth; -} - -ASVisibilityDidMoveToParentViewController; - -ASVisibilityViewWillAppear; - -ASVisibilityViewDidDisappearImplementation; - -ASVisibilitySetVisibilityDepth; - -ASVisibilityDepthImplementation; - -- (void)visibilityDepthDidChange -{ - for (UIViewController *viewController in self.viewControllers) { - if ([viewController conformsToProtocol:@protocol(ASVisibilityDepth)]) { - [(id )viewController visibilityDepthDidChange]; - } - } -} - -- (NSInteger)visibilityDepthOfChildViewController:(UIViewController *)childViewController -{ - NSUInteger viewControllerIndex = [self.viewControllers indexOfObjectIdenticalTo:childViewController]; - if (viewControllerIndex == NSNotFound) { - //If childViewController is not actually a child, return NSNotFound which is also a really large number. - return NSNotFound; - } - - if (viewControllerIndex == self.viewControllers.count - 1) { - //view controller is at the top, just return our own visibility depth. - return [self visibilityDepth]; - } else if (viewControllerIndex == 0) { - //view controller is the root view controller. Can be accessed by holding the back button. - return [self visibilityDepth] + 1; - } - - return [self visibilityDepth] + self.viewControllers.count - 1 - viewControllerIndex; -} - -#pragma mark - UIKit overrides - -- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated -{ - as_activity_create_for_scope("Pop multiple from ASNavigationController"); - NSArray *viewControllers = [super popToViewController:viewController animated:animated]; - as_log_info(ASNodeLog(), "Popped %@ to %@, removing %@", self, viewController, ASGetDescriptionValueString(viewControllers)); - - [self visibilityDepthDidChange]; - return viewControllers; -} - -- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated -{ - as_activity_create_for_scope("Pop to root of ASNavigationController"); - NSArray *viewControllers = [super popToRootViewControllerAnimated:animated]; - as_log_info(ASNodeLog(), "Popped view controllers %@ from %@", ASGetDescriptionValueString(viewControllers), self); - - [self visibilityDepthDidChange]; - return viewControllers; -} - -- (void)setViewControllers:(NSArray *)viewControllers -{ - // NOTE: As of now this method calls through to setViewControllers:animated: so no need to log/activity here. - - [super setViewControllers:viewControllers]; - [self visibilityDepthDidChange]; -} - -- (void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated -{ - as_activity_create_for_scope("Set view controllers of ASNavigationController"); - as_log_info(ASNodeLog(), "Set view controllers of %@ to %@ animated: %d", self, ASGetDescriptionValueString(viewControllers), animated); - [super setViewControllers:viewControllers animated:animated]; - [self visibilityDepthDidChange]; -} - -- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated -{ - as_activity_create_for_scope("Push view controller on ASNavigationController"); - as_log_info(ASNodeLog(), "Pushing %@ onto %@", viewController, self); - [super pushViewController:viewController animated:animated]; - [self visibilityDepthDidChange]; -} - -- (UIViewController *)popViewControllerAnimated:(BOOL)animated -{ - as_activity_create_for_scope("Pop view controller from ASNavigationController"); - UIViewController *viewController = [super popViewControllerAnimated:animated]; - as_log_info(ASNodeLog(), "Popped %@ from %@", viewController, self); - [self visibilityDepthDidChange]; - return viewController; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASNetworkImageLoadInfo+Private.h b/submodules/AsyncDisplayKit/Source/ASNetworkImageLoadInfo+Private.h deleted file mode 100644 index 36e423b535..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASNetworkImageLoadInfo+Private.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// ASNetworkImageLoadInfo+Private.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ASNetworkImageLoadInfo () - -- (instancetype)initWithURL:(NSURL *)url - sourceType:(ASNetworkImageSourceType)sourceType - downloadIdentifier:(nullable id)downloadIdentifier - userInfo:(nullable id)userInfo; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASNetworkImageLoadInfo.h b/submodules/AsyncDisplayKit/Source/ASNetworkImageLoadInfo.h deleted file mode 100644 index 55c4b49a7a..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASNetworkImageLoadInfo.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// ASNetworkImageLoadInfo.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -typedef NS_ENUM(NSInteger, ASNetworkImageSourceType) { - ASNetworkImageSourceUnspecified = 0, - ASNetworkImageSourceSynchronousCache, - ASNetworkImageSourceAsynchronousCache, - ASNetworkImageSourceFileURL, - ASNetworkImageSourceDownload, -}; - -AS_SUBCLASSING_RESTRICTED -@interface ASNetworkImageLoadInfo : NSObject - -/// The type of source from which the image was loaded. -@property (readonly) ASNetworkImageSourceType sourceType; - -/// The image URL that was downloaded. -@property (readonly) NSURL *url; - -/// The download identifier, if one was provided. -@property (nullable, readonly) id downloadIdentifier; - -/// The userInfo object provided by the downloader, if one was provided. -@property (nullable, readonly) id userInfo; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASNetworkImageLoadInfo.mm b/submodules/AsyncDisplayKit/Source/ASNetworkImageLoadInfo.mm deleted file mode 100644 index c80603b7e1..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASNetworkImageLoadInfo.mm +++ /dev/null @@ -1,32 +0,0 @@ -// -// ASNetworkImageLoadInfo.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASNetworkImageLoadInfo.h" -#import "Private/ASNetworkImageLoadInfo+Private.h" - -@implementation ASNetworkImageLoadInfo - -- (instancetype)initWithURL:(NSURL *)url sourceType:(ASNetworkImageSourceType)sourceType downloadIdentifier:(id)downloadIdentifier userInfo:(id)userInfo -{ - if (self = [super init]) { - _url = [url copy]; - _sourceType = sourceType; - _downloadIdentifier = downloadIdentifier; - _userInfo = userInfo; - } - return self; -} - -#pragma mark - NSCopying - -- (id)copyWithZone:(NSZone *)zone -{ - return self; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASNetworkImageNode.h b/submodules/AsyncDisplayKit/Source/ASNetworkImageNode.h deleted file mode 100644 index f67826e86c..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASNetworkImageNode.h +++ /dev/null @@ -1,241 +0,0 @@ -// -// ASNetworkImageNode.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 - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASNetworkImageNodeDelegate, ASImageCacheProtocol, ASImageDownloaderProtocol; -@class ASNetworkImageLoadInfo; - - -/** - * ASNetworkImageNode is a simple image node that can download and display an image from the network, with support for a - * placeholder image (). The currently-displayed image is always available in the inherited ASImageNode - * property. - * - * @see ASMultiplexImageNode for a more powerful counterpart to this class. - */ -@interface ASNetworkImageNode : ASImageNode - -/** - * The designated initializer. Cache and Downloader are WEAK references. - * - * @param cache The object that implements a cache of images for the image node. Weak reference. - * @param downloader The object that implements image downloading for the image node. Must not be nil. Weak reference. - * - * @discussion If `cache` is nil, the receiver will not attempt to retrieve images from a cache before downloading them. - * - * @return An initialized ASNetworkImageNode. - */ -- (instancetype)initWithCache:(nullable id)cache downloader:(id)downloader NS_DESIGNATED_INITIALIZER; - -/** - * Convenience initializer. - * - * @return An ASNetworkImageNode configured to use the NSURLSession-powered ASBasicImageDownloader, and no extra cache. - */ -- (instancetype)init; - -/** - * The delegate, which must conform to the protocol. - */ -@property (nullable, weak) id delegate; - -/** - * The delegate will receive callbacks on main thread. Default to YES. - */ -@property (class) BOOL useMainThreadDelegateCallbacks; - -/** - * The image to display. - * - * @discussion By setting an image to the image property the ASNetworkImageNode will act like a plain ASImageNode. - * As soon as the URL is set the ASNetworkImageNode will act like an ASNetworkImageNode and the image property - * will be managed internally. This means the image property will be cleared out and replaced by the placeholder - * () image while loading and the final image after the new image data was downloaded and processed. - * If you want to use a placholder image functionality use the defaultImage property instead. - */ -@property (nullable) UIImage *image; - -/** - * A placeholder image to display while the URL is loading. This is slightly different than placeholderImage in the - * ASDisplayNode superclass as defaultImage will *not* be displayed synchronously. If you wish to have the image - * displayed synchronously, use @c placeholderImage. - */ -@property (nullable) UIImage *defaultImage; - -/** - * The URL of a new image to download and display. - * - * @discussion By setting an URL, the image property of this node will be managed internally. This means previously - * directly set images to the image property will be cleared out and replaced by the placeholder () image - * while loading and the final image after the new image data was downloaded and processed. - */ -@property (nullable, copy) NSURL *URL; - -/** - * An array of URLs of increasing cost to download. - * - * @discussion By setting an array of URLs, the image property of this node will be managed internally. This means previously - * directly set images to the image property will be cleared out and replaced by the placeholder () image - * while loading and the final image after the new image data was downloaded and processed. - * - * @deprecated This API has been removed for now due to the increased complexity to the class that it brought. - * Please use .URL instead. - */ -@property (nullable, copy) NSArray *URLs ASDISPLAYNODE_DEPRECATED_MSG("Please use URL instead."); - -/** - * Download and display a new image. - * - * @param URL The URL of a new image to download and display. - * @param reset Whether to display a placeholder () while loading the new image. - * - * @discussion By setting an URL, the image property of this node will be managed internally. This means previously - * directly set images to the image property will be cleared out and replaced by the placeholder () image - * while loading and the final image after the new image data was downloaded and processed. - */ -- (void)setURL:(nullable NSURL *)URL resetToDefault:(BOOL)reset; - -/** - * If is a local file, set this property to YES to take advantage of UIKit's image caching. Defaults to YES. - */ -@property BOOL shouldCacheImage; - -/** - * If the downloader implements progressive image rendering and this value is YES progressive renders of the - * image will be displayed as the image downloads. Regardless of this properties value, progress renders will - * only occur when the node is visible. Defaults to YES. - */ -@property BOOL shouldRenderProgressImages; - -/** - * The image quality of the current image. - * - * If the URL is set, this is a number between 0 and 1 and can be used to track - * progressive progress. Calculated by dividing number of bytes / expected number of total bytes. - * This is zero until the first progressive render or the final display. - * - * If the URL is unset, this is 1 if defaultImage or image is set to non-nil. - * - */ -@property (readonly) CGFloat currentImageQuality; - -/** - * The currentImageQuality (value between 0 and 1) of the last image that completed displaying. - */ -@property (readonly) CGFloat renderedImageQuality; - -@end - - -#pragma mark - - -/** - * The methods declared by the ASNetworkImageNodeDelegate protocol allow the adopting delegate to respond to - * notifications such as finished decoding and downloading an image. - */ -@protocol ASNetworkImageNodeDelegate -@optional - -/** - * Notification that the image node started to load - * - * @param imageNode The sender. - * - * @discussion Called on the main thread. - */ -- (void)imageNodeDidStartFetchingData:(ASNetworkImageNode *)imageNode; - -/** - * Notification that the image node will load image from cache - * - * @param imageNode The sender. - * - * @discussion Called on the main thread. - */ -- (void)imageNodeWillLoadImageFromCache:(ASNetworkImageNode *)imageNode; - -/** - * Notification that the image node finished loading image from cache - * - * @param imageNode The sender. - * - * @discussion Called on the main thread. - */ -- (void)imageNodeDidLoadImageFromCache:(ASNetworkImageNode *)imageNode; - -/** - * Notification that the image node will load image from network - * - * @param imageNode The sender. - * - * @discussion Called on the main thread. - */ -- (void)imageNodeWillLoadImageFromNetwork:(ASNetworkImageNode *)imageNode; - -/** - * Notification that the image node will start display - * - * @param imageNode The sender. - * - * @discussion Called on the main thread. - */ -- (void)imageNodeWillStartDisplayAsynchronously:(ASNetworkImageNode *)imageNode; - -/** - * Notification that the image node finished downloading an image, with additional info. - * If implemented, this method will be called instead of `imageNode:didLoadImage:`. - * - * @param imageNode The sender. - * @param image The newly-loaded image. - * @param info Additional information about the image load. - * - * @discussion Called on the main thread if useMainThreadDelegateCallbacks=YES (the default), otherwise on a background thread. - */ -- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image info:(ASNetworkImageLoadInfo *)info; - -/** - * Notification that the image node finished downloading an image. - * - * @param imageNode The sender. - * @param image The newly-loaded image. - * - * @discussion Called on the main thread if useMainThreadDelegateCallbacks=YES (the default), otherwise on a background thread. - */ -- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image; - -/** - * Notification that the image node failed to download the image. - * - * @param imageNode The sender. - * @param error The error with details. - * - * @discussion Called on the main thread if useMainThreadDelegateCallbacks=YES (the default), otherwise on a background thread. - */ -- (void)imageNode:(ASNetworkImageNode *)imageNode didFailWithError:(NSError *)error; - -/** - * Notification that the image node finished decoding an image. - * - * @param imageNode The sender. - * - * @discussion Called on the main thread. - */ -- (void)imageNodeDidFinishDecoding:(ASNetworkImageNode *)imageNode; - - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASNetworkImageNode.mm b/submodules/AsyncDisplayKit/Source/ASNetworkImageNode.mm deleted file mode 100644 index 1fc0368fd9..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASNetworkImageNode.mm +++ /dev/null @@ -1,892 +0,0 @@ -// -// ASNetworkImageNode.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 - -#import -#import -#import -#import -#import -#import -#import -#import "Private/ASInternalHelpers.h" -#import -#import -#import -#import -#import - -#import - -#if AS_PIN_REMOTE_IMAGE -#import -#endif - -@interface ASNetworkImageNode () -{ - // Only access any of these while locked. - __weak id _delegate; - - NSURL *_URL; - UIImage *_defaultImage; - - NSInteger _cacheSentinel; - id _downloadIdentifier; - // The download identifier that we have set a progress block on, if any. - id _downloadIdentifierForProgressBlock; - - BOOL _imageLoaded; - BOOL _imageWasSetExternally; - CGFloat _currentImageQuality; - CGFloat _renderedImageQuality; - BOOL _shouldRenderProgressImages; - - struct { - unsigned int delegateWillStartDisplayAsynchronously:1; - unsigned int delegateWillLoadImageFromCache:1; - unsigned int delegateWillLoadImageFromNetwork:1; - unsigned int delegateDidStartFetchingData:1; - unsigned int delegateDidFailWithError:1; - unsigned int delegateDidFinishDecoding:1; - unsigned int delegateDidLoadImage:1; - unsigned int delegateDidLoadImageFromCache:1; - unsigned int delegateDidLoadImageWithInfo:1; - } _delegateFlags; - - - // Immutable and set on init only. We don't need to lock in this case. - __weak id _downloader; - struct { - unsigned int downloaderImplementsSetProgress:1; - unsigned int downloaderImplementsSetPriority:1; - unsigned int downloaderImplementsAnimatedImage:1; - unsigned int downloaderImplementsCancelWithResume:1; - unsigned int downloaderImplementsDownloadWithPriority:1; - } _downloaderFlags; - - // Immutable and set on init only. We don't need to lock in this case. - __weak id _cache; - struct { - unsigned int cacheSupportsClearing:1; - unsigned int cacheSupportsSynchronousFetch:1; - } _cacheFlags; -} - -@end - -@implementation ASNetworkImageNode - -static std::atomic_bool _useMainThreadDelegateCallbacks(true); - -@dynamic image; - -- (instancetype)initWithCache:(id)cache downloader:(id)downloader -{ - if (!(self = [super init])) - return nil; - - _cache = (id)cache; - _downloader = (id)downloader; - - _downloaderFlags.downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)]; - _downloaderFlags.downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)]; - _downloaderFlags.downloaderImplementsAnimatedImage = [downloader respondsToSelector:@selector(animatedImageWithData:)]; - _downloaderFlags.downloaderImplementsCancelWithResume = [downloader respondsToSelector:@selector(cancelImageDownloadWithResumePossibilityForIdentifier:)]; - _downloaderFlags.downloaderImplementsDownloadWithPriority = [downloader respondsToSelector:@selector(downloadImageWithURL:priority:callbackQueue:downloadProgress:completion:)]; - - _cacheFlags.cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)]; - _cacheFlags.cacheSupportsSynchronousFetch = [cache respondsToSelector:@selector(synchronouslyFetchedCachedImageWithURL:)]; - - _shouldCacheImage = YES; - _shouldRenderProgressImages = YES; - self.shouldBypassEnsureDisplay = YES; - - return self; -} - -- (instancetype)init -{ -#if AS_PIN_REMOTE_IMAGE - return [self initWithCache:[ASPINRemoteImageDownloader sharedDownloader] downloader:[ASPINRemoteImageDownloader sharedDownloader]]; -#else - return [self initWithCache:nil downloader:[ASBasicImageDownloader sharedImageDownloader]]; -#endif -} - -- (void)dealloc -{ - [self _cancelImageDownloadWithResumePossibility:NO]; -} - -- (dispatch_queue_t)callbackQueue -{ - return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); -} - -#pragma mark - Public methods -- must lock - -/// Setter for public image property. It has the side effect of setting an internal _imageWasSetExternally that prevents setting an image internally. Setting an image internally should happen with the _setImage: method -- (void)setImage:(UIImage *)image -{ - ASLockScopeSelf(); - [self _locked_setImage:image]; -} - -- (void)_locked_setImage:(UIImage *)image -{ - ASAssertLocked(__instanceLock__); - - BOOL imageWasSetExternally = (image != nil); - BOOL shouldCancelAndClear = imageWasSetExternally && (imageWasSetExternally != _imageWasSetExternally); - _imageWasSetExternally = imageWasSetExternally; - if (shouldCancelAndClear) { - ASDisplayNodeAssertNil(_URL, @"Directly setting an image on an ASNetworkImageNode causes it to behave like an ASImageNode instead of an ASNetworkImageNode. If this is what you want, set the URL to nil first."); - _URL = nil; - [self _locked_cancelDownloadAndClearImageWithResumePossibility:NO]; - } - - // If our image is being set externally, the image quality is 100% - if (imageWasSetExternally) { - [self _setCurrentImageQuality:1.0]; - } - - [self _locked__setImage:image]; -} - -/// Setter for private image property. See @c _locked_setImage why this is needed -- (void)_setImage:(UIImage *)image -{ - ASLockScopeSelf(); - [self _locked__setImage:image]; -} - -- (void)_locked__setImage:(UIImage *)image -{ - ASAssertLocked(__instanceLock__); - [super _locked_setImage:image]; -} - -// Deprecated -- (void)setURLs:(NSArray *)URLs -{ - [self setURL:[URLs firstObject]]; -} - -// Deprecated -- (NSArray *)URLs -{ - return @[self.URL]; -} - -- (void)setURL:(NSURL *)URL -{ - [self setURL:URL resetToDefault:YES]; -} - -- (void)setURL:(NSURL *)URL resetToDefault:(BOOL)reset -{ - { - ASLockScopeSelf(); - - if (ASObjectIsEqual(URL, _URL)) { - return; - } - - URL = [URL copy]; - - ASDisplayNodeAssert(_imageWasSetExternally == NO, @"Setting a URL to an ASNetworkImageNode after setting an image changes its behavior from an ASImageNode to an ASNetworkImageNode. If this is what you want, set the image to nil first."); - - _imageWasSetExternally = NO; - - [self _locked_cancelImageDownloadWithResumePossibility:NO]; - - _imageLoaded = NO; - - _URL = URL; - - // If URL is nil and URL was not equal to _URL (checked at the top), then we previously had a URL but it's been nil'd out. - BOOL hadURL = (URL == nil); - if (reset || hadURL) { - [self _setCurrentImageQuality:(hadURL ? 0.0 : 1.0)]; - [self _locked__setImage:_defaultImage]; - } - } - - [self setNeedsPreload]; -} - -- (NSURL *)URL -{ - return ASLockedSelf(_URL); -} - -- (void)setDefaultImage:(UIImage *)defaultImage -{ - ASLockScopeSelf(); - - [self _locked_setDefaultImage:defaultImage]; -} - -- (void)_locked_setDefaultImage:(UIImage *)defaultImage -{ - if (ASObjectIsEqual(defaultImage, _defaultImage)) { - return; - } - - _defaultImage = defaultImage; - - if (!_imageLoaded) { - [self _setCurrentImageQuality:((_URL == nil) ? 0.0 : 1.0)]; - [self _locked__setImage:defaultImage]; - } -} - -- (UIImage *)defaultImage -{ - return ASLockedSelf(_defaultImage); -} - -- (void)setCurrentImageQuality:(CGFloat)currentImageQuality -{ - ASLockScopeSelf(); - _currentImageQuality = currentImageQuality; -} - -- (CGFloat)currentImageQuality -{ - return ASLockedSelf(_currentImageQuality); -} - -/** - * Always use these methods internally to update the current image quality - * We want to maintain the order that currentImageQuality is set regardless of the calling thread, - * so we always have to dispatch to the main thread to ensure that we queue the operations in the correct order. - * (see comment in displayDidFinish) - */ -- (void)_setCurrentImageQuality:(CGFloat)imageQuality -{ - dispatch_async(dispatch_get_main_queue(), ^{ - self.currentImageQuality = imageQuality; - }); -} - -- (void)setRenderedImageQuality:(CGFloat)renderedImageQuality -{ - ASLockScopeSelf(); - _renderedImageQuality = renderedImageQuality; -} - -- (CGFloat)renderedImageQuality -{ - ASLockScopeSelf(); - return _renderedImageQuality; -} - -- (void)setDelegate:(id)delegate -{ - ASLockScopeSelf(); - _delegate = delegate; - - _delegateFlags.delegateWillStartDisplayAsynchronously = [delegate respondsToSelector:@selector(imageNodeWillStartDisplayAsynchronously:)]; - _delegateFlags.delegateWillLoadImageFromCache = [delegate respondsToSelector:@selector(imageNodeWillLoadImageFromCache:)]; - _delegateFlags.delegateWillLoadImageFromNetwork = [delegate respondsToSelector:@selector(imageNodeWillLoadImageFromNetwork:)]; - _delegateFlags.delegateDidStartFetchingData = [delegate respondsToSelector:@selector(imageNodeDidStartFetchingData:)]; - _delegateFlags.delegateDidFailWithError = [delegate respondsToSelector:@selector(imageNode:didFailWithError:)]; - _delegateFlags.delegateDidFinishDecoding = [delegate respondsToSelector:@selector(imageNodeDidFinishDecoding:)]; - _delegateFlags.delegateDidLoadImage = [delegate respondsToSelector:@selector(imageNode:didLoadImage:)]; - _delegateFlags.delegateDidLoadImageFromCache = [delegate respondsToSelector:@selector(imageNodeDidLoadImageFromCache:)]; - _delegateFlags.delegateDidLoadImageWithInfo = [delegate respondsToSelector:@selector(imageNode:didLoadImage:info:)]; -} - -- (id)delegate -{ - ASLockScopeSelf(); - return _delegate; -} - -- (void)setShouldRenderProgressImages:(BOOL)shouldRenderProgressImages -{ - if (ASLockedSelfCompareAssign(_shouldRenderProgressImages, shouldRenderProgressImages)) { - [self _updateProgressImageBlockOnDownloaderIfNeeded]; - } -} - -- (BOOL)shouldRenderProgressImages -{ - ASLockScopeSelf(); - return _shouldRenderProgressImages; -} - -- (BOOL)placeholderShouldPersist -{ - ASLockScopeSelf(); - return (self.image == nil && self.animatedImage == nil && _URL != nil); -} - -/* displayWillStartAsynchronously: in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary - in ASMultiplexImageNode as well. */ -- (void)displayWillStartAsynchronously:(BOOL)asynchronously -{ - [super displayWillStartAsynchronously:asynchronously]; - - id delegate; - BOOL notifyDelegate; - { - ASLockScopeSelf(); - notifyDelegate = _delegateFlags.delegateWillStartDisplayAsynchronously; - delegate = _delegate; - } - if (notifyDelegate) { - [delegate imageNodeWillStartDisplayAsynchronously:self]; - } - - if (asynchronously == NO && _cacheFlags.cacheSupportsSynchronousFetch) { - ASLockScopeSelf(); - - NSURL *url = _URL; - if (_imageLoaded == NO && url && _downloadIdentifier == nil) { - UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:url] asdk_image]; - if (result) { - [self _setCurrentImageQuality:1.0]; - [self _locked__setImage:result]; - _imageLoaded = YES; - - // Call out to the delegate. - if (_delegateFlags.delegateDidLoadImageWithInfo) { - ASUnlockScope(self); - const auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:url sourceType:ASNetworkImageSourceSynchronousCache downloadIdentifier:nil userInfo:nil]; - [delegate imageNode:self didLoadImage:result info:info]; - } else if (_delegateFlags.delegateDidLoadImage) { - ASUnlockScope(self); - [delegate imageNode:self didLoadImage:result]; - } - } - } - } - - if (self.image == nil) { - [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityImminent]; - } -} - -/* visibileStateDidChange in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary - in ASMultiplexImageNode as well. */ -- (void)didEnterVisibleState -{ - [super didEnterVisibleState]; - [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityVisible]; - [self _updateProgressImageBlockOnDownloaderIfNeeded]; -} - -- (void)didExitVisibleState -{ - [super didExitVisibleState]; - [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityPreload]; - [self _updateProgressImageBlockOnDownloaderIfNeeded]; -} - -- (void)didExitDisplayState -{ - [super didExitDisplayState]; - if (ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) { - [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityPreload]; - } -} - -- (void)didExitPreloadState -{ - [super didExitPreloadState]; - - // If the image was set explicitly we don't want to remove it while exiting the preload state - if (ASLockedSelf(_imageWasSetExternally)) { - return; - } - - [self _cancelDownloadAndClearImageWithResumePossibility:YES]; -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - - // Image was set externally no need to load an image - [self _lazilyLoadImageIfNecessary]; -} - -+ (void)setUseMainThreadDelegateCallbacks:(BOOL)useMainThreadDelegateCallbacks -{ - _useMainThreadDelegateCallbacks = useMainThreadDelegateCallbacks; -} - -+ (BOOL)useMainThreadDelegateCallbacks -{ - return _useMainThreadDelegateCallbacks; -} - -#pragma mark - Progress - -- (void)handleProgressImage:(UIImage *)progressImage progress:(CGFloat)progress downloadIdentifier:(nullable id)downloadIdentifier -{ - ASLockScopeSelf(); - - // Getting a result back for a different download identifier, download must not have been successfully canceled - if (ASObjectIsEqual(_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { - return; - } - - as_log_verbose(ASImageLoadingLog(), "Received progress image for %@ q: %.2g id: %@", self, progress, progressImage); - [self _setCurrentImageQuality:progress]; - [self _locked__setImage:progressImage]; -} - -- (void)_updatePriorityOnDownloaderIfNeededWithDefaultPriority:(ASImageDownloaderPriority)defaultPriority -{ - if (_downloaderFlags.downloaderImplementsSetPriority) { - ASLockScopeSelf(); - - if (_downloadIdentifier != nil) { - ASImageDownloaderPriority priority = defaultPriority; - if (ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) { - priority = ASImageDownloaderPriorityWithInterfaceState(_interfaceState); - } - - [_downloader setPriority:priority withDownloadIdentifier:_downloadIdentifier]; - } - } -} - -- (void)_updateProgressImageBlockOnDownloaderIfNeeded -{ - // If the downloader doesn't do progress, we are done. - if (_downloaderFlags.downloaderImplementsSetProgress == NO) { - return; - } - - // Read state. - [self lock]; - BOOL shouldRender = _shouldRenderProgressImages && ASInterfaceStateIncludesVisible(_interfaceState); - id oldDownloadIDForProgressBlock = _downloadIdentifierForProgressBlock; - id newDownloadIDForProgressBlock = shouldRender ? _downloadIdentifier : nil; - BOOL clearAndReattempt = NO; - [self unlock]; - - // If we're already bound to the correct download, we're done. - if (ASObjectIsEqual(oldDownloadIDForProgressBlock, newDownloadIDForProgressBlock)) { - return; - } - - // Unbind from the previous download. - if (oldDownloadIDForProgressBlock != nil) { - as_log_verbose(ASImageLoadingLog(), "Disabled progress images for %@ id: %@", self, oldDownloadIDForProgressBlock); - [_downloader setProgressImageBlock:nil callbackQueue:[self callbackQueue] withDownloadIdentifier:oldDownloadIDForProgressBlock]; - } - - // Bind to the current download. - if (newDownloadIDForProgressBlock != nil) { - __weak __typeof(self) weakSelf = self; - as_log_verbose(ASImageLoadingLog(), "Enabled progress images for %@ id: %@", self, newDownloadIDForProgressBlock); - [_downloader setProgressImageBlock:^(UIImage * _Nonnull progressImage, CGFloat progress, id _Nullable downloadIdentifier) { - [weakSelf handleProgressImage:progressImage progress:progress downloadIdentifier:downloadIdentifier]; - } callbackQueue:[self callbackQueue] withDownloadIdentifier:newDownloadIDForProgressBlock]; - } - - // Update state local state with lock held. - { - ASLockScopeSelf(); - // Check if the oldDownloadIDForProgressBlock still is the same as the _downloadIdentifierForProgressBlock - if (_downloadIdentifierForProgressBlock == oldDownloadIDForProgressBlock) { - _downloadIdentifierForProgressBlock = newDownloadIDForProgressBlock; - } else if (newDownloadIDForProgressBlock != nil) { - // If this is not the case another thread did change the _downloadIdentifierForProgressBlock already so - // we have to deregister the newDownloadIDForProgressBlock that we registered above - clearAndReattempt = YES; - } - } - - if (clearAndReattempt) { - // In this case another thread changed the _downloadIdentifierForProgressBlock before we finished registering - // the new progress block for newDownloadIDForProgressBlock ID. Let's clear it now and reattempt to register - if (newDownloadIDForProgressBlock) { - [_downloader setProgressImageBlock:nil callbackQueue:[self callbackQueue] withDownloadIdentifier:newDownloadIDForProgressBlock]; - } - [self _updateProgressImageBlockOnDownloaderIfNeeded]; - } -} - -- (void)_cancelDownloadAndClearImageWithResumePossibility:(BOOL)storeResume -{ - ASLockScopeSelf(); - [self _locked_cancelDownloadAndClearImageWithResumePossibility:storeResume]; -} - -- (void)_locked_cancelDownloadAndClearImageWithResumePossibility:(BOOL)storeResume -{ - ASAssertLocked(__instanceLock__); - - [self _locked_cancelImageDownloadWithResumePossibility:storeResume]; - - [self _locked_setAnimatedImage:nil]; - [self _setCurrentImageQuality:0.0]; - [self _locked__setImage:_defaultImage]; - - _imageLoaded = NO; - - if (_cacheFlags.cacheSupportsClearing) { - if (_URL != nil) { - as_log_verbose(ASImageLoadingLog(), "Clearing cached image for %@ url: %@", self, _URL); - [_cache clearFetchedImageFromCacheWithURL:_URL]; - } - } -} - -- (void)_cancelImageDownloadWithResumePossibility:(BOOL)storeResume -{ - ASLockScopeSelf(); - [self _locked_cancelImageDownloadWithResumePossibility:storeResume]; -} - -- (void)_locked_cancelImageDownloadWithResumePossibility:(BOOL)storeResume -{ - ASAssertLocked(__instanceLock__); - - if (!_downloadIdentifier) { - return; - } - - if (_downloadIdentifier) { - if (storeResume && _downloaderFlags.downloaderImplementsCancelWithResume) { - as_log_verbose(ASImageLoadingLog(), "Canceling image download w resume for %@ id: %@", self, _downloadIdentifier); - [_downloader cancelImageDownloadWithResumePossibilityForIdentifier:_downloadIdentifier]; - } else { - as_log_verbose(ASImageLoadingLog(), "Canceling image download no resume for %@ id: %@", self, _downloadIdentifier); - [_downloader cancelImageDownloadForIdentifier:_downloadIdentifier]; - } - } - _downloadIdentifier = nil; - _cacheSentinel++; -} - -- (void)_downloadImageWithCompletion:(void (^)(id imageContainer, NSError*, id downloadIdentifier, id userInfo))finished -{ - ASPerformBlockOnBackgroundThread(^{ - NSURL *url; - id downloadIdentifier; - BOOL cancelAndReattempt = NO; - ASInterfaceState interfaceState; - - // Below, to avoid performance issues, we're calling downloadImageWithURL without holding the lock. This is a bit ugly because - // We need to reobtain the lock after and ensure that the task we've kicked off still matches our URL. If not, we need to cancel - // it and try again. - { - ASLockScopeSelf(); - url = self->_URL; - interfaceState = self->_interfaceState; - } - - dispatch_queue_t callbackQueue = [self callbackQueue]; - ASImageDownloaderProgress downloadProgress = NULL; - ASImageDownloaderCompletion completion = ^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) { - if (finished != NULL) { - finished(imageContainer, error, downloadIdentifier, userInfo); - } - }; - - if (self->_downloaderFlags.downloaderImplementsDownloadWithPriority - && ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) { - /* - Decide a priority based on the current interface state of this node. - It can happen that this method was called when the node entered preload state - but the interface state, at this point, tells us that the node is (going to be) visible. - If that's the case, we jump to a higher priority directly. - */ - ASImageDownloaderPriority priority = ASImageDownloaderPriorityWithInterfaceState(interfaceState); - - downloadIdentifier = [self->_downloader downloadImageWithURL:url - priority:priority - callbackQueue:callbackQueue - downloadProgress:downloadProgress - completion:completion]; - } else { - /* - Kick off a download with default priority. - The actual "default" value is decided by the downloader. - ASBasicImageDownloader and ASPINRemoteImageDownloader both use ASImageDownloaderPriorityImminent - which is mapped to NSURLSessionTaskPriorityDefault. - - This means that preload and display nodes use the same priority - and their requests are put into the same pool. - */ - downloadIdentifier = [self->_downloader downloadImageWithURL:url - callbackQueue:callbackQueue - downloadProgress:downloadProgress - completion:completion]; - } - as_log_verbose(ASImageLoadingLog(), "Downloading image for %@ url: %@", self, url); - - { - ASLockScopeSelf(); - if (ASObjectIsEqual(self->_URL, url)) { - // The download we kicked off is correct, no need to do any more work. - self->_downloadIdentifier = downloadIdentifier; - } else { - // The URL changed since we kicked off our download task. This shouldn't happen often so we'll pay the cost and - // cancel that request and kick off a new one. - cancelAndReattempt = YES; - } - } - - if (cancelAndReattempt) { - if (downloadIdentifier != nil) { - as_log_verbose(ASImageLoadingLog(), "Canceling image download no resume for %@ id: %@", self, downloadIdentifier); - [self->_downloader cancelImageDownloadForIdentifier:downloadIdentifier]; - } - [self _downloadImageWithCompletion:finished]; - return; - } - - [self _updateProgressImageBlockOnDownloaderIfNeeded]; - }); -} - -- (void)_lazilyLoadImageIfNecessary -{ - ASDisplayNodeAssertMainThread(); - - [self lock]; - __weak id delegate = _delegate; - BOOL delegateDidStartFetchingData = _delegateFlags.delegateDidStartFetchingData; - BOOL delegateWillLoadImageFromCache = _delegateFlags.delegateWillLoadImageFromCache; - BOOL delegateWillLoadImageFromNetwork = _delegateFlags.delegateWillLoadImageFromNetwork; - BOOL delegateDidLoadImageFromCache = _delegateFlags.delegateDidLoadImageFromCache; - BOOL isImageLoaded = _imageLoaded; - NSURL *URL = _URL; - id currentDownloadIdentifier = _downloadIdentifier; - [self unlock]; - - if (!isImageLoaded && URL != nil && currentDownloadIdentifier == nil) { - if (delegateDidStartFetchingData) { - [delegate imageNodeDidStartFetchingData:self]; - } - - if (URL.isFileURL) { - dispatch_async(dispatch_get_main_queue(), ^{ - ASLockScopeSelf(); - - // Bail out if not the same URL anymore - if (!ASObjectIsEqual(URL, self->_URL)) { - return; - } - - if (self->_shouldCacheImage) { - [self _locked__setImage:[UIImage imageNamed:URL.path.lastPathComponent]]; - } else { - // First try to load the path directly, for efficiency assuming a developer who - // doesn't want caching is trying to be as minimal as possible. - auto nonAnimatedImage = [[UIImage alloc] initWithContentsOfFile:URL.path]; - if (nonAnimatedImage == nil) { - // If we couldn't find it, execute an -imageNamed:-like search so we can find resources even if the - // extension is not provided in the path. This allows the same path to work regardless of shouldCacheImage. - NSString *filename = [[NSBundle mainBundle] pathForResource:URL.path.lastPathComponent ofType:nil]; - if (filename != nil) { - nonAnimatedImage = [[UIImage alloc] initWithContentsOfFile:filename]; - } - } - - // If the file may be an animated gif and then created an animated image. - id animatedImage = nil; - if (self->_downloaderFlags.downloaderImplementsAnimatedImage) { - const auto data = [[NSData alloc] initWithContentsOfURL:URL]; - if (data != nil) { - animatedImage = [self->_downloader animatedImageWithData:data]; - - if ([animatedImage respondsToSelector:@selector(isDataSupported:)] && [animatedImage isDataSupported:data] == NO) { - animatedImage = nil; - } - } - } - - if (animatedImage != nil) { - [self _locked_setAnimatedImage:animatedImage]; - } else { - [self _locked__setImage:nonAnimatedImage]; - } - } - - self->_imageLoaded = YES; - - [self _setCurrentImageQuality:1.0]; - - if (self->_delegateFlags.delegateDidLoadImageWithInfo) { - ASUnlockScope(self); - const auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:URL sourceType:ASNetworkImageSourceFileURL downloadIdentifier:nil userInfo:nil]; - [delegate imageNode:self didLoadImage:self.image info:info]; - } else if (self->_delegateFlags.delegateDidLoadImage) { - ASUnlockScope(self); - [delegate imageNode:self didLoadImage:self.image]; - } - }); - } else { - __weak __typeof__(self) weakSelf = self; - const auto finished = ^(id imageContainer, NSError *error, id downloadIdentifier, ASNetworkImageSourceType imageSource, id userInfo) { - ASPerformBlockOnBackgroundThread(^{ - __typeof__(self) strongSelf = weakSelf; - if (strongSelf == nil) { - return; - } - - as_log_verbose(ASImageLoadingLog(), "Downloaded image for %@ img: %@ url: %@", self, [imageContainer asdk_image], URL); - - // Grab the lock for the rest of the block - ASLockScope(strongSelf); - - //Getting a result back for a different download identifier, download must not have been successfully canceled - if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { - return; - } - - //No longer in preload range, no point in setting the results (they won't be cleared in exit preload range) - if (ASInterfaceStateIncludesPreload(strongSelf->_interfaceState) == NO) { - strongSelf->_downloadIdentifier = nil; - strongSelf->_cacheSentinel++; - return; - } - - UIImage *newImage; - if (imageContainer != nil) { - [strongSelf _setCurrentImageQuality:1.0]; - NSData *animatedImageData = [imageContainer asdk_animatedImageData]; - if (animatedImageData && strongSelf->_downloaderFlags.downloaderImplementsAnimatedImage) { - id animatedImage = [strongSelf->_downloader animatedImageWithData:animatedImageData]; - [strongSelf _locked_setAnimatedImage:animatedImage]; - } else { - newImage = [imageContainer asdk_image]; - [strongSelf _locked__setImage:newImage]; - } - strongSelf->_imageLoaded = YES; - } - - strongSelf->_downloadIdentifier = nil; - strongSelf->_cacheSentinel++; - - void (^calloutBlock)(ASNetworkImageNode *inst); - - if (newImage) { - if (strongSelf->_delegateFlags.delegateDidLoadImageWithInfo) { - calloutBlock = ^(ASNetworkImageNode *strongSelf) { - const auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:URL sourceType:imageSource downloadIdentifier:downloadIdentifier userInfo:userInfo]; - [delegate imageNode:strongSelf didLoadImage:newImage info:info]; - }; - } else if (strongSelf->_delegateFlags.delegateDidLoadImage) { - calloutBlock = ^(ASNetworkImageNode *strongSelf) { - [delegate imageNode:strongSelf didLoadImage:newImage]; - }; - } - } else if (error && strongSelf->_delegateFlags.delegateDidFailWithError) { - calloutBlock = ^(ASNetworkImageNode *strongSelf) { - [delegate imageNode:strongSelf didFailWithError:error]; - }; - } - - if (calloutBlock) { - if (ASNetworkImageNode.useMainThreadDelegateCallbacks) { - ASPerformBlockOnMainThread(^{ - if (auto strongSelf = weakSelf) { - calloutBlock(strongSelf); - } - }); - } else { - calloutBlock(strongSelf); - } - } - }); - }; - - // As the _cache and _downloader is only set once in the intializer we don't have to use a - // lock in here - if (_cache != nil) { - NSInteger cacheSentinel = ASLockedSelf(++_cacheSentinel); - - as_log_verbose(ASImageLoadingLog(), "Decaching image for %@ url: %@", self, URL); - - ASImageCacherCompletion completion = ^(id imageContainer) { - // If the cache sentinel changed, that means this request was cancelled. - if (ASLockedSelf(self->_cacheSentinel != cacheSentinel)) { - return; - } - - if ([imageContainer asdk_image] == nil && self->_downloader != nil) { - if (delegateWillLoadImageFromNetwork) { - [delegate imageNodeWillLoadImageFromNetwork:self]; - } - [self _downloadImageWithCompletion:^(id imageContainer, NSError *error, id downloadIdentifier, id userInfo) { - finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload, userInfo); - }]; - } else { - if (delegateDidLoadImageFromCache) { - [delegate imageNodeDidLoadImageFromCache:self]; - } - as_log_verbose(ASImageLoadingLog(), "Decached image for %@ img: %@ url: %@", self, [imageContainer asdk_image], URL); - finished(imageContainer, nil, nil, ASNetworkImageSourceAsynchronousCache, nil); - } - }; - - if (delegateWillLoadImageFromCache) { - [delegate imageNodeWillLoadImageFromCache:self]; - } - [_cache cachedImageWithURL:URL - callbackQueue:[self callbackQueue] - completion:completion]; - } else { - if (delegateWillLoadImageFromNetwork) { - [delegate imageNodeWillLoadImageFromNetwork:self]; - } - [self _downloadImageWithCompletion:^(id imageContainer, NSError *error, id downloadIdentifier, id userInfo) { - finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload, userInfo); - }]; - } - } - } -} - -#pragma mark - ASDisplayNode+Subclasses - -- (void)displayDidFinish -{ - [super displayDidFinish]; - - id delegate = nil; - - { - ASLockScopeSelf(); - if (_delegateFlags.delegateDidFinishDecoding && self.layer.contents != nil) { - /* We store the image quality in _currentImageQuality whenever _image is set. On the following displayDidFinish, we'll know that - _currentImageQuality is the quality of the image that has just finished rendering. In order for this to be accurate, we - need to be sure we are on main thread when we set _currentImageQuality. Otherwise, it is possible for _currentImageQuality - to be modified at a point where it is too late to cancel the main thread's previous display (the final sentinel check has passed), - but before the displayDidFinish of the previous display pass is called. In this situation, displayDidFinish would be called and we - would set _renderedImageQuality to the new _currentImageQuality, but the actual quality of the rendered image should be the previous - value stored in _currentImageQuality. */ - - _renderedImageQuality = _currentImageQuality; - - // Assign the delegate to be used - delegate = _delegate; - } - } - - if (delegate != nil) { - [delegate imageNodeDidFinishDecoding:self]; - } -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASPINRemoteImageDownloader.h b/submodules/AsyncDisplayKit/Source/ASPINRemoteImageDownloader.h deleted file mode 100644 index dceca079e2..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPINRemoteImageDownloader.h +++ /dev/null @@ -1,84 +0,0 @@ -// -// ASPINRemoteImageDownloader.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 - -#if AS_PIN_REMOTE_IMAGE - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class PINRemoteImageManager; -@protocol PINRemoteImageCaching; - -@interface ASPINRemoteImageDownloader : NSObject - -/** - * A shared image downloader which can be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes. - * The userInfo provided by this downloader is an instance of `PINRemoteImageManagerResult`. - * - * This is the default downloader used by network backed image nodes if PINRemoteImage and PINCache are - * available. It uses PINRemoteImage's features to provide caching and progressive image downloads. - */ -+ (ASPINRemoteImageDownloader *)sharedDownloader NS_RETURNS_RETAINED; - -/** - * Sets the default NSURLSessionConfiguration that will be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes - * while loading images off the network. This must be specified early in the application lifecycle before - * `sharedDownloader` is accessed. - * - * @param configuration The session configuration that will be used by `sharedDownloader` - * - */ -+ (void)setSharedImageManagerWithConfiguration:(nullable NSURLSessionConfiguration *)configuration; - -/** - * Sets the default NSURLSessionConfiguration that will be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes - * while loading images off the network. This must be specified early in the application lifecycle before - * `sharedDownloader` is accessed. - * - * @param configuration The session configuration that will be used by `sharedDownloader` - * @param imageCache The cache to be used by PINRemoteImage - nil will set up a default cache: PINCache - * if it is available, PINRemoteImageBasicCache (NSCache) if not. - * - */ -+ (void)setSharedImageManagerWithConfiguration:(nullable NSURLSessionConfiguration *)configuration - imageCache:(nullable id)imageCache; - -/** - * Sets a custom preconfigured PINRemoteImageManager that will be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes - * while loading images off the network. This must be specified early in the application lifecycle before - * `sharedDownloader` is accessed. - * - * @param preconfiguredPINRemoteImageManager The preconfigured remote image manager that will be used by `sharedDownloader` - */ -+ (void)setSharedPreconfiguredRemoteImageManager:(PINRemoteImageManager *)preconfiguredPINRemoteImageManager; - -/** - * The shared instance of a @c PINRemoteImageManager used by all @c ASPINRemoteImageDownloaders - * - * @discussion you can use this method to access the shared manager. This is useful to share a cache - * and resources if you need to download images outside of an @c ASNetworkImageNode or - * @c ASMultiplexImageNode. It's also useful to access the memoryCache and diskCache to set limits - * or handle authentication challenges. - * - * @return An instance of a @c PINRemoteImageManager - */ -- (PINRemoteImageManager *)sharedPINRemoteImageManager; - -@end - -NS_ASSUME_NONNULL_END - -#endif - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASPINRemoteImageDownloader.mm b/submodules/AsyncDisplayKit/Source/ASPINRemoteImageDownloader.mm deleted file mode 100644 index 84341b40c8..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPINRemoteImageDownloader.mm +++ /dev/null @@ -1,393 +0,0 @@ -// -// ASPINRemoteImageDownloader.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 - -#if AS_PIN_REMOTE_IMAGE -#import - -#import -#import -#import - -#if __has_include () -#define PIN_ANIMATED_AVAILABLE 1 -#import -#import -#else -#define PIN_ANIMATED_AVAILABLE 0 -#endif - -#if __has_include() -#define PIN_WEBP_AVAILABLE 1 -#else -#define PIN_WEBP_AVAILABLE 0 -#endif - -#import -#import -#import - -static inline PINRemoteImageManagerPriority PINRemoteImageManagerPriorityWithASImageDownloaderPriority(ASImageDownloaderPriority priority) { - switch (priority) { - case ASImageDownloaderPriorityPreload: - return PINRemoteImageManagerPriorityLow; - - case ASImageDownloaderPriorityImminent: - return PINRemoteImageManagerPriorityDefault; - - case ASImageDownloaderPriorityVisible: - return PINRemoteImageManagerPriorityHigh; - } -} - -#if PIN_ANIMATED_AVAILABLE - -@interface ASPINRemoteImageDownloader () -@end - -@interface PINCachedAnimatedImage (ASPINRemoteImageDownloader) -@end - -@implementation PINCachedAnimatedImage (ASPINRemoteImageDownloader) - -- (BOOL)isDataSupported:(NSData *)data -{ - if ([data pin_isGIF]) { - return YES; - } -#if PIN_WEBP_AVAILABLE - else if ([data pin_isAnimatedWebP]) { - return YES; - } -#endif - return NO; -} - -@end -#endif - -// Declare two key methods on PINCache objects, avoiding a direct dependency on PINCache.h -@protocol ASPINCache -- (id)diskCache; -@end - -@protocol ASPINDiskCache -@property NSUInteger byteLimit; -@end - -@interface ASPINRemoteImageManager : PINRemoteImageManager -@end - -@implementation ASPINRemoteImageManager - -//Share image cache with sharedImageManager image cache. -+ (id )defaultImageCache -{ - static dispatch_once_t onceToken; - static id cache = nil; - dispatch_once(&onceToken, ^{ - cache = [[PINRemoteImageManager sharedImageManager] cache]; - if ([cache respondsToSelector:@selector(diskCache)]) { - id diskCache = [(id )cache diskCache]; - if ([diskCache respondsToSelector:@selector(setByteLimit:)]) { - // Set a default byteLimit. PINCache recently implemented a 50MB default (PR #201). - // Ensure that older versions of PINCache also have a byteLimit applied. - // NOTE: Using 20MB limit while large cache initialization is being optimized (Issue #144). - ((id )diskCache).byteLimit = 20 * 1024 * 1024; - } - } - }); - return cache; -} - -@end - -static ASPINRemoteImageDownloader *sharedDownloader = nil; -static PINRemoteImageManager *sharedPINRemoteImageManager = nil; - -@interface ASPINRemoteImageDownloader () -@end - -@implementation ASPINRemoteImageDownloader - -+ (ASPINRemoteImageDownloader *)sharedDownloader NS_RETURNS_RETAINED -{ - static dispatch_once_t onceToken = 0; - dispatch_once(&onceToken, ^{ - sharedDownloader = [[ASPINRemoteImageDownloader alloc] init]; - }); - return sharedDownloader; -} - -+ (void)setSharedImageManagerWithConfiguration:(nullable NSURLSessionConfiguration *)configuration -{ - NSAssert(sharedDownloader == nil, @"Singleton has been created and session can no longer be configured."); - PINRemoteImageManager *sharedManager = [self PINRemoteImageManagerWithConfiguration:configuration imageCache:nil]; - [self setSharedPreconfiguredRemoteImageManager:sharedManager]; -} - -+ (void)setSharedImageManagerWithConfiguration:(nullable NSURLSessionConfiguration *)configuration - imageCache:(nullable id)imageCache -{ - NSAssert(sharedDownloader == nil, @"Singleton has been created and session can no longer be configured."); - PINRemoteImageManager *sharedManager = [self PINRemoteImageManagerWithConfiguration:configuration imageCache:imageCache]; - [self setSharedPreconfiguredRemoteImageManager:sharedManager]; -} - -static dispatch_once_t shared_init_predicate; - -+ (void)setSharedPreconfiguredRemoteImageManager:(PINRemoteImageManager *)preconfiguredPINRemoteImageManager -{ - NSAssert(preconfiguredPINRemoteImageManager != nil, @"setSharedPreconfiguredRemoteImageManager requires a non-nil parameter"); - NSAssert1(sharedPINRemoteImageManager == nil, @"An instance of %@ has been set. Either configuration or preconfigured image manager can be set at a time and only once.", [[sharedPINRemoteImageManager class] description]); - - dispatch_once(&shared_init_predicate, ^{ - sharedPINRemoteImageManager = preconfiguredPINRemoteImageManager; - }); -} - -+ (PINRemoteImageManager *)PINRemoteImageManagerWithConfiguration:(nullable NSURLSessionConfiguration *)configuration imageCache:(nullable id)imageCache -{ - PINRemoteImageManager *manager = nil; -#if DEBUG - // Check that Carthage users have linked both PINRemoteImage & PINCache by testing for one file each - if (!(NSClassFromString(@"PINRemoteImageManager"))) { - NSException *e = [NSException - exceptionWithName:@"FrameworkSetupException" - reason:@"Missing the path to the PINRemoteImage framework." - userInfo:nil]; - @throw e; - } - if (!(NSClassFromString(@"PINCache"))) { - NSException *e = [NSException - exceptionWithName:@"FrameworkSetupException" - reason:@"Missing the path to the PINCache framework." - userInfo:nil]; - @throw e; - } -#endif -#if PIN_ANIMATED_AVAILABLE - manager = [[ASPINRemoteImageManager alloc] initWithSessionConfiguration:configuration - alternativeRepresentationProvider:[self sharedDownloader] - imageCache:imageCache]; -#else - manager = [[ASPINRemoteImageManager alloc] initWithSessionConfiguration:configuration - alternativeRepresentationProvider:nil - imageCache:imageCache]; -#endif - return manager; -} - -- (PINRemoteImageManager *)sharedPINRemoteImageManager -{ - dispatch_once(&shared_init_predicate, ^{ - sharedPINRemoteImageManager = [ASPINRemoteImageDownloader PINRemoteImageManagerWithConfiguration:nil imageCache:nil]; - }); - return sharedPINRemoteImageManager; -} - -- (BOOL)sharedImageManagerSupportsMemoryRemoval -{ - static BOOL sharedImageManagerSupportsMemoryRemoval = NO; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedImageManagerSupportsMemoryRemoval = [[[self sharedPINRemoteImageManager] cache] respondsToSelector:@selector(removeObjectForKeyFromMemory:)]; - }); - return sharedImageManagerSupportsMemoryRemoval; -} - -#pragma mark ASImageProtocols - -#if PIN_ANIMATED_AVAILABLE -- (nullable id )animatedImageWithData:(NSData *)animatedImageData -{ - return [[PINCachedAnimatedImage alloc] initWithAnimatedImageData:animatedImageData]; -} -#endif - -- (id )synchronouslyFetchedCachedImageWithURL:(NSURL *)URL; -{ - PINRemoteImageManager *manager = [self sharedPINRemoteImageManager]; - PINRemoteImageManagerResult *result = [manager synchronousImageFromCacheWithURL:URL processorKey:nil options:PINRemoteImageManagerDownloadOptionsSkipDecode]; - -#if PIN_ANIMATED_AVAILABLE - if (result.alternativeRepresentation) { - return result.alternativeRepresentation; - } -#endif - return result.image; -} - -- (void)cachedImageWithURL:(NSURL *)URL - callbackQueue:(dispatch_queue_t)callbackQueue - completion:(ASImageCacherCompletion)completion -{ - [[self sharedPINRemoteImageManager] imageFromCacheWithURL:URL processorKey:nil options:PINRemoteImageManagerDownloadOptionsSkipDecode completion:^(PINRemoteImageManagerResult * _Nonnull result) { - [ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{ -#if PIN_ANIMATED_AVAILABLE - if (result.alternativeRepresentation) { - completion(result.alternativeRepresentation); - } else { - completion(result.image); - } -#else - completion(result.image); -#endif - }]; - }]; -} - -- (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL -{ - if ([self sharedImageManagerSupportsMemoryRemoval]) { - PINRemoteImageManager *manager = [self sharedPINRemoteImageManager]; - NSString *key = [manager cacheKeyForURL:URL processorKey:nil]; - [[manager cache] removeObjectForKeyFromMemory:key]; - } -} - -- (nullable id)downloadImageWithURL:(NSURL *)URL - callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(ASImageDownloaderProgress)downloadProgress - completion:(ASImageDownloaderCompletion)completion; -{ - return [self downloadImageWithURL:URL - priority:ASImageDownloaderPriorityImminent // maps to default priority - callbackQueue:callbackQueue - downloadProgress:downloadProgress - completion:completion]; -} - -- (nullable id)downloadImageWithURL:(NSURL *)URL - priority:(ASImageDownloaderPriority)priority - callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(ASImageDownloaderProgress)downloadProgress - completion:(ASImageDownloaderCompletion)completion -{ - PINRemoteImageManagerPriority pi_priority = PINRemoteImageManagerPriorityWithASImageDownloaderPriority(priority); - - PINRemoteImageManagerProgressDownload progressDownload = ^(int64_t completedBytes, int64_t totalBytes) { - if (downloadProgress == nil) { return; } - - [ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{ - downloadProgress(completedBytes / (CGFloat)totalBytes); - }]; - }; - - PINRemoteImageManagerImageCompletion imageCompletion = ^(PINRemoteImageManagerResult * _Nonnull result) { - [ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{ -#if PIN_ANIMATED_AVAILABLE - if (result.alternativeRepresentation) { - completion(result.alternativeRepresentation, result.error, result.UUID, result); - } else { - completion(result.image, result.error, result.UUID, result); - } -#else - completion(result.image, result.error, result.UUID, result); -#endif - }]; - }; - - // add "IgnoreCache" option since we have a caching API so we already checked it, not worth checking again. - // PINRemoteImage is responsible for coalescing downloads, and even if it wasn't, the tiny probability of - // extra downloads isn't worth the effort of rechecking caches every single time. In order to provide - // feedback to the consumer about whether images are cached, we can't simply make the cache a no-op and - // check the cache as part of this download. - return [[self sharedPINRemoteImageManager] downloadImageWithURL:URL - options:PINRemoteImageManagerDownloadOptionsSkipDecode | PINRemoteImageManagerDownloadOptionsIgnoreCache - priority:pi_priority - progressImage:nil - progressDownload:progressDownload - completion:imageCompletion]; -} - -- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier -{ - ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - [[self sharedPINRemoteImageManager] cancelTaskWithUUID:downloadIdentifier storeResumeData:NO]; -} - -- (void)cancelImageDownloadWithResumePossibilityForIdentifier:(id)downloadIdentifier -{ - ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - [[self sharedPINRemoteImageManager] cancelTaskWithUUID:downloadIdentifier storeResumeData:YES]; -} - -- (void)setProgressImageBlock:(ASImageDownloaderProgressImage)progressBlock callbackQueue:(dispatch_queue_t)callbackQueue withDownloadIdentifier:(id)downloadIdentifier -{ - ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - - if (progressBlock) { - [[self sharedPINRemoteImageManager] setProgressImageCallback:^(PINRemoteImageManagerResult * _Nonnull result) { - dispatch_async(callbackQueue, ^{ - progressBlock(result.image, result.renderedImageQuality, result.UUID); - }); - } ofTaskWithUUID:downloadIdentifier]; - } else { - [[self sharedPINRemoteImageManager] setProgressImageCallback:nil ofTaskWithUUID:downloadIdentifier]; - } -} - -- (void)setPriority:(ASImageDownloaderPriority)priority withDownloadIdentifier:(id)downloadIdentifier -{ - ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - - PINRemoteImageManagerPriority pi_priority = PINRemoteImageManagerPriorityWithASImageDownloaderPriority(priority); - [[self sharedPINRemoteImageManager] setPriority:pi_priority ofTaskWithUUID:downloadIdentifier]; -} - -#pragma mark - PINRemoteImageManagerAlternateRepresentationProvider - -- (id)alternateRepresentationWithData:(NSData *)data options:(PINRemoteImageManagerDownloadOptions)options -{ -#if PIN_ANIMATED_AVAILABLE - if ([data pin_isAnimatedGIF]) { - return data; - } -#if PIN_WEBP_AVAILABLE - else if ([data pin_isAnimatedWebP]) { - return data; - } -#endif - -#endif - return nil; -} - -#pragma mark - Private - -/** - * If on main thread and queue is main, perform now. - * If queue is nil, assert and perform now. - * Otherwise, dispatch async to queue. - */ -+ (void)_performWithCallbackQueue:(dispatch_queue_t)queue work:(void (^)(void))work -{ - if (work == nil) { - // No need to assert here, really. We aren't expecting any feedback from this method. - return; - } - - if (ASDisplayNodeThreadIsMain() && queue == dispatch_get_main_queue()) { - work(); - } else if (queue == nil) { - ASDisplayNodeFailAssert(@"Callback queue should not be nil."); - work(); - } else { - dispatch_async(queue, work); - } -} - -@end -#endif - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASPageTable.h b/submodules/AsyncDisplayKit/Source/ASPageTable.h deleted file mode 100644 index 7e01e933a6..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPageTable.h +++ /dev/null @@ -1,124 +0,0 @@ -// -// ASPageTable.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import -#import - -@class ASCollectionElement; - -NS_ASSUME_NONNULL_BEGIN - -/** - * Represents x and y coordinates of a page. - */ -typedef uintptr_t ASPageCoordinate; - -/** - * Returns a page coordinate with the given x and y values. Both of them must be less than 65,535. - */ -AS_EXTERN ASPageCoordinate ASPageCoordinateMake(uint16_t x, uint16_t y) AS_WARN_UNUSED_RESULT; - -/** - * Returns coordinate of the page that contains the specified point. - * Similar to CGRectContainsPoint, a point is considered inside a page if its lie inside the page or on the minimum X or minimum Y edge. - * - * @param point The point that the page at the returned should contain. Any negative of the point will be corrected to 0.0 - * - * @param pageSize The size of each page. - */ -AS_EXTERN ASPageCoordinate ASPageCoordinateForPageThatContainsPoint(CGPoint point, CGSize pageSize) AS_WARN_UNUSED_RESULT; - -AS_EXTERN uint16_t ASPageCoordinateGetX(ASPageCoordinate pageCoordinate) AS_WARN_UNUSED_RESULT; - -AS_EXTERN uint16_t ASPageCoordinateGetY(ASPageCoordinate pageCoordinate) AS_WARN_UNUSED_RESULT; - -AS_EXTERN CGRect ASPageCoordinateGetPageRect(ASPageCoordinate pageCoordinate, CGSize pageSize) AS_WARN_UNUSED_RESULT; - -/** - * Returns coordinate pointers for pages that intersect the specified rect. For each pointer, use ASPageCoordinateFromPointer() to get the original coordinate. - * The specified rect is restricted to the bounds of a content rect that has an origin of {0, 0} and a size of the given contentSize. - * - * @param rect The rect intersecting the target pages. - * - * @param contentSize The combined size of all pages. - * - * @param pageSize The size of each page. - */ -AS_EXTERN NSPointerArray * _Nullable ASPageCoordinatesForPagesThatIntersectRect(CGRect rect, CGSize contentSize, CGSize pageSize) AS_WARN_UNUSED_RESULT; - -/** - * An alias for an NSMapTable created to store objects using ASPageCoordinates as keys. - * - * You should not call -objectForKey:, -setObject:forKey:, or -removeObjectForKey: - * on these objects. - */ -typedef NSMapTable ASPageTable; - -/** - * A page to array of layout attributes table. - */ -typedef ASPageTable *> ASPageToLayoutAttributesTable; - -/** - * A category for creating & using map tables meant for storing objects using ASPage as keys. - */ -@interface NSMapTable (ASPageTableMethods) - -/** - * Creates a new page table with (NSMapTableStrongMemory | NSMapTableObjectPointerPersonality) for values. - */ -+ (ASPageTable *)pageTableForStrongObjectPointers NS_RETURNS_RETAINED; - -/** - * Creates a new page table with (NSMapTableWeakMemory | NSMapTableObjectPointerPersonality) for values. - */ -+ (ASPageTable *)pageTableForWeakObjectPointers NS_RETURNS_RETAINED; - -/** - * Builds a new page to layout attributes from the given layout attributes. - * - * @param layoutAttributesEnumerator The layout attributes to build from - * - * @param contentSize The combined size of all pages. - * - * @param pageSize The size of each page. - */ -+ (ASPageToLayoutAttributesTable *)pageTableWithLayoutAttributes:(id)layoutAttributesEnumerator contentSize:(CGSize)contentSize pageSize:(CGSize)pageSize NS_RETURNS_RETAINED; - -/** - * Retrieves the object for a given page, or nil if the page is not found. - * - * @param page A page to lookup the object for. - */ -- (nullable ObjectType)objectForPage:(ASPageCoordinate)page; - -/** - * Sets the given object for the associated page. - * - * @param object The object to store as value. - * - * @param page The page to use for the rect. - */ -- (void)setObject:(ObjectType)object forPage:(ASPageCoordinate)page; - -/** - * Removes the object for the given page, if one exists. - * - * @param page The page to remove. - */ -- (void)removeObjectForPage:(ASPageCoordinate)page; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASPageTable.mm b/submodules/AsyncDisplayKit/Source/ASPageTable.mm deleted file mode 100644 index af74d0c59e..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPageTable.mm +++ /dev/null @@ -1,151 +0,0 @@ -// -// ASPageTable.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -ASPageCoordinate ASPageCoordinateMake(uint16_t x, uint16_t y) -{ - // Add 1 to the end result because 0 is not accepted by NSArray and NSMapTable. - // To avoid overflow after adding, x and y can't be UINT16_MAX (0xFFFF) **at the same time**. - // But for API simplification, we enforce the same restriction to both values. - ASDisplayNodeCAssert(x < UINT16_MAX, @"x coordinate must be less than 65,535"); - ASDisplayNodeCAssert(y < UINT16_MAX, @"y coordinate must be less than 65,535"); - return (x << 16) + y + 1; -} - -ASPageCoordinate ASPageCoordinateForPageThatContainsPoint(CGPoint point, CGSize pageSize) -{ - return ASPageCoordinateMake((MAX(0.0, point.x) / pageSize.width), (MAX(0.0, point.y) / pageSize.height)); -} - -uint16_t ASPageCoordinateGetX(ASPageCoordinate pageCoordinate) -{ - return (pageCoordinate - 1) >> 16; -} - -uint16_t ASPageCoordinateGetY(ASPageCoordinate pageCoordinate) -{ - return (pageCoordinate - 1) & ~(0xFFFF<<16); -} - -CGRect ASPageCoordinateGetPageRect(ASPageCoordinate pageCoordinate, CGSize pageSize) -{ - CGFloat pageWidth = pageSize.width; - CGFloat pageHeight = pageSize.height; - return CGRectMake(ASPageCoordinateGetX(pageCoordinate) * pageWidth, ASPageCoordinateGetY(pageCoordinate) * pageHeight, pageWidth, pageHeight); -} - -NSPointerArray *ASPageCoordinatesForPagesThatIntersectRect(CGRect rect, CGSize contentSize, CGSize pageSize) -{ - CGRect contentRect = CGRectMake(0.0, 0.0, contentSize.width, contentSize.height); - // Make sure the specified rect is within contentRect - rect = CGRectIntersection(rect, contentRect); - if (CGRectIsNull(rect) || CGRectIsEmpty(rect)) { - return nil; - } - - NSPointerArray *result = [NSPointerArray pointerArrayWithOptions:(NSPointerFunctionsIntegerPersonality | NSPointerFunctionsOpaqueMemory)]; - - ASPageCoordinate minPage = ASPageCoordinateForPageThatContainsPoint(CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect)), pageSize); - ASPageCoordinate maxPage = ASPageCoordinateForPageThatContainsPoint(CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect)), pageSize); - if (minPage == maxPage) { - [result addPointer:(void *)minPage]; - return result; - } - - NSUInteger minX = ASPageCoordinateGetX(minPage); - NSUInteger minY = ASPageCoordinateGetY(minPage); - NSUInteger maxX = ASPageCoordinateGetX(maxPage); - NSUInteger maxY = ASPageCoordinateGetY(maxPage); - - for (NSUInteger x = minX; x <= maxX; x++) { - for (NSUInteger y = minY; y <= maxY; y++) { - ASPageCoordinate page = ASPageCoordinateMake(x, y); - [result addPointer:(void *)page]; - } - } - - return result; -} - -@implementation NSMapTable (ASPageTableMethods) - -+ (instancetype)pageTableWithValuePointerFunctions:(NSPointerFunctions *)valueFuncs NS_RETURNS_RETAINED -{ - static NSPointerFunctions *pageCoordinatesFuncs; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - pageCoordinatesFuncs = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsIntegerPersonality | NSPointerFunctionsOpaqueMemory]; - }); - - return [[NSMapTable alloc] initWithKeyPointerFunctions:pageCoordinatesFuncs valuePointerFunctions:valueFuncs capacity:0]; -} - -+ (ASPageTable *)pageTableForStrongObjectPointers NS_RETURNS_RETAINED -{ - static NSPointerFunctions *strongObjectPointerFuncs; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - strongObjectPointerFuncs = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory]; - }); - return [self pageTableWithValuePointerFunctions:strongObjectPointerFuncs]; -} - -+ (ASPageTable *)pageTableForWeakObjectPointers NS_RETURNS_RETAINED -{ - static NSPointerFunctions *weakObjectPointerFuncs; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - weakObjectPointerFuncs = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsWeakMemory]; - }); - return [self pageTableWithValuePointerFunctions:weakObjectPointerFuncs]; -} - -+ (ASPageToLayoutAttributesTable *)pageTableWithLayoutAttributes:(id)layoutAttributesEnumerator contentSize:(CGSize)contentSize pageSize:(CGSize)pageSize NS_RETURNS_RETAINED -{ - ASPageToLayoutAttributesTable *result = [ASPageTable pageTableForStrongObjectPointers]; - for (UICollectionViewLayoutAttributes *attrs in layoutAttributesEnumerator) { - // This attrs may span multiple pages. Make sure it's registered to all of them - NSPointerArray *pages = ASPageCoordinatesForPagesThatIntersectRect(attrs.frame, contentSize, pageSize); - - for (id pagePtr in pages) { - ASPageCoordinate page = (ASPageCoordinate)pagePtr; - NSMutableArray *attrsInPage = [result objectForPage:page]; - if (attrsInPage == nil) { - attrsInPage = [[NSMutableArray alloc] init]; - [result setObject:attrsInPage forPage:page]; - } - [attrsInPage addObject:attrs]; - } - } - return result; -} - -- (id)objectForPage:(ASPageCoordinate)page -{ - __unsafe_unretained id key = (__bridge id)(void *)page; - return [self objectForKey:key]; -} - -- (void)setObject:(id)object forPage:(ASPageCoordinate)page -{ - __unsafe_unretained id key = (__bridge id)(void *)page; - [self setObject:object forKey:key]; -} - -- (void)removeObjectForPage:(ASPageCoordinate)page -{ - __unsafe_unretained id key = (__bridge id)(void *)page; - [self removeObjectForKey:key]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASPagerFlowLayout.h b/submodules/AsyncDisplayKit/Source/ASPagerFlowLayout.h deleted file mode 100644 index 8b2f7e2684..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPagerFlowLayout.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// ASPagerFlowLayout.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 - -NS_ASSUME_NONNULL_BEGIN - -@interface ASPagerFlowLayout : UICollectionViewFlowLayout - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASPagerFlowLayout.mm b/submodules/AsyncDisplayKit/Source/ASPagerFlowLayout.mm deleted file mode 100644 index de20b08961..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPagerFlowLayout.mm +++ /dev/null @@ -1,113 +0,0 @@ -// -// ASPagerFlowLayout.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 -#import -#import - -@interface ASPagerFlowLayout () { - __weak ASCellNode *_currentCellNode; -} - -@end - -//TODO make this an ASCollectionViewLayout -@implementation ASPagerFlowLayout - -- (ASCollectionView *)asCollectionView -{ - // Dynamic cast is too slow and not worth it. - return (ASCollectionView *)self.collectionView; -} - -- (void)prepareLayout -{ - [super prepareLayout]; - if (_currentCellNode == nil) { - [self _updateCurrentNode]; - } -} - -- (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 (!self.collectionView.decelerating && !self.collectionView.tracking) { - NSIndexPath *indexPath = [self.asCollectionView indexPathForNode:_currentCellNode]; - if (indexPath) { - return [self _targetContentOffsetForItemAtIndexPath:indexPath proposedContentOffset:proposedContentOffset]; - } - } - - return [super targetContentOffsetForProposedContentOffset:proposedContentOffset]; -} - -- (CGPoint)_targetContentOffsetForItemAtIndexPath:(NSIndexPath *)indexPath proposedContentOffset:(CGPoint)proposedContentOffset -{ - if ([self _dataSourceIsEmpty]) { - return proposedContentOffset; - } - - UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; - 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); -} - -- (void)_updateCurrentNode -{ - // Never change node during an animated bounds change (rotation) - // NOTE! Listening for -prepareForAnimatedBoundsChange and -finalizeAnimatedBoundsChange - // isn't sufficient here! It's broken! - NSArray *animKeys = self.collectionView.layer.animationKeys; - for (NSString *key in animKeys) { - if ([key hasPrefix:@"bounds"]) { - return; - } - } - - CGRect bounds = self.collectionView.bounds; - CGRect rect = CGRectMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds), 1, 1); - - NSIndexPath *indexPath = [self layoutAttributesForElementsInRect:rect].firstObject.indexPath; - if (indexPath) { - ASCellNode *node = [self.asCollectionView nodeForItemAtIndexPath:indexPath]; - if (node) { - _currentCellNode = node; - } - } -} - -- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds -{ - [self _updateCurrentNode]; - return [super shouldInvalidateLayoutForBoundsChange:newBounds]; -} - -- (UICollectionViewLayoutInvalidationContext *)invalidationContextForBoundsChange:(CGRect)newBounds -{ - UICollectionViewFlowLayoutInvalidationContext *ctx = (UICollectionViewFlowLayoutInvalidationContext *)[super invalidationContextForBoundsChange:newBounds]; - ctx.invalidateFlowLayoutDelegateMetrics = YES; - ctx.invalidateFlowLayoutAttributes = YES; - return ctx; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASPagerNode+Beta.h b/submodules/AsyncDisplayKit/Source/ASPagerNode+Beta.h deleted file mode 100644 index c4de4f45de..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPagerNode+Beta.h +++ /dev/null @@ -1,17 +0,0 @@ -// -// ASPagerNode+Beta.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK -#import - -@interface ASPagerNode (Beta) - -- (instancetype)initUsingAsyncCollectionLayout; - -@end -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASPagerNode.h b/submodules/AsyncDisplayKit/Source/ASPagerNode.h deleted file mode 100644 index b20f187f67..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPagerNode.h +++ /dev/null @@ -1,137 +0,0 @@ -// -// ASPagerNode.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 - -@class ASPagerNode; -@class ASPagerFlowLayout; - -NS_ASSUME_NONNULL_BEGIN - -#define ASPagerNodeDataSource ASPagerDataSource -@protocol ASPagerDataSource - -/** - * This method replaces -collectionView:numberOfItemsInSection: - * - * @param pagerNode The sender. - * @return The total number of pages that can display in the pagerNode. - */ -- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode; - -@optional - -/** - * This method replaces -collectionView:nodeForItemAtIndexPath: - * - * @param pagerNode The sender. - * @param index The index of the requested node. - * @return a node for display at this index. 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 *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; - -/** - * This method replaces -collectionView:nodeBlockForItemAtIndexPath: - * This method takes precedence over pagerNode:nodeAtIndex: if implemented. - * - * @param pagerNode The sender. - * @param index The index of the requested node. - * @return a block that creates the node for display at this index. - * 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)pagerNode:(ASPagerNode *)pagerNode nodeBlockAtIndex:(NSInteger)index; - -@end - -@protocol ASPagerDelegate -@end - -/** - * A horizontal, paging collection node. - */ -@interface ASPagerNode : ASCollectionNode - -/** - * Configures a default horizontal, paging flow layout with 0 inter-item spacing. - */ -- (instancetype)init; - -/** - * Initializer with custom-configured flow layout properties. - * - * NOTE: The flow layout must have a horizontal scroll direction. - */ -- (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout; - -/** - * Data Source is required, and uses a different protocol from ASCollectionNode. - */ -- (void)setDataSource:(nullable id )dataSource; -- (nullable id )dataSource AS_WARN_UNUSED_RESULT; - -/** - * Delegate is optional. - * This includes UIScrollViewDelegate as well as most methods from UICollectionViewDelegate, like willDisplay... - */ -- (void)setDelegate:(nullable id )delegate; -- (nullable id )delegate AS_WARN_UNUSED_RESULT; - -/** - * The underlying ASCollectionView object. - */ -@property (readonly) ASCollectionView *view; - -/** - * Returns the current page index. Main thread only. - */ -@property (nonatomic, readonly) NSInteger currentPageIndex; - -/** - * Scroll the contents of the receiver to ensure that the page is visible - */ -- (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated; - -/** - * Returns the node for the passed page index - */ -- (ASCellNode *)nodeForPageAtIndex:(NSInteger)index AS_WARN_UNUSED_RESULT; - -/** - * Returns the index of the page for the cell passed or NSNotFound - */ -- (NSInteger)indexOfPageWithNode:(ASCellNode *)node; - -/** - * Tells the pager node to allow its view controller to automatically adjust its content insets. - * - * @see UIViewController.automaticallyAdjustsScrollViewInsets - * - * @discussion ASPagerNode should usually not have its content insets automatically adjusted - * because it scrolls horizontally, and flow layout will log errors because the pages - * do not fit between the top & bottom insets of the collection view. - * - * The default value is NO, which means that ASPagerNode expects that its view controller will - * have automaticallyAdjustsScrollViewInsets=NO. - * - * If this property is NO, but your view controller has automaticallyAdjustsScrollViewInsets=YES, - * the pager node will set the property on the view controller to NO and log a warning message. In the future, - * the pager node will just log the warning, and you'll need to configure your view controller on your own. - */ -@property (nonatomic) BOOL allowsAutomaticInsetsAdjustment; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASPagerNode.mm b/submodules/AsyncDisplayKit/Source/ASPagerNode.mm deleted file mode 100644 index 73c9987c68..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPagerNode.mm +++ /dev/null @@ -1,234 +0,0 @@ -// -// ASPagerNode.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 -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -@interface ASPagerNode () -{ - __weak id _pagerDataSource; - ASPagerNodeProxy *_proxyDataSource; - struct { - unsigned nodeBlockAtIndex:1; - unsigned nodeAtIndex:1; - } _pagerDataSourceFlags; - - __weak id _pagerDelegate; - ASPagerNodeProxy *_proxyDelegate; -} - -@end - -@implementation ASPagerNode - -@dynamic view, delegate, dataSource; - -#pragma mark - Lifecycle - -- (instancetype)init -{ - ASPagerFlowLayout *flowLayout = [[ASPagerFlowLayout alloc] init]; - flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; - flowLayout.minimumInteritemSpacing = 0; - flowLayout.minimumLineSpacing = 0; - - return [self initWithCollectionViewLayout:flowLayout]; -} - -- (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout; -{ - ASDisplayNodeAssert([flowLayout isKindOfClass:[ASPagerFlowLayout class]], @"ASPagerNode requires a flow layout."); - ASDisplayNodeAssertTrue(flowLayout.scrollDirection == UICollectionViewScrollDirectionHorizontal); - self = [super initWithCollectionViewLayout:flowLayout]; - return self; -} - -- (instancetype)initUsingAsyncCollectionLayout -{ - ASCollectionGalleryLayoutDelegate *layoutDelegate = [[ASCollectionGalleryLayoutDelegate alloc] initWithScrollableDirections:ASScrollDirectionHorizontalDirections]; - self = [super initWithLayoutDelegate:layoutDelegate layoutFacilitator:nil]; - if (self) { - layoutDelegate.propertiesProvider = self; - } - return self; -} - -#pragma mark - ASDisplayNode - -- (void)didLoad -{ - [super didLoad]; - - ASCollectionView *cv = self.view; - cv.asyncDataSource = (id)_proxyDataSource ?: self; - cv.asyncDelegate = (id)_proxyDelegate ?: self; -#if TARGET_OS_IOS - cv.pagingEnabled = YES; - cv.scrollsToTop = NO; -#endif - cv.allowsSelection = NO; - cv.showsVerticalScrollIndicator = NO; - cv.showsHorizontalScrollIndicator = NO; - - ASRangeTuningParameters minimumRenderParams = { .leadingBufferScreenfuls = 0.0, .trailingBufferScreenfuls = 0.0 }; - ASRangeTuningParameters minimumPreloadParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; - [self setTuningParameters:minimumRenderParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypeDisplay]; - [self setTuningParameters:minimumPreloadParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypePreload]; - - ASRangeTuningParameters fullRenderParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; - ASRangeTuningParameters fullPreloadParams = { .leadingBufferScreenfuls = 2.0, .trailingBufferScreenfuls = 2.0 }; - [self setTuningParameters:fullRenderParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypeDisplay]; - [self setTuningParameters:fullPreloadParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypePreload]; -} - -#pragma mark - Getters / Setters - -- (NSInteger)currentPageIndex -{ - return (self.view.contentOffset.x / [self pageSize].width); -} - -- (CGSize)pageSize -{ - UIEdgeInsets contentInset = self.contentInset; - CGSize pageSize = self.bounds.size; - pageSize.height -= (contentInset.top + contentInset.bottom); - return pageSize; -} - -#pragma mark - Helpers - -- (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated -{ - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0]; - [self scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:animated]; -} - -- (ASCellNode *)nodeForPageAtIndex:(NSInteger)index -{ - return [self nodeForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]]; -} - -- (NSInteger)indexOfPageWithNode:(ASCellNode *)node -{ - NSIndexPath *indexPath = [self indexPathForNode:node]; - if (!indexPath) { - return NSNotFound; - } - return indexPath.row; -} - -#pragma mark - ASCollectionGalleryLayoutPropertiesProviding - -- (CGSize)galleryLayoutDelegate:(nonnull ASCollectionGalleryLayoutDelegate *)delegate sizeForElements:(nonnull ASElementMap *)elements -{ - ASDisplayNodeAssertMainThread(); - return [self pageSize]; -} - -#pragma mark - ASCollectionDataSource - -- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath -{ - if (_pagerDataSourceFlags.nodeBlockAtIndex) { - return [_pagerDataSource pagerNode:self nodeBlockAtIndex:indexPath.item]; - } else if (_pagerDataSourceFlags.nodeAtIndex) { - ASCellNode *node = [_pagerDataSource pagerNode:self nodeAtIndex:indexPath.item]; - return ^{ return node; }; - } else { - ASDisplayNodeFailAssert(@"Pager data source must implement either %@ or %@. Data source: %@", NSStringFromSelector(@selector(pagerNode:nodeBlockAtIndex:)), NSStringFromSelector(@selector(pagerNode:nodeAtIndex:)), _pagerDataSource); - return ^{ - return [[ASCellNode alloc] init]; - }; - } -} - -- (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section -{ - ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display"); - return [_pagerDataSource numberOfPagesInPagerNode:self]; -} - -#pragma mark - ASCollectionDelegate - -- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode constrainedSizeForItemAtIndexPath:(NSIndexPath *)indexPath -{ - return ASSizeRangeMake([self pageSize]); -} - -#pragma mark - Data Source Proxy - -- (id )dataSource -{ - return _pagerDataSource; -} - -- (void)setDataSource:(id )dataSource -{ - if (dataSource != _pagerDataSource) { - _pagerDataSource = dataSource; - - if (dataSource == nil) { - memset(&_pagerDataSourceFlags, 0, sizeof(_pagerDataSourceFlags)); - } else { - _pagerDataSourceFlags.nodeBlockAtIndex = [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeBlockAtIndex:)]; - _pagerDataSourceFlags.nodeAtIndex = [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeAtIndex:)]; - } - - _proxyDataSource = dataSource ? [[ASPagerNodeProxy alloc] initWithTarget:dataSource interceptor:self] : nil; - - super.dataSource = (id )_proxyDataSource; - } -} - -- (void)setDelegate:(id)delegate -{ - if (delegate != _pagerDelegate) { - _pagerDelegate = delegate; - _proxyDelegate = delegate ? [[ASPagerNodeProxy alloc] initWithTarget:delegate interceptor:self] : nil; - super.delegate = (id )_proxyDelegate; - } -} - -- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy -{ - [self setDataSource:nil]; - [self setDelegate:nil]; -} - -- (void)didEnterHierarchy -{ - [super didEnterHierarchy]; - - // Check that our view controller does not automatically set our content insets - // In every use case I can imagine, the pager is not hosted inside a range-managed node. - if (_allowsAutomaticInsetsAdjustment == NO) { - UIViewController *vc = [self.view asdk_associatedViewController]; - if (vc.automaticallyAdjustsScrollViewInsets) { - NSLog(@"AsyncDisplayKit: ASPagerNode is setting automaticallyAdjustsScrollViewInsets=NO on its owning view controller %@. This automatic behavior will be disabled in the future. Set allowsAutomaticInsetsAdjustment=YES on the pager node to suppress this behavior.", vc); - vc.automaticallyAdjustsScrollViewInsets = NO; - } - } -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASPhotosFrameworkImageRequest.h b/submodules/AsyncDisplayKit/Source/ASPhotosFrameworkImageRequest.h deleted file mode 100644 index 2ca24bfbee..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPhotosFrameworkImageRequest.h +++ /dev/null @@ -1,73 +0,0 @@ -// -// ASPhotosFrameworkImageRequest.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 - -#if AS_USE_PHOTOS - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -AS_EXTERN NSString *const ASPhotosURLScheme; - -/** - @abstract Use ASPhotosFrameworkImageRequest to encapsulate all the information needed to request an image from - the Photos framework and store it in a URL. - */ -API_AVAILABLE(ios(8.0), tvos(10.0)) -@interface ASPhotosFrameworkImageRequest : NSObject - -- (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier NS_DESIGNATED_INITIALIZER; - -/** - @return A new image request deserialized from `url`, or nil if `url` is not a valid photos URL. - */ -+ (nullable ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url; - -/** - @abstract The asset identifier for this image request provided during initialization. - */ -@property (nonatomic, readonly) NSString *assetIdentifier; - -/** - @abstract The target size for this image request. Defaults to `PHImageManagerMaximumSize`. - */ -@property (nonatomic) CGSize targetSize; - -/** - @abstract The content mode for this image request. Defaults to `PHImageContentModeDefault`. - - @see `PHImageManager` - */ -@property (nonatomic) PHImageContentMode contentMode; - -/** - @abstract The options specified for this request. Default value is the result of `[PHImageRequestOptions new]`. - - @discussion Some properties of this object are ignored when converting this request into a URL. - As of iOS SDK 9.0, these properties are `progressHandler` and `synchronous`. - */ -@property (nonatomic) PHImageRequestOptions *options; - -/** - @return A new URL converted from this request. - */ -@property (nonatomic, readonly) NSURL *url; - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_USE_PHOTOS diff --git a/submodules/AsyncDisplayKit/Source/ASPhotosFrameworkImageRequest.h.orig b/submodules/AsyncDisplayKit/Source/ASPhotosFrameworkImageRequest.h.orig deleted file mode 100644 index bf52121f9e..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPhotosFrameworkImageRequest.h.orig +++ /dev/null @@ -1,81 +0,0 @@ -// -// ASPhotosFrameworkImageRequest.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 - -#if AS_USE_PHOTOS - ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -AS_EXTERN NSString *const ASPhotosURLScheme; - -/** - @abstract Use ASPhotosFrameworkImageRequest to encapsulate all the information needed to request an image from - the Photos framework and store it in a URL. - */ -API_AVAILABLE(ios(8.0), tvos(10.0)) -@interface ASPhotosFrameworkImageRequest : NSObject - -- (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier NS_DESIGNATED_INITIALIZER; - -/** - @return A new image request deserialized from `url`, or nil if `url` is not a valid photos URL. - */ -+ (nullable ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url; - -/** - @abstract The asset identifier for this image request provided during initialization. - */ -@property (nonatomic, readonly) NSString *assetIdentifier; - -/** - @abstract The target size for this image request. Defaults to `PHImageManagerMaximumSize`. - */ -@property (nonatomic) CGSize targetSize; - -/** - @abstract The content mode for this image request. Defaults to `PHImageContentModeDefault`. - - @see `PHImageManager` - */ -@property (nonatomic) PHImageContentMode contentMode; - -/** - @abstract The options specified for this request. Default value is the result of `[PHImageRequestOptions new]`. - - @discussion Some properties of this object are ignored when converting this request into a URL. - As of iOS SDK 9.0, these properties are `progressHandler` and `synchronous`. - */ -@property (nonatomic) PHImageRequestOptions *options; - -/** - @return A new URL converted from this request. - */ -@property (nonatomic, readonly) NSURL *url; - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END -<<<<<<< HEAD -#endif -======= - -#endif // AS_USE_PHOTOS ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e diff --git a/submodules/AsyncDisplayKit/Source/ASPhotosFrameworkImageRequest.m.orig b/submodules/AsyncDisplayKit/Source/ASPhotosFrameworkImageRequest.m.orig deleted file mode 100644 index e24ee882b2..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPhotosFrameworkImageRequest.m.orig +++ /dev/null @@ -1,163 +0,0 @@ -// -// ASPhotosFrameworkImageRequest.m -// 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 - -#if AS_USE_PHOTOS - -#import - -NSString *const ASPhotosURLScheme = @"ph"; - -static NSString *const _ASPhotosURLQueryKeyWidth = @"width"; -static NSString *const _ASPhotosURLQueryKeyHeight = @"height"; - -// value is PHImageContentMode value -static NSString *const _ASPhotosURLQueryKeyContentMode = @"contentmode"; - -// value is PHImageRequestOptionsResizeMode value -static NSString *const _ASPhotosURLQueryKeyResizeMode = @"resizemode"; - -// value is PHImageRequestOptionsDeliveryMode value -static NSString *const _ASPhotosURLQueryKeyDeliveryMode = @"deliverymode"; - -// value is PHImageRequestOptionsVersion value -static NSString *const _ASPhotosURLQueryKeyVersion = @"version"; - -// value is 0 or 1 -static NSString *const _ASPhotosURLQueryKeyAllowNetworkAccess = @"network"; - -static NSString *const _ASPhotosURLQueryKeyCropOriginX = @"crop_x"; -static NSString *const _ASPhotosURLQueryKeyCropOriginY = @"crop_y"; -static NSString *const _ASPhotosURLQueryKeyCropWidth = @"crop_w"; -static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; - -@implementation ASPhotosFrameworkImageRequest - -- (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier -{ - self = [super init]; - if (self) { - _assetIdentifier = assetIdentifier; - _options = [PHImageRequestOptions new]; - _contentMode = PHImageContentModeDefault; - _targetSize = PHImageManagerMaximumSize; - } - return self; -} - -#pragma mark NSCopying - -- (id)copyWithZone:(NSZone *)zone -{ - ASPhotosFrameworkImageRequest *copy = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:self.assetIdentifier]; - copy.options = [self.options copy]; - copy.targetSize = self.targetSize; - copy.contentMode = self.contentMode; - return copy; -} - -#pragma mark Converting to URL - -- (NSURL *)url -{ - NSURLComponents *comp = [NSURLComponents new]; - comp.scheme = ASPhotosURLScheme; - comp.host = _assetIdentifier; - NSMutableArray *queryItems = [NSMutableArray arrayWithObjects: - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyWidth value:@(_targetSize.width).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyHeight value:@(_targetSize.height).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyVersion value:@(_options.version).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyContentMode value:@(_contentMode).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyAllowNetworkAccess value:@(_options.networkAccessAllowed).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyResizeMode value:@(_options.resizeMode).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyDeliveryMode value:@(_options.deliveryMode).stringValue] - , nil]; - - CGRect cropRect = _options.normalizedCropRect; - if (!CGRectIsEmpty(cropRect)) { - [queryItems addObjectsFromArray:@[ - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropOriginX value:@(cropRect.origin.x).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropOriginY value:@(cropRect.origin.y).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropWidth value:@(cropRect.size.width).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropHeight value:@(cropRect.size.height).stringValue] - ]]; - } - comp.queryItems = queryItems; - return comp.URL; -} - -#pragma mark Converting from URL - -+ (ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url -{ - // not a photos URL - if (![url.scheme isEqualToString:ASPhotosURLScheme]) { - return nil; - } - - NSURLComponents *comp = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; - - ASPhotosFrameworkImageRequest *request = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:url.host]; - - CGRect cropRect = CGRectZero; - CGSize targetSize = PHImageManagerMaximumSize; - for (NSURLQueryItem *item in comp.queryItems) { - if ([_ASPhotosURLQueryKeyAllowNetworkAccess isEqualToString:item.name]) { - request.options.networkAccessAllowed = item.value.boolValue; - } else if ([_ASPhotosURLQueryKeyWidth isEqualToString:item.name]) { - targetSize.width = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyHeight isEqualToString:item.name]) { - targetSize.height = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyContentMode isEqualToString:item.name]) { - request.contentMode = (PHImageContentMode)item.value.integerValue; - } else if ([_ASPhotosURLQueryKeyVersion isEqualToString:item.name]) { - request.options.version = (PHImageRequestOptionsVersion)item.value.integerValue; - } else if ([_ASPhotosURLQueryKeyCropOriginX isEqualToString:item.name]) { - cropRect.origin.x = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyCropOriginY isEqualToString:item.name]) { - cropRect.origin.y = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyCropWidth isEqualToString:item.name]) { - cropRect.size.width = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyCropHeight isEqualToString:item.name]) { - cropRect.size.height = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyResizeMode isEqualToString:item.name]) { - request.options.resizeMode = (PHImageRequestOptionsResizeMode)item.value.integerValue; - } else if ([_ASPhotosURLQueryKeyDeliveryMode isEqualToString:item.name]) { - request.options.deliveryMode = (PHImageRequestOptionsDeliveryMode)item.value.integerValue; - } - } - request.targetSize = targetSize; - request.options.normalizedCropRect = cropRect; - return request; -} - -#pragma mark NSObject - -- (BOOL)isEqual:(id)object -{ - if (![object isKindOfClass:ASPhotosFrameworkImageRequest.class]) { - return NO; - } - ASPhotosFrameworkImageRequest *other = object; - return [other.assetIdentifier isEqualToString:self.assetIdentifier] && - other.contentMode == self.contentMode && - CGSizeEqualToSize(other.targetSize, self.targetSize) && - CGRectEqualToRect(other.options.normalizedCropRect, self.options.normalizedCropRect) && - other.options.resizeMode == self.options.resizeMode && - other.options.version == self.options.version; -} - -@end -<<<<<<< HEAD -#endif -======= - -#endif // AS_USE_PHOTOS ->>>>>>> 565da7d4935740d12fc204aa061faf093831da1e diff --git a/submodules/AsyncDisplayKit/Source/ASPhotosFrameworkImageRequest.mm b/submodules/AsyncDisplayKit/Source/ASPhotosFrameworkImageRequest.mm deleted file mode 100644 index a7e0b41655..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASPhotosFrameworkImageRequest.mm +++ /dev/null @@ -1,159 +0,0 @@ -// -// ASPhotosFrameworkImageRequest.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 - -#if AS_USE_PHOTOS - -#import - -NSString *const ASPhotosURLScheme = @"ph"; - -static NSString *const _ASPhotosURLQueryKeyWidth = @"width"; -static NSString *const _ASPhotosURLQueryKeyHeight = @"height"; - -// value is PHImageContentMode value -static NSString *const _ASPhotosURLQueryKeyContentMode = @"contentmode"; - -// value is PHImageRequestOptionsResizeMode value -static NSString *const _ASPhotosURLQueryKeyResizeMode = @"resizemode"; - -// value is PHImageRequestOptionsDeliveryMode value -static NSString *const _ASPhotosURLQueryKeyDeliveryMode = @"deliverymode"; - -// value is PHImageRequestOptionsVersion value -static NSString *const _ASPhotosURLQueryKeyVersion = @"version"; - -// value is 0 or 1 -static NSString *const _ASPhotosURLQueryKeyAllowNetworkAccess = @"network"; - -static NSString *const _ASPhotosURLQueryKeyCropOriginX = @"crop_x"; -static NSString *const _ASPhotosURLQueryKeyCropOriginY = @"crop_y"; -static NSString *const _ASPhotosURLQueryKeyCropWidth = @"crop_w"; -static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; - -@implementation ASPhotosFrameworkImageRequest - -- (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier -{ - self = [super init]; - if (self) { - _assetIdentifier = assetIdentifier; - _options = [PHImageRequestOptions new]; - _contentMode = PHImageContentModeDefault; - _targetSize = PHImageManagerMaximumSize; - } - return self; -} - -#pragma mark NSCopying - -- (id)copyWithZone:(NSZone *)zone -{ - ASPhotosFrameworkImageRequest *copy = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:self.assetIdentifier]; - copy.options = [self.options copy]; - copy.targetSize = self.targetSize; - copy.contentMode = self.contentMode; - return copy; -} - -#pragma mark Converting to URL - -- (NSURL *)url -{ - NSURLComponents *comp = [NSURLComponents new]; - comp.scheme = ASPhotosURLScheme; - comp.host = _assetIdentifier; - NSMutableArray *queryItems = [NSMutableArray arrayWithObjects: - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyWidth value:@(_targetSize.width).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyHeight value:@(_targetSize.height).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyVersion value:@(_options.version).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyContentMode value:@(_contentMode).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyAllowNetworkAccess value:@(_options.networkAccessAllowed).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyResizeMode value:@(_options.resizeMode).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyDeliveryMode value:@(_options.deliveryMode).stringValue] - , nil]; - - CGRect cropRect = _options.normalizedCropRect; - if (!CGRectIsEmpty(cropRect)) { - [queryItems addObjectsFromArray:@[ - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropOriginX value:@(cropRect.origin.x).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropOriginY value:@(cropRect.origin.y).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropWidth value:@(cropRect.size.width).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropHeight value:@(cropRect.size.height).stringValue] - ]]; - } - comp.queryItems = queryItems; - return comp.URL; -} - -#pragma mark Converting from URL - -+ (ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url -{ - // not a photos URL - if (![url.scheme isEqualToString:ASPhotosURLScheme]) { - return nil; - } - - NSURLComponents *comp = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; - - ASPhotosFrameworkImageRequest *request = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:url.host]; - - CGRect cropRect = CGRectZero; - CGSize targetSize = PHImageManagerMaximumSize; - for (NSURLQueryItem *item in comp.queryItems) { - if ([_ASPhotosURLQueryKeyAllowNetworkAccess isEqualToString:item.name]) { - request.options.networkAccessAllowed = item.value.boolValue; - } else if ([_ASPhotosURLQueryKeyWidth isEqualToString:item.name]) { - targetSize.width = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyHeight isEqualToString:item.name]) { - targetSize.height = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyContentMode isEqualToString:item.name]) { - request.contentMode = (PHImageContentMode)item.value.integerValue; - } else if ([_ASPhotosURLQueryKeyVersion isEqualToString:item.name]) { - request.options.version = (PHImageRequestOptionsVersion)item.value.integerValue; - } else if ([_ASPhotosURLQueryKeyCropOriginX isEqualToString:item.name]) { - cropRect.origin.x = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyCropOriginY isEqualToString:item.name]) { - cropRect.origin.y = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyCropWidth isEqualToString:item.name]) { - cropRect.size.width = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyCropHeight isEqualToString:item.name]) { - cropRect.size.height = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyResizeMode isEqualToString:item.name]) { - request.options.resizeMode = (PHImageRequestOptionsResizeMode)item.value.integerValue; - } else if ([_ASPhotosURLQueryKeyDeliveryMode isEqualToString:item.name]) { - request.options.deliveryMode = (PHImageRequestOptionsDeliveryMode)item.value.integerValue; - } - } - request.targetSize = targetSize; - request.options.normalizedCropRect = cropRect; - return request; -} - -#pragma mark NSObject - -- (BOOL)isEqual:(id)object -{ - if (![object isKindOfClass:ASPhotosFrameworkImageRequest.class]) { - return NO; - } - ASPhotosFrameworkImageRequest *other = object; - return [other.assetIdentifier isEqualToString:self.assetIdentifier] && - other.contentMode == self.contentMode && - CGSizeEqualToSize(other.targetSize, self.targetSize) && - CGRectEqualToRect(other.options.normalizedCropRect, self.options.normalizedCropRect) && - other.options.resizeMode == self.options.resizeMode && - other.options.version == self.options.version; -} - -@end - -#endif // AS_USE_PHOTOS diff --git a/submodules/AsyncDisplayKit/Source/ASRangeController.h b/submodules/AsyncDisplayKit/Source/ASRangeController.h deleted file mode 100644 index ebdc9befaf..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASRangeController.h +++ /dev/null @@ -1,206 +0,0 @@ -// -// ASRangeController.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 -#import -#import -#import -#import -#import -#import - -#define ASRangeControllerLoggingEnabled 0 - -NS_ASSUME_NONNULL_BEGIN - -@class _ASHierarchyChangeSet; -@protocol ASRangeControllerDataSource; -@protocol ASRangeControllerDelegate; -@protocol ASLayoutController; - -/** - * Working range controller. - * - * Used internally by ASTableView and ASCollectionView. It is paired with ASDataController. - * It is designed to support custom scrolling containers as well. Observes the visible range, maintains - * "working ranges" to trigger network calls and rendering, and is responsible for driving asynchronous layout of cells. - * This includes cancelling those asynchronous operations as cells fall outside of the working ranges. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASRangeController : NSObject -{ - id _layoutController; - __weak id _dataSource; - __weak id _delegate; -} - -/** - * Notify the range controller that the visible range has been updated. - * This is the primary input call that drives updating the working ranges, and triggering their actions. - * The ranges will be updated in the next turn of the main loop, or when -updateIfNeeded is called. - * - * @see [ASRangeControllerDelegate rangeControllerVisibleNodeIndexPaths:] - */ -- (void)setNeedsUpdate; - -/** - * Update the ranges immediately, if -setNeedsUpdate has been called since the last update. - * This is useful because the ranges must be updated immediately after a cell is added - * into a table/collection to satisfy interface state API guarantees. - */ -- (void)updateIfNeeded; - -/** - * Force update the ranges immediately. - */ -- (void)updateRanges; - -/** - * Add the sized node for `indexPath` as a subview of `contentView`. - * - * @param contentView UIView to add a (sized) node's view to. - * - * @param node The cell node to be added. - */ -- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node; - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; - -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType; - -// These methods call the corresponding method on each node, visiting each one that -// the range controller has set a non-default interface state on. -- (void)clearContents; -- (void)clearPreloadedData; - -/** - * An object that describes the layout behavior of the ranged component (table view, collection view, etc.) - * - * Used primarily for providing the current range of index paths and identifying when the - * range controller should invalidate its range. - */ -@property (nonatomic) id layoutController; - -/** - * The underlying data source for the range controller - */ -@property (nonatomic, weak) id dataSource; - -/** - * Delegate for handling range controller events. Must not be nil. - */ -@property (nonatomic, weak) id delegate; - -/** - * Property that indicates whether the scroll view for this range controller has ever changed its contentOffset. - */ -@property (nonatomic) BOOL contentHasBeenScrolled; - -@end - - -/** - * Data source for ASRangeController. - * - * Allows the range controller to perform external queries on the range. - * Ex. range nodes, visible index paths, and viewport size. - */ -@protocol ASRangeControllerDataSource - -/** - * @param rangeController Sender. - * - * @return an table of elements corresponding to the data currently visible onscreen (i.e., the visible range). - */ -- (nullable NSHashTable *)visibleElementsForRangeController:(ASRangeController *)rangeController; - -/** - * @param rangeController Sender. - * - * @return the current scroll direction of the view using this range controller. - */ -- (ASScrollDirection)scrollDirectionForRangeController:(ASRangeController *)rangeController; - -/** - * @param rangeController Sender. - * - * @return the ASInterfaceState of the node that this controller is powering. This allows nested range controllers - * to collaborate with one another, as an outer controller may set bits in .interfaceState such as Visible. - * If this controller is an orthogonally scrolling element, it waits until it is visible to preload outside the viewport. - */ -- (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController; - -- (ASElementMap *)elementMapForRangeController:(ASRangeController *)rangeController; - -- (NSString *)nameForRangeControllerDataSource; - -@end - -/** - * Delegate for ASRangeController. - */ -@protocol ASRangeControllerDelegate - -/** - * Called to update with given change set. - * - * @param changeSet The change set that includes all updates - * - * @param updates The block that performs relevant data updates. - * - * @discussion The updates block must always be executed or the data controller will get into a bad state. - * It should be called at the time the backing view is ready to process the updates, - * i.e inside the updates block of `-[UICollectionView performBatchUpdates:completion:] or after calling `-[UITableView beginUpdates]`. - */ -- (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates; - -- (BOOL)rangeControllerShouldUpdateRanges:(ASRangeController *)rangeController; - -@end - -@interface ASRangeController (ASRangeControllerUpdateRangeProtocol) - -/** - * Update the range mode for a range controller to a explicitly set mode until the node that contains the range - * controller becomes visible again - * - * Logic for the automatic range mode: - * 1. If there are no visible node paths available nothing is to be done and no range update will happen - * 2. The initial range update if the range controller is visible always will be - * ASLayoutRangeModeMinimum as it's the initial fetch - * 3. The range mode set explicitly via updateCurrentRangeWithMode: will last at least one range update. After that it - the range controller will use the explicit set range mode until it becomes visible and a new range update was - triggered or a new range mode via updateCurrentRangeWithMode: is set - * 4. If range mode is not explicitly set the range mode is variying based if the range controller is visible or not - */ -- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; - -@end - -@interface ASRangeController (DebugInternal) - -+ (void)layoutDebugOverlayIfNeeded; - -- (void)addRangeControllerToRangeDebugOverlay; - -- (void)updateRangeController:(ASRangeController *)controller - withScrollableDirections:(ASScrollDirection)scrollableDirections - scrollDirection:(ASScrollDirection)direction - rangeMode:(ASLayoutRangeMode)mode - displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters - preloadTuningParameters:(ASRangeTuningParameters)preloadTuningParameters - interfaceState:(ASInterfaceState)interfaceState; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASRangeController.mm b/submodules/AsyncDisplayKit/Source/ASRangeController.mm deleted file mode 100644 index 31fc79e80e..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASRangeController.mm +++ /dev/null @@ -1,673 +0,0 @@ -// -// ASRangeController.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 - -#import -#import -#import -#import -#import -#import // Required for interfaceState and hierarchyState setter methods. -#import -#import "Private/ASInternalHelpers.h" -#import -#import -#import - -#import -#import - -#define AS_RANGECONTROLLER_LOG_UPDATE_FREQ 0 - -#ifndef ASRangeControllerAutomaticLowMemoryHandling -#define ASRangeControllerAutomaticLowMemoryHandling 1 -#endif - -@interface ASRangeController () -{ - BOOL _rangeIsValid; - BOOL _needsRangeUpdate; - NSSet *_allPreviousIndexPaths; - NSHashTable *_visibleNodes; - ASLayoutRangeMode _currentRangeMode; - BOOL _contentHasBeenScrolled; - BOOL _preserveCurrentRangeMode; - BOOL _didRegisterForNodeDisplayNotifications; - CFTimeInterval _pendingDisplayNodesTimestamp; - - // If the user is not currently scrolling, we will keep our ranges - // configured to match their previous scroll direction. Defaults - // to [.right, .down] so that when the user first opens a screen - // the ranges point down into the content. - ASScrollDirection _previousScrollDirection; - -#if AS_RANGECONTROLLER_LOG_UPDATE_FREQ - NSUInteger _updateCountThisFrame; - CADisplayLink *_displayLink; -#endif -} - -@end - -static UIApplicationState __ApplicationState = UIApplicationStateActive; - -@implementation ASRangeController - -#pragma mark - Lifecycle - -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - - _rangeIsValid = YES; - _currentRangeMode = ASLayoutRangeModeUnspecified; - _contentHasBeenScrolled = NO; - _preserveCurrentRangeMode = NO; - _previousScrollDirection = ASScrollDirectionDown | ASScrollDirectionRight; - - [[[self class] allRangeControllersWeakSet] addObject:self]; - -#if AS_RANGECONTROLLER_LOG_UPDATE_FREQ - _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_updateCountDisplayLinkDidFire)]; - [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; -#endif - - if (ASDisplayNode.shouldShowRangeDebugOverlay) { - [self addRangeControllerToRangeDebugOverlay]; - } - - return self; -} - -- (void)dealloc -{ -#if AS_RANGECONTROLLER_LOG_UPDATE_FREQ - [_displayLink invalidate]; -#endif - - if (_didRegisterForNodeDisplayNotifications) { - [[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil]; - } -} - -#pragma mark - Core visible node range management API - -+ (BOOL)isFirstRangeUpdateForRangeMode:(ASLayoutRangeMode)rangeMode -{ - return (rangeMode == ASLayoutRangeModeUnspecified); -} - -+ (ASLayoutRangeMode)rangeModeForInterfaceState:(ASInterfaceState)interfaceState - currentRangeMode:(ASLayoutRangeMode)currentRangeMode -{ - BOOL isVisible = (ASInterfaceStateIncludesVisible(interfaceState)); - BOOL isFirstRangeUpdate = [self isFirstRangeUpdateForRangeMode:currentRangeMode]; - if (!isVisible || isFirstRangeUpdate) { - return ASLayoutRangeModeMinimum; - } - - return ASLayoutRangeModeFull; -} - -- (ASInterfaceState)interfaceState -{ - ASInterfaceState selfInterfaceState = ASInterfaceStateNone; - if (_dataSource) { - selfInterfaceState = [_dataSource interfaceStateForRangeController:self]; - } - if (__ApplicationState == UIApplicationStateBackground) { - // If the app is background, pretend to be invisible so that we inform each cell it is no longer being viewed by the user - selfInterfaceState &= ~(ASInterfaceStateVisible); - } - return selfInterfaceState; -} - -- (void)setNeedsUpdate -{ - if (!_needsRangeUpdate) { - _needsRangeUpdate = YES; - - __weak __typeof__(self) weakSelf = self; - dispatch_async(dispatch_get_main_queue(), ^{ - [weakSelf updateIfNeeded]; - }); - } -} - -- (void)updateIfNeeded -{ - if (_needsRangeUpdate) { - [self updateRanges]; - } -} - -- (void)updateRanges -{ - _needsRangeUpdate = NO; - [self _updateVisibleNodeIndexPaths]; -} - -- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode -{ - _preserveCurrentRangeMode = YES; - if (_currentRangeMode != rangeMode) { - _currentRangeMode = rangeMode; - - [self setNeedsUpdate]; - } -} - -- (void)setLayoutController:(id)layoutController -{ - _layoutController = layoutController; - if (layoutController && _dataSource) { - [self updateIfNeeded]; - } -} - -- (void)setDataSource:(id)dataSource -{ - _dataSource = dataSource; - if (dataSource && _layoutController) { - [self updateIfNeeded]; - } -} - -// Clear the visible bit from any nodes that disappeared since last update. -// Currently we guarantee that nodes will not be marked visible when deallocated, -// but it's OK to be in e.g. the preload range. So for the visible bit specifically, -// we add this extra mechanism to account for e.g. deleted items. -// -// NOTE: There is a minor risk here, if a node is transferred from one range controller -// to another before the first rc updates and clears the node out of this set. It's a pretty -// wild scenario that I doubt happens in practice. -- (void)_setVisibleNodes:(NSHashTable *)newVisibleNodes -{ - for (ASCellNode *node in _visibleNodes) { - if (![newVisibleNodes containsObject:node] && node.isVisible) { - [node exitInterfaceState:ASInterfaceStateVisible]; - } - } - _visibleNodes = newVisibleNodes; -} - -- (void)_updateVisibleNodeIndexPaths -{ - as_activity_scope_verbose(as_activity_create("Update range controller", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)); - as_log_verbose(ASCollectionLog(), "Updating ranges for %@", ASViewToDisplayNode(ASDynamicCast(self.delegate, UIView))); - ASDisplayNodeAssert(_layoutController, @"An ASLayoutController is required by ASRangeController"); - if (!_layoutController || !_dataSource) { - return; - } - - if (![_delegate rangeControllerShouldUpdateRanges:self]) { - return; - } - -#if AS_RANGECONTROLLER_LOG_UPDATE_FREQ - _updateCountThisFrame += 1; -#endif - - ASElementMap *map = [_dataSource elementMapForRangeController:self]; - - // TODO: Consider if we need to use this codepath, or can rely on something more similar to the data & display ranges - // Example: ... = [_layoutController indexPathsForScrolling:scrollDirection rangeType:ASLayoutRangeTypeVisible]; - auto visibleElements = [_dataSource visibleElementsForRangeController:self]; - NSHashTable *newVisibleNodes = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; - - ASSignpostStart(ASSignpostRangeControllerUpdate); - - // Get the scroll direction. Default to using the previous one, if they're not scrolling. - ASScrollDirection scrollDirection = [_dataSource scrollDirectionForRangeController:self]; - if (scrollDirection == ASScrollDirectionNone) { - scrollDirection = _previousScrollDirection; - } - _previousScrollDirection = scrollDirection; - - if (visibleElements.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)... - // Verify the actual state by checking the layout with a "VisibleOnly" range. - // This allows us to avoid thrashing through -didExitVisibleState in the case of -reloadData, since that generates didEndDisplayingCell calls. - // Those didEndDisplayingCell calls result in items being removed from the visibleElements returned by the _dataSource, even though the layout remains correct. - visibleElements = [_layoutController elementsForScrolling:scrollDirection rangeMode:ASLayoutRangeModeVisibleOnly rangeType:ASLayoutRangeTypeDisplay map:map]; - for (ASCollectionElement *element in visibleElements) { - [newVisibleNodes addObject:element.node]; - } - [self _setVisibleNodes:newVisibleNodes]; - return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later - } - - ASInterfaceState selfInterfaceState = [self interfaceState]; - ASLayoutRangeMode rangeMode = _currentRangeMode; - BOOL updateRangeMode = (!_preserveCurrentRangeMode && _contentHasBeenScrolled); - - // If we've never scrolled before, we never update the range mode, so it doesn't jump into Full too early. - // This can happen if we have multiple, noisy updates occurring from application code before the user has engaged. - // If the range mode is explicitly set via updateCurrentRangeWithMode:, we'll preserve that for at least one update cycle. - // Once the user has scrolled and the range is visible, we'll always resume managing the range mode automatically. - if ((updateRangeMode && ASInterfaceStateIncludesVisible(selfInterfaceState)) || [[self class] isFirstRangeUpdateForRangeMode:rangeMode]) { - rangeMode = [ASRangeController rangeModeForInterfaceState:selfInterfaceState currentRangeMode:_currentRangeMode]; - } - - ASRangeTuningParameters parametersPreload = [_layoutController tuningParametersForRangeMode:rangeMode - rangeType:ASLayoutRangeTypePreload]; - ASRangeTuningParameters parametersDisplay = [_layoutController tuningParametersForRangeMode:rangeMode - rangeType:ASLayoutRangeTypeDisplay]; - - // Preload can express the ultra-low-memory state with 0, 0 returned for its tuningParameters above, and will match Visible. - // However, in this rangeMode, Display is not supposed to contain *any* paths -- not even the visible bounds. TuningParameters can't express this. - BOOL emptyDisplayRange = (rangeMode == ASLayoutRangeModeLowMemory); - BOOL equalDisplayPreload = ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, parametersPreload); - BOOL equalDisplayVisible = (ASRangeTuningParametersEqualToRangeTuningParameters(parametersDisplay, ASRangeTuningParametersZero) - && emptyDisplayRange == NO); - - // Check if both Display and Preload are unique. If they are, we load them with a single fetch from the layout controller for performance. - BOOL optimizedLoadingOfBothRanges = (equalDisplayPreload == NO && equalDisplayVisible == NO && emptyDisplayRange == NO); - - NSHashTable *displayElements = nil; - NSHashTable *preloadElements = nil; - - if (optimizedLoadingOfBothRanges) { - [_layoutController allElementsForScrolling:scrollDirection rangeMode:rangeMode displaySet:&displayElements preloadSet:&preloadElements map:map]; - } else { - if (emptyDisplayRange == YES) { - displayElements = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; - } else if (equalDisplayVisible == YES) { - displayElements = visibleElements; - } else { - // Calculating only the Display range means the Preload range is either the same as Display or Visible. - displayElements = [_layoutController elementsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypeDisplay map:map]; - } - - BOOL equalPreloadVisible = ASRangeTuningParametersEqualToRangeTuningParameters(parametersPreload, ASRangeTuningParametersZero); - if (equalDisplayPreload == YES) { - preloadElements = displayElements; - } else if (equalPreloadVisible == YES) { - preloadElements = visibleElements; - } else { - preloadElements = [_layoutController elementsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypePreload map:map]; - } - } - - // For now we are only interested in items. Filter-map out from element to item-index-path. - NSSet *visibleIndexPaths = ASSetByFlatMapping(visibleElements, ASCollectionElement *element, [map indexPathForElementIfCell:element]); - NSSet *displayIndexPaths = ASSetByFlatMapping(displayElements, ASCollectionElement *element, [map indexPathForElementIfCell:element]); - NSSet *preloadIndexPaths = ASSetByFlatMapping(preloadElements, ASCollectionElement *element, [map indexPathForElementIfCell:element]); - - // Prioritize the order in which we visit each. Visible nodes should be updated first so they are enqueued on - // the network or display queues before preloading (offscreen) nodes are enqueued. - NSMutableOrderedSet *allIndexPaths = [[NSMutableOrderedSet alloc] initWithSet:visibleIndexPaths]; - - // Typically the preloadIndexPaths will be the largest, and be a superset of the others, though it may be disjoint. - // Because allIndexPaths is an NSMutableOrderedSet, this adds the non-duplicate items /after/ the existing items. - // This means that during iteration, we will first visit visible, then display, then preload nodes. - [allIndexPaths unionSet:displayIndexPaths]; - [allIndexPaths unionSet:preloadIndexPaths]; - - // Add anything we had applied interfaceState to in the last update, but is no longer in range, so we can clear any - // range flags it still has enabled. Most of the time, all but a few elements are equal; a large programmatic - // scroll or major main thread stall could cause entirely disjoint sets. In either case we must visit all. - // Calling "-set" on NSMutableOrderedSet just references the underlying mutable data store, so we must copy it. - NSSet *allCurrentIndexPaths = [[allIndexPaths set] copy]; - [allIndexPaths unionSet:_allPreviousIndexPaths]; - _allPreviousIndexPaths = allCurrentIndexPaths; - - _currentRangeMode = rangeMode; - _preserveCurrentRangeMode = NO; - - if (!_rangeIsValid) { - [allIndexPaths addObjectsFromArray:map.itemIndexPaths]; - } - -#if ASRangeControllerLoggingEnabled - ASDisplayNodeAssertTrue([visibleIndexPaths isSubsetOfSet:displayIndexPaths]); - NSMutableArray *modifiedIndexPaths = (ASRangeControllerLoggingEnabled ? [NSMutableArray array] : nil); -#endif - - for (NSIndexPath *indexPath in allIndexPaths) { - // Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it. - // For consistency, make sure each node knows that it should measure itself if something changes. - ASInterfaceState interfaceState = ASInterfaceStateMeasureLayout; - - if (ASInterfaceStateIncludesVisible(selfInterfaceState)) { - if ([visibleIndexPaths containsObject:indexPath]) { - interfaceState |= (ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStatePreload); - } else { - if ([preloadIndexPaths containsObject:indexPath]) { - interfaceState |= ASInterfaceStatePreload; - } - if ([displayIndexPaths containsObject:indexPath]) { - interfaceState |= ASInterfaceStateDisplay; - } - } - } else { - // If selfInterfaceState isn't visible, then visibleIndexPaths represents what /will/ be immediately visible at the - // instant we come onscreen. So, preload and display all of those things, but don't waste resources preloading yet. - // We handle this as a separate case to minimize set operations for offscreen preloading, including containsObject:. - - if ([allCurrentIndexPaths containsObject:indexPath]) { - // DO NOT set Visible: even though these elements are in the visible range / "viewport", - // our overall container object is itself not visible yet. The moment it becomes visible, we will run the condition above - - // Set Layout, Preload - interfaceState |= ASInterfaceStatePreload; - - if (rangeMode != ASLayoutRangeModeLowMemory) { - // Add Display. - // We might be looking at an indexPath that was previously in-range, but now we need to clear it. - // In that case we'll just set it back to MeasureLayout. Only set Display | Preload if in allCurrentIndexPaths. - interfaceState |= ASInterfaceStateDisplay; - } - } - } - - ASCellNode *node = [map elementForItemAtIndexPath:indexPath].nodeIfAllocated; - if (node != nil) { - ASDisplayNodeAssert(node.hierarchyState & ASHierarchyStateRangeManaged, @"All nodes reaching this point should be range-managed, or interfaceState may be incorrectly reset."); - if (ASInterfaceStateIncludesVisible(interfaceState)) { - [newVisibleNodes addObject:node]; - } - // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. - if (node.pendingInterfaceState != interfaceState) { -#if ASRangeControllerLoggingEnabled - [modifiedIndexPaths addObject:indexPath]; -#endif - - BOOL nodeShouldScheduleDisplay = [node shouldScheduleDisplayWithNewInterfaceState:interfaceState]; - [node recursivelySetInterfaceState:interfaceState]; - - if (nodeShouldScheduleDisplay) { - [self registerForNodeDisplayNotificationsForInterfaceStateIfNeeded:selfInterfaceState]; - if (_didRegisterForNodeDisplayNotifications) { - _pendingDisplayNodesTimestamp = CACurrentMediaTime(); - } - } - } - } - } - - [self _setVisibleNodes:newVisibleNodes]; - - // TODO: This code is for debugging only, but would be great to clean up with a delegate method implementation. - if (ASDisplayNode.shouldShowRangeDebugOverlay) { - ASScrollDirection scrollableDirections = ASScrollDirectionUp | ASScrollDirectionDown; - if ([_dataSource isKindOfClass:NSClassFromString(@"ASCollectionView")]) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wundeclared-selector" - scrollableDirections = (ASScrollDirection)[_dataSource performSelector:@selector(scrollableDirections)]; -#pragma clang diagnostic pop - } - - [self updateRangeController:self - withScrollableDirections:scrollableDirections - scrollDirection:scrollDirection - rangeMode:rangeMode - displayTuningParameters:parametersDisplay - preloadTuningParameters:parametersPreload - interfaceState:selfInterfaceState]; - } - - _rangeIsValid = YES; - -#if ASRangeControllerLoggingEnabled -// NSSet *visibleNodePathsSet = [NSSet setWithArray:visibleNodePaths]; -// BOOL setsAreEqual = [visibleIndexPaths isEqualToSet:visibleNodePathsSet]; -// NSLog(@"visible sets are equal: %d", setsAreEqual); -// if (!setsAreEqual) { -// NSLog(@"standard: %@", visibleIndexPaths); -// NSLog(@"custom: %@", visibleNodePathsSet); -// } - [modifiedIndexPaths sortUsingSelector:@selector(compare:)]; - NSLog(@"Range update complete; modifiedIndexPaths: %@, rangeMode: %d", [self descriptionWithIndexPaths:modifiedIndexPaths], rangeMode); -#endif - - ASSignpostEnd(ASSignpostRangeControllerUpdate); -} - -#pragma mark - Notification observers - -/** - * If we're in a restricted range mode, but we're going to change to a full range mode soon, - * go ahead and schedule the transition as soon as all the currently-scheduled rendering is done #1163. - */ -- (void)registerForNodeDisplayNotificationsForInterfaceStateIfNeeded:(ASInterfaceState)interfaceState -{ - // Do not schedule to listen if we're already in full range mode. - // This avoids updating the range controller during a collection teardown when it is removed - // from the hierarchy and its data source is cleared, causing UIKit to call -reloadData. - if (!_didRegisterForNodeDisplayNotifications && _currentRangeMode != ASLayoutRangeModeFull) { - ASLayoutRangeMode nextRangeMode = [ASRangeController rangeModeForInterfaceState:interfaceState - currentRangeMode:_currentRangeMode]; - if (_currentRangeMode != nextRangeMode) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(scheduledNodesDidDisplay:) - name:ASRenderingEngineDidDisplayScheduledNodesNotification - object:nil]; - _didRegisterForNodeDisplayNotifications = YES; - } - } -} - -- (void)scheduledNodesDidDisplay:(NSNotification *)notification -{ - CFAbsoluteTime notificationTimestamp = ((NSNumber *) notification.userInfo[ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp]).doubleValue; - if (_pendingDisplayNodesTimestamp < notificationTimestamp) { - // The rendering engine has processed all the nodes this range controller scheduled. Let's schedule a range update - [[NSNotificationCenter defaultCenter] removeObserver:self name:ASRenderingEngineDidDisplayScheduledNodesNotification object:nil]; - _didRegisterForNodeDisplayNotifications = NO; - - [self setNeedsUpdate]; - } -} - -#pragma mark - Cell node view handling - -- (void)configureContentView:(UIView *)contentView forCellNode:(ASCellNode *)node -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(node, @"Cannot move a nil node to a view"); - ASDisplayNodeAssert(contentView, @"Cannot move a node to a non-existent view"); - - if (node.shouldUseUIKitCell) { - // When using UIKit cells, the ASCellNode is just a placeholder object with a preferredSize. - // In this case, we should not disrupt the subviews of the contentView. - return; - } - - if (node.view.superview == contentView) { - // this content view is already correctly configured - return; - } - - // clean the content view - for (UIView *view in contentView.subviews) { - [view removeFromSuperview]; - } - - [contentView addSubview:node.view]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - [_layoutController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; -} - -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - return [_layoutController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; -} - -#pragma mark - ASDataControllerDelegete - -- (void)dataController:(ASDataController *)dataController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates -{ - ASDisplayNodeAssertMainThread(); - if (changeSet.includesReloadData) { - [self _setVisibleNodes:nil]; - } - _rangeIsValid = NO; - [_delegate rangeController:self updateWithChangeSet:changeSet updates:updates]; -} - -#pragma mark - Memory Management - -// Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. -- (void)clearContents -{ - ASDisplayNodeAssertMainThread(); - for (ASCollectionElement *element in [_dataSource elementMapForRangeController:self]) { - ASCellNode *node = element.nodeIfAllocated; - if (ASInterfaceStateIncludesDisplay(node.interfaceState)) { - [node exitInterfaceState:ASInterfaceStateDisplay]; - } - } -} - -- (void)clearPreloadedData -{ - ASDisplayNodeAssertMainThread(); - for (ASCollectionElement *element in [_dataSource elementMapForRangeController:self]) { - ASCellNode *node = element.nodeIfAllocated; - if (ASInterfaceStateIncludesPreload(node.interfaceState)) { - [node exitInterfaceState:ASInterfaceStatePreload]; - } - } -} - -#pragma mark - Class Methods (Application Notification Handlers) - -+ (ASWeakSet *)allRangeControllersWeakSet -{ - static ASWeakSet *__allRangeControllersWeakSet; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - __allRangeControllersWeakSet = [[ASWeakSet alloc] init]; - [self registerSharedApplicationNotifications]; - }); - return __allRangeControllersWeakSet; -} - -+ (void)registerSharedApplicationNotifications -{ - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; -#if ASRangeControllerAutomaticLowMemoryHandling - [center addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; -#endif - [center addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; - [center addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; -} - -static ASLayoutRangeMode __rangeModeForMemoryWarnings = ASLayoutRangeModeLowMemory; -+ (void)setRangeModeForMemoryWarnings:(ASLayoutRangeMode)rangeMode -{ - ASDisplayNodeAssert(rangeMode == ASLayoutRangeModeVisibleOnly || rangeMode == ASLayoutRangeModeLowMemory, @"It is highly inadvisable to engage a larger range mode when a memory warning occurs, as this will almost certainly cause app eviction"); - __rangeModeForMemoryWarnings = rangeMode; -} - -+ (void)didReceiveMemoryWarning:(NSNotification *)notification -{ - NSArray *allRangeControllers = [[self allRangeControllersWeakSet] allObjects]; - for (ASRangeController *rangeController in allRangeControllers) { - BOOL isDisplay = ASInterfaceStateIncludesDisplay([rangeController interfaceState]); - [rangeController updateCurrentRangeWithMode:isDisplay ? ASLayoutRangeModeVisibleOnly : __rangeModeForMemoryWarnings]; - // There's no need to call needs update as updateCurrentRangeWithMode sets this if necessary. - [rangeController updateIfNeeded]; - } - -#if ASRangeControllerLoggingEnabled - NSLog(@"+[ASRangeController didReceiveMemoryWarning] with controllers: %@", allRangeControllers); -#endif -} - -+ (void)didEnterBackground:(NSNotification *)notification -{ - NSArray *allRangeControllers = [[self allRangeControllersWeakSet] allObjects]; - for (ASRangeController *rangeController in allRangeControllers) { - // We do not want to fully collapse the Display ranges of any visible range controllers so that flashes can be avoided when - // the app is resumed. Non-visible controllers can be more aggressively culled to the LowMemory state (see definitions for documentation) - BOOL isVisible = ASInterfaceStateIncludesVisible([rangeController interfaceState]); - [rangeController updateCurrentRangeWithMode:isVisible ? ASLayoutRangeModeVisibleOnly : ASLayoutRangeModeLowMemory]; - } - - // Because -interfaceState checks __ApplicationState and always clears the "visible" bit if Backgrounded, we must set this after updating the range mode. - __ApplicationState = UIApplicationStateBackground; - for (ASRangeController *rangeController in allRangeControllers) { - // Trigger a range update immediately, as we may not be allowed by the system to run the update block scheduled by changing range mode. - // There's no need to call needs update as updateCurrentRangeWithMode sets this if necessary. - [rangeController updateIfNeeded]; - } - -#if ASRangeControllerLoggingEnabled - NSLog(@"+[ASRangeController didEnterBackground] with controllers, after backgrounding: %@", allRangeControllers); -#endif -} - -+ (void)willEnterForeground:(NSNotification *)notification -{ - NSArray *allRangeControllers = [[self allRangeControllersWeakSet] allObjects]; - __ApplicationState = UIApplicationStateActive; - for (ASRangeController *rangeController in allRangeControllers) { - BOOL isVisible = ASInterfaceStateIncludesVisible([rangeController interfaceState]); - [rangeController updateCurrentRangeWithMode:isVisible ? ASLayoutRangeModeMinimum : ASLayoutRangeModeVisibleOnly]; - // There's no need to call needs update as updateCurrentRangeWithMode sets this if necessary. - [rangeController updateIfNeeded]; - } - -#if ASRangeControllerLoggingEnabled - NSLog(@"+[ASRangeController willEnterForeground] with controllers, after foregrounding: %@", allRangeControllers); -#endif -} - -#pragma mark - Debugging - -#if AS_RANGECONTROLLER_LOG_UPDATE_FREQ -- (void)_updateCountDisplayLinkDidFire -{ - if (_updateCountThisFrame > 1) { - NSLog(@"ASRangeController %p updated %lu times this frame.", self, (unsigned long)_updateCountThisFrame); - } - _updateCountThisFrame = 0; -} -#endif - -- (NSString *)descriptionWithIndexPaths:(NSArray *)indexPaths -{ - NSMutableString *description = [NSMutableString stringWithFormat:@"%@ %@", [super description], @" allPreviousIndexPaths:\n"]; - for (NSIndexPath *indexPath in indexPaths) { - ASDisplayNode *node = [[_dataSource elementMapForRangeController:self] elementForItemAtIndexPath:indexPath].nodeIfAllocated; - ASInterfaceState interfaceState = node.interfaceState; - BOOL inVisible = ASInterfaceStateIncludesVisible(interfaceState); - BOOL inDisplay = ASInterfaceStateIncludesDisplay(interfaceState); - BOOL inPreload = ASInterfaceStateIncludesPreload(interfaceState); - [description appendFormat:@"indexPath %@, Visible: %d, Display: %d, Preload: %d\n", indexPath, inVisible, inDisplay, inPreload]; - } - return description; -} - -- (NSString *)description -{ - NSArray *indexPaths = [[_allPreviousIndexPaths allObjects] sortedArrayUsingSelector:@selector(compare:)]; - return [self descriptionWithIndexPaths:indexPaths]; -} - -@end - -@implementation ASDisplayNode (RangeModeConfiguring) - -+ (void)setRangeModeForMemoryWarnings:(ASLayoutRangeMode)rangeMode -{ - [ASRangeController setRangeModeForMemoryWarnings:rangeMode]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASRangeControllerUpdateRangeProtocol+Beta.h b/submodules/AsyncDisplayKit/Source/ASRangeControllerUpdateRangeProtocol+Beta.h deleted file mode 100644 index 5bc4fae301..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASRangeControllerUpdateRangeProtocol+Beta.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// ASRangeControllerUpdateRangeProtocol+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 -#import - -@protocol ASRangeControllerUpdateRangeProtocol - -/** - * Updates the current range mode of the range controller for at least the next range update - * and, if the new mode is different from the previous mode, enqueues a range update. - */ -- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASRangeManagingNode.h b/submodules/AsyncDisplayKit/Source/ASRangeManagingNode.h deleted file mode 100644 index c331a77bee..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASRangeManagingNode.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// ASRangeManagingNode.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@class ASCellNode; - -NS_ASSUME_NONNULL_BEGIN - -/** - * Basically ASTableNode or ASCollectionNode. - */ -@protocol ASRangeManagingNode - -/** - * Retrieve the index path for the given node, if it's a member of this container. - * - * @param node The node. - * @return The index path, or nil if the node is not part of this container. - */ -- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)node; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASRatioLayoutSpec.h b/submodules/AsyncDisplayKit/Source/ASRatioLayoutSpec.h deleted file mode 100644 index b70e6fe7fb..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASRatioLayoutSpec.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// ASRatioLayoutSpec.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 - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASLayoutElement; - -/** - Ratio layout spec - For when the content should respect a certain inherent ratio but can be scaled (think photos or videos) - The ratio passed is the ratio of height / width you expect - - For a ratio 0.5, the spec will have a flat rectangle shape - _ _ _ _ - | | - |_ _ _ _| - - For a ratio 2.0, the spec will be twice as tall as it is wide - _ _ - | | - | | - | | - |_ _| - - **/ -@interface ASRatioLayoutSpec : ASLayoutSpec - -@property (nonatomic) CGFloat ratio; - -+ (instancetype)ratioLayoutSpecWithRatio:(CGFloat)ratio child:(id)child NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASRatioLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/ASRatioLayoutSpec.mm deleted file mode 100644 index e0d32730e2..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASRatioLayoutSpec.mm +++ /dev/null @@ -1,103 +0,0 @@ -// -// ASRatioLayoutSpec.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 - -#import -#import -#import - -#import - -#import -#import "Private/ASInternalHelpers.h" - -#pragma mark - ASRatioLayoutSpec - -@implementation ASRatioLayoutSpec -{ - CGFloat _ratio; -} - -#pragma mark - Lifecycle - -+ (instancetype)ratioLayoutSpecWithRatio:(CGFloat)ratio child:(id)child NS_RETURNS_RETAINED -{ - return [[self alloc] initWithRatio:ratio child:child]; -} - -- (instancetype)initWithRatio:(CGFloat)ratio child:(id)child; -{ - if (!(self = [super init])) { - return nil; - } - - ASDisplayNodeAssertNotNil(child, @"Child cannot be nil"); - ASDisplayNodeAssert(ratio > 0, @"Ratio should be strictly positive, but received %f", ratio); - _ratio = ratio; - self.child = child; - - return self; -} - -#pragma mark - Setter / Getter - -- (void)setRatio:(CGFloat)ratio -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _ratio = ratio; -} - -#pragma mark - ASLayoutElement - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - std::vector sizeOptions; - - if (ASPointsValidForSize(constrainedSize.max.width)) { - sizeOptions.push_back(ASSizeRangeClamp(constrainedSize, { - constrainedSize.max.width, - ASFloorPixelValue(_ratio * constrainedSize.max.width) - })); - } - - if (ASPointsValidForSize(constrainedSize.max.height)) { - sizeOptions.push_back(ASSizeRangeClamp(constrainedSize, { - ASFloorPixelValue(constrainedSize.max.height / _ratio), - constrainedSize.max.height - })); - } - - // Choose the size closest to the desired ratio. - const auto &bestSize = std::max_element(sizeOptions.begin(), sizeOptions.end(), [&](const CGSize &a, const CGSize &b){ - return std::fabs((a.height / a.width) - _ratio) > std::fabs((b.height / b.width) - _ratio); - }); - - // If there is no max size in *either* dimension, we can't apply the ratio, so just pass our size range through. - const ASSizeRange childRange = (bestSize == sizeOptions.end()) ? constrainedSize : ASSizeRangeIntersect(constrainedSize, ASSizeRangeMake(*bestSize, *bestSize)); - const CGSize parentSize = (bestSize == sizeOptions.end()) ? ASLayoutElementParentSizeUndefined : *bestSize; - ASLayout *sublayout = [self.child layoutThatFits:childRange parentSize:parentSize]; - sublayout.position = CGPointZero; - return [ASLayout layoutWithLayoutElement:self size:sublayout.size sublayouts:@[sublayout]]; -} - -@end - -#pragma mark - ASRatioLayoutSpec (Debugging) - -@implementation ASRatioLayoutSpec (Debugging) - -#pragma mark - ASLayoutElementAsciiArtProtocol - -- (NSString *)asciiArtName -{ - return [NSString stringWithFormat:@"%@ (%.1f)", NSStringFromClass([self class]), self.ratio]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASRelativeLayoutSpec.h b/submodules/AsyncDisplayKit/Source/ASRelativeLayoutSpec.h deleted file mode 100644 index 9b260d9ccc..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASRelativeLayoutSpec.h +++ /dev/null @@ -1,87 +0,0 @@ -// -// ASRelativeLayoutSpec.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 - -/** - * How the child is positioned within the spec. - * - * The default option will position the child at point 0. - * Swift: use [] for the default behavior. - */ -typedef NS_ENUM(NSUInteger, ASRelativeLayoutSpecPosition) { - /** The child is positioned at point 0 */ - ASRelativeLayoutSpecPositionNone = 0, - /** The child is positioned at point 0 relatively to the layout axis (ie left / top most) */ - ASRelativeLayoutSpecPositionStart = 1, - /** The child is centered along the specified axis */ - ASRelativeLayoutSpecPositionCenter = 2, - /** The child is positioned at the maximum point of the layout axis (ie right / bottom most) */ - ASRelativeLayoutSpecPositionEnd = 3, -}; - -/** - * How much space the spec will take up. - * - * The default option will allow the spec to take up the maximum size possible. - * Swift: use [] for the default behavior. - */ -typedef NS_OPTIONS(NSUInteger, ASRelativeLayoutSpecSizingOption) { - /** The spec will take up the maximum size possible */ - ASRelativeLayoutSpecSizingOptionDefault, - /** The spec will take up the minimum size possible along the X axis */ - ASRelativeLayoutSpecSizingOptionMinimumWidth = 1 << 0, - /** The spec will take up the minimum size possible along the Y axis */ - ASRelativeLayoutSpecSizingOptionMinimumHeight = 1 << 1, - /** Convenience option to take up the minimum size along both the X and Y axis */ - ASRelativeLayoutSpecSizingOptionMinimumSize = ASRelativeLayoutSpecSizingOptionMinimumWidth | ASRelativeLayoutSpecSizingOptionMinimumHeight, -}; - -NS_ASSUME_NONNULL_BEGIN - -/** Lays out a single layoutElement child and positions it within the layout bounds according to vertical and horizontal positional specifiers. - * Can position the child at any of the 4 corners, or the middle of any of the 4 edges, as well as the center - similar to "9-part" image areas. - */ -@interface ASRelativeLayoutSpec : ASLayoutSpec - -// You may create a spec with alloc / init, then set any non-default properties; or use a convenience initialize that accepts all properties. -@property (nonatomic) ASRelativeLayoutSpecPosition horizontalPosition; -@property (nonatomic) ASRelativeLayoutSpecPosition verticalPosition; -@property (nonatomic) ASRelativeLayoutSpecSizingOption sizingOption; - -/*! - * @discussion convenience constructor for a ASRelativeLayoutSpec - * @param horizontalPosition how to position the item on the horizontal (x) axis - * @param verticalPosition how to position the item on the vertical (y) axis - * @param sizingOption how much size to take up - * @param child the child to layout - * @return a configured ASRelativeLayoutSpec - */ -+ (instancetype)relativePositionLayoutSpecWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition - verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition - sizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption - child:(id)child NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -/*! - * @discussion convenience initializer for a ASRelativeLayoutSpec - * @param horizontalPosition how to position the item on the horizontal (x) axis - * @param verticalPosition how to position the item on the vertical (y) axis - * @param sizingOption how much size to take up - * @param child the child to layout - * @return a configured ASRelativeLayoutSpec - */ -- (instancetype)initWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition - verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition - sizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption - child:(id)child; - -@end - -NS_ASSUME_NONNULL_END - diff --git a/submodules/AsyncDisplayKit/Source/ASRelativeLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/ASRelativeLayoutSpec.mm deleted file mode 100644 index d76ccac4f1..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASRelativeLayoutSpec.mm +++ /dev/null @@ -1,104 +0,0 @@ -// -// ASRelativeLayoutSpec.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 "ASRelativeLayoutSpec.h" - -#import - -#import "Private/ASInternalHelpers.h" - -@implementation ASRelativeLayoutSpec - -- (instancetype)initWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition sizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption child:(id)child -{ - if (!(self = [super init])) { - return nil; - } - ASDisplayNodeAssertNotNil(child, @"Child cannot be nil"); - _horizontalPosition = horizontalPosition; - _verticalPosition = verticalPosition; - _sizingOption = sizingOption; - [self setChild:child]; - return self; -} - -+ (instancetype)relativePositionLayoutSpecWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition sizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption child:(id)child NS_RETURNS_RETAINED -{ - return [[self alloc] initWithHorizontalPosition:horizontalPosition verticalPosition:verticalPosition sizingOption:sizingOption child:child]; -} - -- (void)setHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _horizontalPosition = horizontalPosition; -} - -- (void)setVerticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition { - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _verticalPosition = verticalPosition; -} - -- (void)setSizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _sizingOption = sizingOption; -} - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - // If we have a finite size in any direction, pass this so that the child can resolve percentages against it. - // Otherwise pass ASLayoutElementParentDimensionUndefined as the size will depend on the content - CGSize size = { - ASPointsValidForSize(constrainedSize.max.width) == NO ? ASLayoutElementParentDimensionUndefined : constrainedSize.max.width, - ASPointsValidForSize(constrainedSize.max.height) == NO ? ASLayoutElementParentDimensionUndefined : constrainedSize.max.height - }; - - // Layout the child - const CGSize minChildSize = { - (_horizontalPosition != ASRelativeLayoutSpecPositionNone) ? 0 : constrainedSize.min.width, - (_verticalPosition != ASRelativeLayoutSpecPositionNone) ? 0 : constrainedSize.min.height, - }; - ASLayout *sublayout = [self.child layoutThatFits:ASSizeRangeMake(minChildSize, constrainedSize.max) parentSize:size]; - - // If we have an undetermined height or width, use the child size to define the layout size - size = ASSizeRangeClamp(constrainedSize, { - isfinite(size.width) == NO ? sublayout.size.width : size.width, - isfinite(size.height) == NO ? sublayout.size.height : size.height - }); - - // If minimum size options are set, attempt to shrink the size to the size of the child - size = ASSizeRangeClamp(constrainedSize, { - MIN(size.width, (_sizingOption & ASRelativeLayoutSpecSizingOptionMinimumWidth) != 0 ? sublayout.size.width : size.width), - MIN(size.height, (_sizingOption & ASRelativeLayoutSpecSizingOptionMinimumHeight) != 0 ? sublayout.size.height : size.height) - }); - - // Compute the position for the child on each axis according to layout parameters - CGFloat xPosition = [self proportionOfAxisForAxisPosition:_horizontalPosition]; - CGFloat yPosition = [self proportionOfAxisForAxisPosition:_verticalPosition]; - - sublayout.position = { - ASRoundPixelValue((size.width - sublayout.size.width) * xPosition), - ASRoundPixelValue((size.height - sublayout.size.height) * yPosition) - }; - - return [ASLayout layoutWithLayoutElement:self size:size sublayouts:@[sublayout]]; -} - -- (CGFloat)proportionOfAxisForAxisPosition:(ASRelativeLayoutSpecPosition)position -{ - if (position == ASRelativeLayoutSpecPositionCenter) { - return 0.5f; - } else if (position == ASRelativeLayoutSpecPositionEnd) { - return 1.0f; - } else { - return 0.0f; - } -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASSection.h b/submodules/AsyncDisplayKit/Source/ASSection.h deleted file mode 100644 index 92b6a17170..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASSection.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// ASSection.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 -#import - -@protocol ASSectionContext; - -NS_ASSUME_NONNULL_BEGIN - -/** - * An object representing the metadata for a section of elements in a collection. - * - * Its sectionID is namespaced to the data controller that created the section. - * - * These are useful for tracking the movement & lifetime of sections, independent of - * their contents. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASSection : NSObject - -@property (readonly) NSInteger sectionID; -@property (nullable, readonly) id context; - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithSectionID:(NSInteger)sectionID context:(nullable id)context NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASSection.mm b/submodules/AsyncDisplayKit/Source/ASSection.mm deleted file mode 100644 index a151774bd4..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASSection.mm +++ /dev/null @@ -1,29 +0,0 @@ -// -// ASSection.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 -#import - -@implementation ASSection - -- (instancetype)initWithSectionID:(NSInteger)sectionID context:(id)context -{ - self = [super init]; - if (self) { - _sectionID = sectionID; - _context = context; - } - return self; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASSectionContext.h b/submodules/AsyncDisplayKit/Source/ASSectionContext.h deleted file mode 100644 index e7d4a190a4..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASSectionContext.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// ASSectionContext.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 - -@class ASCollectionView; - -@protocol ASSectionContext - -/** - * Custom name of this section, for debugging only. - */ -@property (nonatomic, copy, nullable) NSString *sectionName; -@property (nonatomic, weak, nullable) ASCollectionView *collectionView; - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASSectionController.h b/submodules/AsyncDisplayKit/Source/ASSectionController.h deleted file mode 100644 index 7df4dc84b8..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASSectionController.h +++ /dev/null @@ -1,88 +0,0 @@ -// -// ASSectionController.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 -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASBatchContext; - -/** - * A protocol that your section controllers should conform to, in order to be used with Texture. - * - * @note Your supplementary view source should conform to @c ASSupplementaryNodeSource. - */ -@protocol ASSectionController - -@optional - -/** - * A method to provide the node block for the item at the given index. - * The node block you return will be run asynchronously off the main thread, - * so it's important to retrieve any objects from your section _outside_ the block - * because by the time the block is run, the array may have changed. - * - * @param index The index of the item. - * @return A block to be run concurrently to build the node for this item. - * @see collectionNode:nodeBlockForItemAtIndexPath: - */ -- (ASCellNodeBlock)nodeBlockForItemAtIndex:(NSInteger)index; - -/** - * Similar to -collectionView:cellForItemAtIndexPath:. - * - * Note: only called if nodeBlockForItemAtIndex: returns nil. - * - * @param index The index 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 *)nodeForItemAtIndex:(NSInteger)index; - -/** - * Asks the section controller whether it should batch fetch because the user is - * near the end of the current data set. - * - * @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 assumed return value is @c YES. - */ -- (BOOL)shouldBatchFetch; - -/** - * Asks the section controller to begin fetching more content (tail loading) because - * the user is near the end of the current data set. - * - * @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. - */ -- (void)beginBatchFetchWithContext:(ASBatchContext *)context; - -/** - * A method to provide the size range used for measuring the item - * at the given index. - * - * @param index The index of the item. - * @return A size range used for asynchronously measuring the node at this index. - * @see collectionNode:constrainedSizeForItemAtIndexPath: - */ -- (ASSizeRange)sizeRangeForItemAtIndex:(NSInteger)index; - -@end - -NS_ASSUME_NONNULL_END -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASStackLayoutSpec.h b/submodules/AsyncDisplayKit/Source/ASStackLayoutSpec.h deleted file mode 100644 index 535758ac7b..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASStackLayoutSpec.h +++ /dev/null @@ -1,133 +0,0 @@ -// -// ASStackLayoutSpec.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 -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - A simple layout spec that stacks a list of children vertically or horizontally. - - - All children are initially laid out with the an infinite available size in the stacking direction. - - In the other direction, this spec's constraint is passed. - - The children's sizes are summed in the stacking direction. - - If this sum is less than this spec's minimum size in stacking direction, children with flexGrow are flexed. - - If it is greater than this spec's maximum size in the stacking direction, children with flexShrink are flexed. - - If, even after flexing, the sum is still greater than this spec's maximum size in the stacking direction, - justifyContent determines how children are laid out. - - For example: - - - Suppose stacking direction is Vertical, min-width=100, max-width=300, min-height=200, max-height=500. - - All children are laid out with min-width=100, max-width=300, min-height=0, max-height=INFINITY. - - If the sum of the childrens' heights is less than 200, children with flexGrow are flexed larger. - - If the sum of the childrens' heights is greater than 500, children with flexShrink are flexed smaller. - Each child is shrunk by `((sum of heights) - 500)/(number of flexShrink-able children)`. - - If the sum of the childrens' heights is greater than 500 even after flexShrink-able children are flexed, - justifyContent determines how children are laid out. - */ -@interface ASStackLayoutSpec : ASLayoutSpec - -/** - Specifies the direction children are stacked in. If horizontalAlignment and verticalAlignment were set, - they will be resolved again, causing justifyContent and alignItems to be updated accordingly - */ -@property (nonatomic) ASStackLayoutDirection direction; -/** The amount of space between each child. */ -@property (nonatomic) CGFloat spacing; -/** - Specifies how children are aligned horizontally. Depends on the stack direction, setting the alignment causes either - justifyContent or alignItems to be updated. The alignment will remain valid after future direction changes. - Thus, it is preferred to those properties - */ -@property (nonatomic) ASHorizontalAlignment horizontalAlignment; -/** - Specifies how children are aligned vertically. Depends on the stack direction, setting the alignment causes either - justifyContent or alignItems to be updated. The alignment will remain valid after future direction changes. - Thus, it is preferred to those properties - */ -@property (nonatomic) ASVerticalAlignment verticalAlignment; -/** The amount of space between each child. Defaults to ASStackLayoutJustifyContentStart */ -@property (nonatomic) ASStackLayoutJustifyContent justifyContent; -/** Orientation of children along cross axis. Defaults to ASStackLayoutAlignItemsStretch */ -@property (nonatomic) ASStackLayoutAlignItems alignItems; -/** Whether children are stacked into a single or multiple lines. Defaults to single line (ASStackLayoutFlexWrapNoWrap) */ -@property (nonatomic) ASStackLayoutFlexWrap flexWrap; -/** Orientation of lines along cross axis if there are multiple lines. Defaults to ASStackLayoutAlignContentStart */ -@property (nonatomic) ASStackLayoutAlignContent alignContent; -/** If the stack spreads on multiple lines using flexWrap, the amount of space between lines. */ -@property (nonatomic) CGFloat lineSpacing; -/** Whether this stack can dispatch to other threads, regardless of which thread it's running on */ -@property (nonatomic, getter=isConcurrent) BOOL concurrent; - -- (instancetype)init; - -/** - @param direction The direction of the stack view (horizontal or vertical) - @param spacing The spacing between the children - @param justifyContent If no children are flexible, this describes how to fill any extra space - @param alignItems Orientation of the children along the cross axis - @param children ASLayoutElement children to be positioned. - */ -+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction - spacing:(CGFloat)spacing - justifyContent:(ASStackLayoutJustifyContent)justifyContent - alignItems:(ASStackLayoutAlignItems)alignItems - children:(NSArray> *)children NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -/** - @param direction The direction of the stack view (horizontal or vertical) - @param spacing The spacing between the children - @param justifyContent If no children are flexible, this describes how to fill any extra space - @param alignItems Orientation of the children along the cross axis - @param flexWrap Whether children are stacked into a single or multiple lines - @param alignContent Orientation of lines along cross axis if there are multiple lines - @param children ASLayoutElement children to be positioned. - */ -+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction - spacing:(CGFloat)spacing - justifyContent:(ASStackLayoutJustifyContent)justifyContent - alignItems:(ASStackLayoutAlignItems)alignItems - flexWrap:(ASStackLayoutFlexWrap)flexWrap - alignContent:(ASStackLayoutAlignContent)alignContent - children:(NSArray> *)children NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -/** - @param direction The direction of the stack view (horizontal or vertical) - @param spacing The spacing between the children - @param justifyContent If no children are flexible, this describes how to fill any extra space - @param alignItems Orientation of the children along the cross axis - @param flexWrap Whether children are stacked into a single or multiple lines - @param alignContent Orientation of lines along cross axis if there are multiple lines - @param lineSpacing The spacing between lines - @param children ASLayoutElement children to be positioned. - */ -+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction - spacing:(CGFloat)spacing - justifyContent:(ASStackLayoutJustifyContent)justifyContent - alignItems:(ASStackLayoutAlignItems)alignItems - flexWrap:(ASStackLayoutFlexWrap)flexWrap - alignContent:(ASStackLayoutAlignContent)alignContent - lineSpacing:(CGFloat)lineSpacing - children:(NSArray> *)children NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -/** - * @return A stack layout spec with direction of ASStackLayoutDirectionVertical - **/ -+ (instancetype)verticalStackLayoutSpec NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -/** - * @return A stack layout spec with direction of ASStackLayoutDirectionHorizontal - **/ -+ (instancetype)horizontalStackLayoutSpec NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASStackLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/ASStackLayoutSpec.mm deleted file mode 100644 index c7c9f8dd21..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASStackLayoutSpec.mm +++ /dev/null @@ -1,212 +0,0 @@ -// -// ASStackLayoutSpec.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 - -#import -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import - -@implementation ASStackLayoutSpec - -- (instancetype)init -{ - return [self initWithDirection:ASStackLayoutDirectionHorizontal spacing:0.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStretch flexWrap:ASStackLayoutFlexWrapNoWrap alignContent:ASStackLayoutAlignContentStart lineSpacing:0.0 children:nil]; -} - -+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems children:(NSArray *)children NS_RETURNS_RETAINED -{ - return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:ASStackLayoutFlexWrapNoWrap alignContent:ASStackLayoutAlignContentStart lineSpacing: 0.0 children:children]; -} - -+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent children:(NSArray> *)children NS_RETURNS_RETAINED -{ - return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:flexWrap alignContent:alignContent lineSpacing:0.0 children:children]; -} - -+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent lineSpacing:(CGFloat)lineSpacing children:(NSArray> *)children NS_RETURNS_RETAINED -{ - return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:flexWrap alignContent:alignContent lineSpacing:lineSpacing children:children]; -} - -+ (instancetype)verticalStackLayoutSpec NS_RETURNS_RETAINED -{ - ASStackLayoutSpec *stackLayoutSpec = [[self alloc] init]; - stackLayoutSpec.direction = ASStackLayoutDirectionVertical; - return stackLayoutSpec; -} - -+ (instancetype)horizontalStackLayoutSpec NS_RETURNS_RETAINED -{ - ASStackLayoutSpec *stackLayoutSpec = [[self alloc] init]; - stackLayoutSpec.direction = ASStackLayoutDirectionHorizontal; - return stackLayoutSpec; -} - -- (instancetype)initWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent lineSpacing:(CGFloat)lineSpacing children:(NSArray *)children -{ - if (!(self = [super init])) { - return nil; - } - _direction = direction; - _spacing = spacing; - _horizontalAlignment = ASHorizontalAlignmentNone; - _verticalAlignment = ASVerticalAlignmentNone; - _alignItems = alignItems; - _justifyContent = justifyContent; - _flexWrap = flexWrap; - _alignContent = alignContent; - _lineSpacing = lineSpacing; - - [self setChildren:children]; - return self; -} - -- (void)setDirection:(ASStackLayoutDirection)direction -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - if (_direction != direction) { - _direction = direction; - [self resolveHorizontalAlignment]; - [self resolveVerticalAlignment]; - } -} - -- (void)setHorizontalAlignment:(ASHorizontalAlignment)horizontalAlignment -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - if (_horizontalAlignment != horizontalAlignment) { - _horizontalAlignment = horizontalAlignment; - [self resolveHorizontalAlignment]; - } -} - -- (void)setVerticalAlignment:(ASVerticalAlignment)verticalAlignment -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - if (_verticalAlignment != verticalAlignment) { - _verticalAlignment = verticalAlignment; - [self resolveVerticalAlignment]; - } -} - -- (void)setAlignItems:(ASStackLayoutAlignItems)alignItems -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - ASDisplayNodeAssert(_horizontalAlignment == ASHorizontalAlignmentNone, @"Cannot set this property directly because horizontalAlignment is being used"); - ASDisplayNodeAssert(_verticalAlignment == ASVerticalAlignmentNone, @"Cannot set this property directly because verticalAlignment is being used"); - _alignItems = alignItems; -} - -- (void)setJustifyContent:(ASStackLayoutJustifyContent)justifyContent -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - ASDisplayNodeAssert(_horizontalAlignment == ASHorizontalAlignmentNone, @"Cannot set this property directly because horizontalAlignment is being used"); - ASDisplayNodeAssert(_verticalAlignment == ASVerticalAlignmentNone, @"Cannot set this property directly because verticalAlignment is being used"); - _justifyContent = justifyContent; -} - -- (void)setSpacing:(CGFloat)spacing -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _spacing = spacing; -} - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - NSArray *children = self.children; - if (children.count == 0) { - return [ASLayout layoutWithLayoutElement:self size:constrainedSize.min]; - } - - as_activity_scope_verbose(as_activity_create("Calculate stack layout", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)); - as_log_verbose(ASLayoutLog(), "Stack layout %@", self); - // Accessing the style and size property is pretty costly we create layout spec children we use to figure - // out the layout for each child - const auto stackChildren = AS::map(children, [&](const id child) -> ASStackLayoutSpecChild { - ASLayoutElementStyle *style = child.style; - return {child, style, style.size}; - }); - - const ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems, .flexWrap = _flexWrap, .alignContent = _alignContent, .lineSpacing = _lineSpacing}; - - const auto unpositionedLayout = ASStackUnpositionedLayout::compute(stackChildren, style, constrainedSize, _concurrent); - const auto positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, style, constrainedSize); - - if (style.direction == ASStackLayoutDirectionVertical) { - self.style.ascender = stackChildren.front().style.ascender; - self.style.descender = stackChildren.back().style.descender; - } - - ASLayout *rawSublayouts[positionedLayout.items.size()]; - int i = 0; - for (const auto &item : positionedLayout.items) { - rawSublayouts[i++] = item.layout; - } - - const auto sublayouts = [NSArray arrayByTransferring:rawSublayouts count:i]; - return [ASLayout layoutWithLayoutElement:self size:positionedLayout.size sublayouts:sublayouts]; -} - -- (void)resolveHorizontalAlignment -{ - if (_direction == ASStackLayoutDirectionHorizontal) { - _justifyContent = justifyContent(_horizontalAlignment, _justifyContent); - } else { - _alignItems = alignment(_horizontalAlignment, _alignItems); - } -} - -- (void)resolveVerticalAlignment -{ - if (_direction == ASStackLayoutDirectionHorizontal) { - _alignItems = alignment(_verticalAlignment, _alignItems); - } else { - _justifyContent = justifyContent(_verticalAlignment, _justifyContent); - } -} - -- (NSMutableArray *)propertiesForDescription -{ - auto result = [super propertiesForDescription]; - - // Add our direction - switch (self.direction) { - case ASStackLayoutDirectionVertical: - [result insertObject:@{ (id)kCFNull: @"vertical" } atIndex:0]; - break; - case ASStackLayoutDirectionHorizontal: - [result insertObject:@{ (id)kCFNull: @"horizontal" } atIndex:0]; - break; - } - - return result; -} - -@end - -@implementation ASStackLayoutSpec (Debugging) - -#pragma mark - ASLayoutElementAsciiArtProtocol - -- (NSString *)asciiArtString -{ - return [ASLayoutSpec asciiArtStringForChildren:self.children parentName:[self asciiArtName] direction:self.direction]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASStackLayoutSpecUtilities.h b/submodules/AsyncDisplayKit/Source/ASStackLayoutSpecUtilities.h deleted file mode 100644 index 6c708e0408..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASStackLayoutSpecUtilities.h +++ /dev/null @@ -1,135 +0,0 @@ -// -// ASStackLayoutSpecUtilities.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 - -typedef struct { - ASStackLayoutDirection direction; - CGFloat spacing; - ASStackLayoutJustifyContent justifyContent; - ASStackLayoutAlignItems alignItems; - ASStackLayoutFlexWrap flexWrap; - ASStackLayoutAlignContent alignContent; - CGFloat lineSpacing; -} ASStackLayoutSpecStyle; - -inline CGFloat stackDimension(const ASStackLayoutDirection direction, const CGSize size) -{ - return (direction == ASStackLayoutDirectionVertical) ? size.height : size.width; -} - -inline CGFloat crossDimension(const ASStackLayoutDirection direction, const CGSize size) -{ - return (direction == ASStackLayoutDirectionVertical) ? size.width : size.height; -} - -inline BOOL compareCrossDimension(const ASStackLayoutDirection direction, const CGSize a, const CGSize b) -{ - return crossDimension(direction, a) < crossDimension(direction, b); -} - -inline CGPoint directionPoint(const ASStackLayoutDirection direction, const CGFloat stack, const CGFloat cross) -{ - return (direction == ASStackLayoutDirectionVertical) ? CGPointMake(cross, stack) : CGPointMake(stack, cross); -} - -inline CGSize directionSize(const ASStackLayoutDirection direction, const CGFloat stack, const CGFloat cross) -{ - return (direction == ASStackLayoutDirectionVertical) ? CGSizeMake(cross, stack) : CGSizeMake(stack, cross); -} - -inline void setStackValueToPoint(const ASStackLayoutDirection direction, const CGFloat stack, CGPoint &point) { - (direction == ASStackLayoutDirectionVertical) ? (point.y = stack) : (point.x = stack); -} - -inline ASSizeRange directionSizeRange(const ASStackLayoutDirection direction, - const CGFloat stackMin, - const CGFloat stackMax, - const CGFloat crossMin, - const CGFloat crossMax) -{ - return {directionSize(direction, stackMin, crossMin), directionSize(direction, stackMax, crossMax)}; -} - -inline ASStackLayoutAlignItems alignment(ASStackLayoutAlignSelf childAlignment, ASStackLayoutAlignItems stackAlignment) -{ - switch (childAlignment) { - case ASStackLayoutAlignSelfCenter: - return ASStackLayoutAlignItemsCenter; - case ASStackLayoutAlignSelfEnd: - return ASStackLayoutAlignItemsEnd; - case ASStackLayoutAlignSelfStart: - return ASStackLayoutAlignItemsStart; - case ASStackLayoutAlignSelfStretch: - return ASStackLayoutAlignItemsStretch; - case ASStackLayoutAlignSelfAuto: - default: - return stackAlignment; - } -} - -inline ASStackLayoutAlignItems alignment(ASHorizontalAlignment alignment, ASStackLayoutAlignItems defaultAlignment) -{ - switch (alignment) { - case ASHorizontalAlignmentLeft: - return ASStackLayoutAlignItemsStart; - case ASHorizontalAlignmentMiddle: - return ASStackLayoutAlignItemsCenter; - case ASHorizontalAlignmentRight: - return ASStackLayoutAlignItemsEnd; - case ASHorizontalAlignmentNone: - default: - return defaultAlignment; - } -} - -inline ASStackLayoutAlignItems alignment(ASVerticalAlignment alignment, ASStackLayoutAlignItems defaultAlignment) -{ - switch (alignment) { - case ASVerticalAlignmentTop: - return ASStackLayoutAlignItemsStart; - case ASVerticalAlignmentCenter: - return ASStackLayoutAlignItemsCenter; - case ASVerticalAlignmentBottom: - return ASStackLayoutAlignItemsEnd; - case ASVerticalAlignmentNone: - default: - return defaultAlignment; - } -} - -inline ASStackLayoutJustifyContent justifyContent(ASHorizontalAlignment alignment, ASStackLayoutJustifyContent defaultJustifyContent) -{ - switch (alignment) { - case ASHorizontalAlignmentLeft: - return ASStackLayoutJustifyContentStart; - case ASHorizontalAlignmentMiddle: - return ASStackLayoutJustifyContentCenter; - case ASHorizontalAlignmentRight: - return ASStackLayoutJustifyContentEnd; - case ASHorizontalAlignmentNone: - default: - return defaultJustifyContent; - } -} - -inline ASStackLayoutJustifyContent justifyContent(ASVerticalAlignment alignment, ASStackLayoutJustifyContent defaultJustifyContent) -{ - switch (alignment) { - case ASVerticalAlignmentTop: - return ASStackLayoutJustifyContentStart; - case ASVerticalAlignmentCenter: - return ASStackLayoutJustifyContentCenter; - case ASVerticalAlignmentBottom: - return ASStackLayoutJustifyContentEnd; - case ASVerticalAlignmentNone: - default: - return defaultJustifyContent; - } -} diff --git a/submodules/AsyncDisplayKit/Source/ASStackPositionedLayout.h b/submodules/AsyncDisplayKit/Source/ASStackPositionedLayout.h deleted file mode 100644 index 103ec3a280..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASStackPositionedLayout.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// ASStackPositionedLayout.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 -#import -#import - -/** Represents a set of laid out and positioned stack layout children. */ -struct ASStackPositionedLayout { - const std::vector items; - /** Final size of the stack */ - const CGSize size; - - /** Given an unpositioned layout, computes the positions each child should be placed at. */ - static ASStackPositionedLayout compute(const ASStackUnpositionedLayout &unpositionedLayout, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &constrainedSize); -}; diff --git a/submodules/AsyncDisplayKit/Source/ASStackPositionedLayout.mm b/submodules/AsyncDisplayKit/Source/ASStackPositionedLayout.mm deleted file mode 100644 index 14a59b04ae..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASStackPositionedLayout.mm +++ /dev/null @@ -1,186 +0,0 @@ -// -// ASStackPositionedLayout.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 "ASStackPositionedLayout.h" - -#import -#import - -#import -#import "Private/ASInternalHelpers.h" -#import -#import - -static CGFloat crossOffsetForItem(const ASStackLayoutSpecItem &item, - const ASStackLayoutSpecStyle &style, - const CGFloat crossSize, - const CGFloat baseline) -{ - switch (alignment(item.child.style.alignSelf, style.alignItems)) { - case ASStackLayoutAlignItemsEnd: - return crossSize - crossDimension(style.direction, item.layout.size); - case ASStackLayoutAlignItemsCenter: - return ASFloorPixelValue((crossSize - crossDimension(style.direction, item.layout.size)) / 2); - case ASStackLayoutAlignItemsBaselineFirst: - case ASStackLayoutAlignItemsBaselineLast: - return baseline - ASStackUnpositionedLayout::baselineForItem(style, item); - case ASStackLayoutAlignItemsStart: - case ASStackLayoutAlignItemsStretch: - case ASStackLayoutAlignItemsNotSet: - return 0; - } -} - -static void crossOffsetAndSpacingForEachLine(const std::size_t numOfLines, - const CGFloat crossViolation, - ASStackLayoutAlignContent alignContent, - CGFloat &offset, - CGFloat &spacing) -{ - ASDisplayNodeCAssertTrue(numOfLines > 0); - - // Handle edge cases - if (alignContent == ASStackLayoutAlignContentSpaceBetween && (crossViolation < kViolationEpsilon || numOfLines == 1)) { - alignContent = ASStackLayoutAlignContentStart; - } else if (alignContent == ASStackLayoutAlignContentSpaceAround && (crossViolation < kViolationEpsilon || numOfLines == 1)) { - alignContent = ASStackLayoutAlignContentCenter; - } - - offset = 0; - spacing = 0; - - switch (alignContent) { - case ASStackLayoutAlignContentCenter: - offset = crossViolation / 2; - break; - case ASStackLayoutAlignContentEnd: - offset = crossViolation; - break; - case ASStackLayoutAlignContentSpaceBetween: - // Spacing between the items, no spaces at the edges, evenly distributed - spacing = crossViolation / (numOfLines - 1); - break; - case ASStackLayoutAlignContentSpaceAround: { - // Spacing between items are twice the spacing on the edges - CGFloat spacingUnit = crossViolation / (numOfLines * 2); - offset = spacingUnit; - spacing = spacingUnit * 2; - break; - } - case ASStackLayoutAlignContentStart: - case ASStackLayoutAlignContentStretch: - break; - } -} - -static void stackOffsetAndSpacingForEachItem(const std::size_t numOfItems, - const CGFloat stackViolation, - ASStackLayoutJustifyContent justifyContent, - CGFloat &offset, - CGFloat &spacing) -{ - ASDisplayNodeCAssertTrue(numOfItems > 0); - - // Handle edge cases - if (justifyContent == ASStackLayoutJustifyContentSpaceBetween && (stackViolation < kViolationEpsilon || numOfItems == 1)) { - justifyContent = ASStackLayoutJustifyContentStart; - } else if (justifyContent == ASStackLayoutJustifyContentSpaceAround && (stackViolation < kViolationEpsilon || numOfItems == 1)) { - justifyContent = ASStackLayoutJustifyContentCenter; - } - - offset = 0; - spacing = 0; - - switch (justifyContent) { - case ASStackLayoutJustifyContentCenter: - offset = stackViolation / 2; - break; - case ASStackLayoutJustifyContentEnd: - offset = stackViolation; - break; - case ASStackLayoutJustifyContentSpaceBetween: - // Spacing between the items, no spaces at the edges, evenly distributed - spacing = stackViolation / (numOfItems - 1); - break; - case ASStackLayoutJustifyContentSpaceAround: { - // Spacing between items are twice the spacing on the edges - CGFloat spacingUnit = stackViolation / (numOfItems * 2); - offset = spacingUnit; - spacing = spacingUnit * 2; - break; - } - case ASStackLayoutJustifyContentStart: - break; - } -} - -static void positionItemsInLine(const ASStackUnpositionedLine &line, - const ASStackLayoutSpecStyle &style, - const CGPoint &startingPoint, - const CGFloat stackSpacing) -{ - CGPoint p = startingPoint; - BOOL first = YES; - - for (const auto &item : line.items) { - p = p + directionPoint(style.direction, item.child.style.spacingBefore, 0); - if (!first) { - p = p + directionPoint(style.direction, style.spacing + stackSpacing, 0); - } - first = NO; - item.layout.position = p + directionPoint(style.direction, 0, crossOffsetForItem(item, style, line.crossSize, line.baseline)); - - p = p + directionPoint(style.direction, stackDimension(style.direction, item.layout.size) + item.child.style.spacingAfter, 0); - } -} - -ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnpositionedLayout &layout, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange) -{ - const auto &lines = layout.lines; - if (lines.empty()) { - return {}; - } - - const auto numOfLines = lines.size(); - const auto direction = style.direction; - const auto alignContent = style.alignContent; - const auto lineSpacing = style.lineSpacing; - const auto justifyContent = style.justifyContent; - const auto crossViolation = ASStackUnpositionedLayout::computeCrossViolation(layout.crossDimensionSum, style, sizeRange); - CGFloat crossOffset; - CGFloat crossSpacing; - crossOffsetAndSpacingForEachLine(numOfLines, crossViolation, alignContent, crossOffset, crossSpacing); - - std::vector positionedItems; - CGPoint p = directionPoint(direction, 0, crossOffset); - BOOL first = YES; - for (const auto &line : lines) { - if (!first) { - p = p + directionPoint(direction, 0, crossSpacing + lineSpacing); - } - first = NO; - - const auto &items = line.items; - const auto stackViolation = ASStackUnpositionedLayout::computeStackViolation(line.stackDimensionSum, style, sizeRange); - CGFloat stackOffset; - CGFloat stackSpacing; - stackOffsetAndSpacingForEachItem(items.size(), stackViolation, justifyContent, stackOffset, stackSpacing); - - setStackValueToPoint(direction, stackOffset, p); - positionItemsInLine(line, style, p, stackSpacing); - std::move(items.begin(), items.end(), std::back_inserter(positionedItems)); - - p = p + directionPoint(direction, -stackOffset, line.crossSize); - } - - const CGSize finalSize = directionSize(direction, layout.stackDimensionSum, layout.crossDimensionSum); - return {std::move(positionedItems), ASSizeRangeClamp(sizeRange, finalSize)}; -} diff --git a/submodules/AsyncDisplayKit/Source/ASStackUnpositionedLayout.h b/submodules/AsyncDisplayKit/Source/ASStackUnpositionedLayout.h deleted file mode 100644 index 989b4c2f11..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASStackUnpositionedLayout.h +++ /dev/null @@ -1,73 +0,0 @@ -// -// ASStackUnpositionedLayout.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 - -#import -#import -#import - -/** The threshold that determines if a violation has actually occurred. */ -AS_EXTERN CGFloat const kViolationEpsilon; - -struct ASStackLayoutSpecChild { - /** The original source child. */ - id element; - /** Style object of element. */ - ASLayoutElementStyle *style; - /** Size object of the element */ - ASLayoutElementSize size; -}; - -struct ASStackLayoutSpecItem { - /** The original source child. */ - ASStackLayoutSpecChild child; - /** The proposed layout or nil if no is calculated yet. */ - ASLayout *layout; -}; - -struct ASStackUnpositionedLine { - /** The set of proposed children in this line, each contains child layout, not yet positioned. */ - std::vector items; - /** The total size of the children in the stack dimension, including all spacing. */ - CGFloat stackDimensionSum; - /** The size in the cross dimension */ - CGFloat crossSize; - /** The baseline of the stack which baseline aligned children should align to */ - CGFloat baseline; -}; - -/** Represents a set of stack layout children that have their final layout computed, but are not yet positioned. */ -struct ASStackUnpositionedLayout { - /** The set of proposed lines, each contains child layouts, not yet positioned. */ - const std::vector lines; - /** - * In a single line stack (e.g no wrao), this is the total size of the children in the stack dimension, including all spacing. - * In a multi-line stack, this is the largest stack dimension among lines. - */ - const CGFloat stackDimensionSum; - const CGFloat crossDimensionSum; - - /** Given a set of children, computes the unpositioned layouts for those children. */ - static ASStackUnpositionedLayout compute(const std::vector &children, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange, - const BOOL concurrent); - - static CGFloat baselineForItem(const ASStackLayoutSpecStyle &style, - const ASStackLayoutSpecItem &l); - - static CGFloat computeStackViolation(const CGFloat stackDimensionSum, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange); - - static CGFloat computeCrossViolation(const CGFloat crossDimensionSum, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange); -}; diff --git a/submodules/AsyncDisplayKit/Source/ASStackUnpositionedLayout.mm b/submodules/AsyncDisplayKit/Source/ASStackUnpositionedLayout.mm deleted file mode 100644 index b24b092cf7..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASStackUnpositionedLayout.mm +++ /dev/null @@ -1,758 +0,0 @@ -// -// ASStackUnpositionedLayout.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 "ASStackUnpositionedLayout.h" - -#import -#import - -#import -#import -#import - -CGFloat const kViolationEpsilon = 0.01; - -static CGFloat resolveCrossDimensionMaxForStretchChild(const ASStackLayoutSpecStyle &style, - const ASStackLayoutSpecChild &child, - const CGFloat stackMax, - const CGFloat crossMax) -{ - // stretched children may have a cross direction max that is smaller than the minimum size constraint of the parent. - const CGFloat computedMax = (style.direction == ASStackLayoutDirectionVertical ? - ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).max.width : - ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).max.height); - return computedMax == INFINITY ? crossMax : computedMax; -} - -static CGFloat resolveCrossDimensionMinForStretchChild(const ASStackLayoutSpecStyle &style, - const ASStackLayoutSpecChild &child, - const CGFloat stackMax, - const CGFloat crossMin) -{ - // stretched children will have a cross dimension of at least crossMin, unless they explicitly define a child size - // that is smaller than the constraint of the parent. - return (style.direction == ASStackLayoutDirectionVertical ? - ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).min.width : - ASLayoutElementSizeResolve(child.style.size, ASLayoutElementParentSizeUndefined).min.height) ?: crossMin; -} - -/** - Sizes the child given the parameters specified, and returns the computed layout. - */ -static ASLayout *crossChildLayout(const ASStackLayoutSpecChild &child, - const ASStackLayoutSpecStyle &style, - const CGFloat stackMin, - const CGFloat stackMax, - const CGFloat crossMin, - const CGFloat crossMax, - const CGSize parentSize) -{ - const ASStackLayoutAlignItems alignItems = alignment(child.style.alignSelf, style.alignItems); - // stretched children will have a cross dimension of at least crossMin - const CGFloat childCrossMin = (alignItems == ASStackLayoutAlignItemsStretch ? - resolveCrossDimensionMinForStretchChild(style, child, stackMax, crossMin) : - 0); - const CGFloat childCrossMax = (alignItems == ASStackLayoutAlignItemsStretch ? - resolveCrossDimensionMaxForStretchChild(style, child, stackMax, crossMax) : - crossMax); - const ASSizeRange childSizeRange = directionSizeRange(style.direction, stackMin, stackMax, childCrossMin, childCrossMax); - ASLayout *layout = [child.element layoutThatFits:childSizeRange parentSize:parentSize]; - ASDisplayNodeCAssertNotNil(layout, @"ASLayout returned from -layoutThatFits:parentSize: must not be nil: %@", child.element); - return layout ? : [ASLayout layoutWithLayoutElement:child.element size:{0, 0}]; -} - -static void dispatchApplyIfNeeded(size_t iterationCount, BOOL forced, void(^work)(size_t i)) -{ - if (iterationCount == 0) { - return; - } - - if (iterationCount == 1) { - work(0); - return; - } - - // TODO Once the locking situation in ASDisplayNode has improved, always dispatch if on main - if (forced == NO) { - for (size_t i = 0; i < iterationCount; i++) { - work(i); - } - return; - } - - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - ASDispatchApply(iterationCount, queue, 0, work); -} - -/** - Computes the consumed cross dimension length for the given vector of lines and stacking style. - - Cross Dimension - +---------------------> - +--------+ +--------+ +--------+ +---------+ - Vertical |Vertical| |Vertical| |Vertical| |Vertical | - Stack | Line 1 | | Line 2 | | Line 3 | | Line 4 | - | | | | | | | | - +--------+ +--------+ +--------+ +---------+ - crossDimensionSum - |------------------------------------------| - - @param lines unpositioned lines - */ -static CGFloat computeLinesCrossDimensionSum(const std::vector &lines, - const ASStackLayoutSpecStyle &style) -{ - return std::accumulate(lines.begin(), lines.end(), - // Start from default spacing between each line: - lines.empty() ? 0 : style.lineSpacing * (lines.size() - 1), - [&](CGFloat x, const ASStackUnpositionedLine &l) { - return x + l.crossSize; - }); -} - - -/** - Computes the violation by comparing a cross dimension sum with the overall allowable size range for the stack. - - Violation is the distance you would have to add to the unbounded cross-direction length of the stack spec's - lines in order to bring the stack within its allowed sizeRange. The diagram below shows 3 vertical stacks, each contains 3-5 vertical lines, - with the different types of violation. - - Cross Dimension - +---------------------> - cross size range - |------------| - +--------+ +--------+ +--------+ +---------+ - - - - - - - - - Vertical |Vertical| |Vertical| |Vertical| |Vertical | | ^ - Stack 1 | Line 1 | | Line 2 | | Line 3 | | Line 4 | (zero violation) | stack size range - | | | | | | | | | | v - +--------+ +--------+ +--------+ +---------+ - - - - - - - - - | | - +--------+ +--------+ +--------+ - - - - - - - - - - - - - Vertical | | | | | | | | ^ - Stack 2 | | | | | |<--> (positive violation) | stack size range - | | | | | | | | v - +--------+ +--------+ +--------+ - - - - - - - - - - - - - | |<------> (negative violation) - +--------+ +--------+ +--------+ +---------+ +-----------+ - - - - Vertical | | | | | | | | | | | | ^ - Stack 3 | | | | | | | | | | | stack size range - | | | | | | | | | | | | v - +--------+ +--------+ +--------+ +---------+ +-----------+ - - - - - @param crossDimensionSum the consumed length of the lines in the stack along the cross dimension - @param style layout style to be applied to all children - @param sizeRange the range of allowable sizes for the stack layout spec - */ -CGFloat ASStackUnpositionedLayout::computeCrossViolation(const CGFloat crossDimensionSum, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange) -{ - const CGFloat minCrossDimension = crossDimension(style.direction, sizeRange.min); - const CGFloat maxCrossDimension = crossDimension(style.direction, sizeRange.max); - if (crossDimensionSum < minCrossDimension) { - return minCrossDimension - crossDimensionSum; - } else if (crossDimensionSum > maxCrossDimension) { - return maxCrossDimension - crossDimensionSum; - } - return 0; -} - -/** - Stretches children to lay out along the cross axis according to the alignment stretch settings of the children - (child.alignSelf), and the stack layout's alignment settings (style.alignItems). This does not do the actual alignment - of the items once stretched though; ASStackPositionedLayout will do centering etc. - - Finds the maximum cross dimension among child layouts. If that dimension exceeds the minimum cross layout size then - we must stretch any children whose alignItems specify ASStackLayoutAlignItemsStretch. - - The diagram below shows 3 children in a horizontal stack. The second child is larger than the minCrossDimension, so - its height is used as the childCrossMax. Any children that are stretchable (which may be all children if - style.alignItems specifies stretch) like the first child must be stretched to match that maximum. All children must be - at least minCrossDimension in cross dimension size, which is shown by the sizing of the third child. - - Stack Dimension - +---------------------> - + +-+-------------+-+-------------+--+---------------+ + + + - | | child. | | | | | | | | - | | alignSelf | | | | | | | | - Cross | | = stretch | | | +-------+-------+ | | | - Dimension | +-----+-------+ | | | | | | | | - | | | | | | | | | | - | | | | | v | | | | - v +-+- - - - - - -+-+ - - - - - - +--+- - - - - - - -+ | | + minCrossDimension - | | | | | - | v | | | | | - +- - - - - - -+ +-------------+ | + childCrossMax - | - +--------------------------------------------------+ + crossMax - - @param items pre-computed items; modified in-place as needed - @param style the layout style of the overall stack layout - */ -static void stretchItemsAlongCrossDimension(std::vector &items, - const ASStackLayoutSpecStyle &style, - const BOOL concurrent, - const CGSize parentSize, - const CGFloat crossSize) -{ - dispatchApplyIfNeeded(items.size(), concurrent, ^(size_t i) { - auto &item = items[i]; - const ASStackLayoutAlignItems alignItems = alignment(item.child.style.alignSelf, style.alignItems); - if (alignItems == ASStackLayoutAlignItemsStretch) { - const CGFloat cross = crossDimension(style.direction, item.layout.size); - const CGFloat stack = stackDimension(style.direction, item.layout.size); - const CGFloat violation = crossSize - cross; - - // Only stretch if violation is positive. Compare against kViolationEpsilon here to avoid stretching against a tiny violation. - if (violation > kViolationEpsilon) { - item.layout = crossChildLayout(item.child, style, stack, stack, crossSize, crossSize, parentSize); - } - } - }); -} - -/** - * Stretch lines and their items according to alignContent, alignItems and alignSelf. - * https://www.w3.org/TR/css-flexbox-1/#algo-line-stretch - * https://www.w3.org/TR/css-flexbox-1/#algo-stretch - */ -static void stretchLinesAlongCrossDimension(std::vector &lines, - const ASStackLayoutSpecStyle &style, - const BOOL concurrent, - const ASSizeRange &sizeRange, - const CGSize parentSize) -{ - ASDisplayNodeCAssertFalse(lines.empty()); - const std::size_t numOfLines = lines.size(); - const CGFloat violation = ASStackUnpositionedLayout::computeCrossViolation(computeLinesCrossDimensionSum(lines, style), style, sizeRange); - // Don't stretch if the stack is single line, because the line's cross size was clamped against the stack's constrained size. - const BOOL shouldStretchLines = (numOfLines > 1 - && style.alignContent == ASStackLayoutAlignContentStretch - && violation > kViolationEpsilon); - - CGFloat extraCrossSizePerLine = violation / numOfLines; - for (auto &line : lines) { - if (shouldStretchLines) { - line.crossSize += extraCrossSizePerLine; - } - - stretchItemsAlongCrossDimension(line.items, style, concurrent, parentSize, line.crossSize); - } -} - -static BOOL itemIsBaselineAligned(const ASStackLayoutSpecStyle &style, - const ASStackLayoutSpecItem &l) -{ - ASStackLayoutAlignItems alignItems = alignment(l.child.style.alignSelf, style.alignItems); - return alignItems == ASStackLayoutAlignItemsBaselineFirst || alignItems == ASStackLayoutAlignItemsBaselineLast; -} - -CGFloat ASStackUnpositionedLayout::baselineForItem(const ASStackLayoutSpecStyle &style, - const ASStackLayoutSpecItem &item) -{ - switch (alignment(item.child.style.alignSelf, style.alignItems)) { - case ASStackLayoutAlignItemsBaselineFirst: - return item.child.style.ascender; - case ASStackLayoutAlignItemsBaselineLast: - return crossDimension(style.direction, item.layout.size) + item.child.style.descender; - default: - return 0; - } -} - -/** - * Computes cross size and baseline of each line. - * https://www.w3.org/TR/css-flexbox-1/#algo-cross-line - * - * @param lines All items to lay out - * @param style the layout style of the overall stack layout - * @param sizeRange the range of allowable sizes for the stack layout component - */ -static void computeLinesCrossSizeAndBaseline(std::vector &lines, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange) -{ - ASDisplayNodeCAssertFalse(lines.empty()); - const BOOL isSingleLine = (lines.size() == 1); - - const auto minCrossSize = crossDimension(style.direction, sizeRange.min); - const auto maxCrossSize = crossDimension(style.direction, sizeRange.max); - const BOOL definiteCrossSize = (minCrossSize == maxCrossSize); - - // If the stack is single-line and has a definite cross size, the cross size of the line is the stack's definite cross size. - if (isSingleLine && definiteCrossSize) { - auto &line = lines[0]; - line.crossSize = minCrossSize; - - // We still need to determine the line's baseline - //TODO unit test - for (const auto &item : line.items) { - if (itemIsBaselineAligned(style, item)) { - CGFloat baseline = ASStackUnpositionedLayout::baselineForItem(style, item); - line.baseline = MAX(line.baseline, baseline); - } - } - - return; - } - - for (auto &line : lines) { - const auto &items = line.items; - CGFloat maxStartToBaselineDistance = 0; - CGFloat maxBaselineToEndDistance = 0; - CGFloat maxItemCrossSize = 0; - - for (const auto &item : items) { - if (itemIsBaselineAligned(style, item)) { - // Step 1. Collect all the items whose align-self is baseline. Find the largest of the distances - // between each item’s baseline and its hypothetical outer cross-start edge (aka. its baseline value), - // and the largest of the distances between each item’s baseline and its hypothetical outer cross-end edge, - // and sum these two values. - CGFloat baseline = ASStackUnpositionedLayout::baselineForItem(style, item); - maxStartToBaselineDistance = MAX(maxStartToBaselineDistance, baseline); - maxBaselineToEndDistance = MAX(maxBaselineToEndDistance, crossDimension(style.direction, item.layout.size) - baseline); - } else { - // Step 2. Among all the items not collected by the previous step, find the largest outer hypothetical cross size. - maxItemCrossSize = MAX(maxItemCrossSize, crossDimension(style.direction, item.layout.size)); - } - } - - // Step 3. The used cross-size of the flex line is the largest of the numbers found in the previous two steps and zero. - line.crossSize = MAX(maxStartToBaselineDistance + maxBaselineToEndDistance, maxItemCrossSize); - if (isSingleLine) { - // If the stack is single-line, then clamp the line’s cross-size to be within the stack's min and max cross-size properties. - line.crossSize = MIN(MAX(minCrossSize, line.crossSize), maxCrossSize); - } - - line.baseline = maxStartToBaselineDistance; - } -} - -/** - Returns a lambda that computes the relevant flex factor based on the given violation. - @param violation The amount that the stack layout violates its size range. See header for sign interpretation. - */ -static std::function flexFactorInViolationDirection(const CGFloat violation) -{ - if (std::fabs(violation) < kViolationEpsilon) { - return [](const ASStackLayoutSpecItem &item) { return 0.0; }; - } else if (violation > 0) { - return [](const ASStackLayoutSpecItem &item) { return item.child.style.flexGrow; }; - } else { - return [](const ASStackLayoutSpecItem &item) { return item.child.style.flexShrink; }; - } -} - -static inline CGFloat scaledFlexShrinkFactor(const ASStackLayoutSpecItem &item, - const ASStackLayoutSpecStyle &style, - const CGFloat flexFactorSum) -{ - return stackDimension(style.direction, item.layout.size) * (item.child.style.flexShrink / flexFactorSum); -} - -/** - Returns a lambda that computes a flex shrink adjustment for a given item based on the provided violation. - @param items The unpositioned items from the original unconstrained layout pass. - @param style The layout style to be applied to all children. - @param violation The amount that the stack layout violates its size range. - @param flexFactorSum The sum of each item's flex factor as determined by the provided violation. - @return A lambda capable of computing the flex shrink adjustment, if any, for a particular item. - */ -static std::function flexShrinkAdjustment(const std::vector &items, - const ASStackLayoutSpecStyle &style, - const CGFloat violation, - const CGFloat flexFactorSum) -{ - const CGFloat scaledFlexShrinkFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackLayoutSpecItem &item) { - return x + scaledFlexShrinkFactor(item, style, flexFactorSum); - }); - return [style, scaledFlexShrinkFactorSum, violation, flexFactorSum](const ASStackLayoutSpecItem &item) { - if (scaledFlexShrinkFactorSum == 0.0) { - return (CGFloat)0.0; - } - - const CGFloat scaledFlexShrinkFactorRatio = scaledFlexShrinkFactor(item, style, flexFactorSum) / scaledFlexShrinkFactorSum; - // The item should shrink proportionally to the scaled flex shrink factor ratio computed above. - // Unlike the flex grow adjustment the flex shrink adjustment needs to take the size of each item into account. - return -std::fabs(scaledFlexShrinkFactorRatio * violation); - }; -} - -/** - Returns a lambda that computes a flex grow adjustment for a given item based on the provided violation. - @param items The unpositioned items from the original unconstrained layout pass. - @param violation The amount that the stack layout violates its size range. - @param flexFactorSum The sum of each item's flex factor as determined by the provided violation. - @return A lambda capable of computing the flex grow adjustment, if any, for a particular item. - */ -static std::function flexGrowAdjustment(const std::vector &items, - const CGFloat violation, - const CGFloat flexFactorSum) -{ - // To compute the flex grow adjustment distribute the violation proportionally based on each item's flex grow factor. - return [violation, flexFactorSum](const ASStackLayoutSpecItem &item) { - return std::floor(violation * (item.child.style.flexGrow / flexFactorSum)); - }; -} - -/** - Returns a lambda that computes a flex adjustment for a given item based on the provided violation. - @param items The unpositioned items from the original unconstrained layout pass. - @param style The layout style to be applied to all children. - @param violation The amount that the stack layout violates its size range. - @param flexFactorSum The sum of each item's flex factor as determined by the provided violation. - @return A lambda capable of computing the flex adjustment for a particular item. - */ -static std::function flexAdjustmentInViolationDirection(const std::vector &items, - const ASStackLayoutSpecStyle &style, - const CGFloat violation, - const CGFloat flexFactorSum) -{ - if (violation > 0) { - return flexGrowAdjustment(items, violation, flexFactorSum); - } else { - return flexShrinkAdjustment(items, style, violation, flexFactorSum); - } -} - -ASDISPLAYNODE_INLINE BOOL isFlexibleInBothDirections(const ASStackLayoutSpecChild &child) -{ - return child.style.flexGrow > 0 && child.style.flexShrink > 0; -} - -/** - The flexible children may have been left not laid out in the initial layout pass, so we may have to go through and size - these children at zero size so that the children layouts are at least present. - */ -static void layoutFlexibleChildrenAtZeroSize(std::vector &items, - const ASStackLayoutSpecStyle &style, - const BOOL concurrent, - const ASSizeRange &sizeRange, - const CGSize parentSize) -{ - dispatchApplyIfNeeded(items.size(), concurrent, ^(size_t i) { - auto &item = items[i]; - if (isFlexibleInBothDirections(item.child)) { - item.layout = crossChildLayout(item.child, - style, - 0, - 0, - crossDimension(style.direction, sizeRange.min), - crossDimension(style.direction, sizeRange.max), - parentSize); - } - }); -} - -/** - Computes the consumed stack dimension length for the given vector of items and stacking style. - - stackDimensionSum - <-----------------------> - +-----+ +-------+ +---+ - | | | | | | - | | | | | | - +-----+ | | +---+ - +-------+ - - @param items unpositioned layouts for items - @param style the layout style of the overall stack layout - */ -static CGFloat computeItemsStackDimensionSum(const std::vector &items, - const ASStackLayoutSpecStyle &style) -{ - // Sum up the childrens' spacing - const CGFloat childSpacingSum = std::accumulate(items.begin(), items.end(), - // Start from default spacing between each child: - items.empty() ? 0 : style.spacing * (items.size() - 1), - [&](CGFloat x, const ASStackLayoutSpecItem &l) { - return x + l.child.style.spacingBefore + l.child.style.spacingAfter; - }); - - // Sum up the childrens' dimensions (including spacing) in the stack direction. - const CGFloat childStackDimensionSum = std::accumulate(items.begin(), items.end(), - childSpacingSum, - [&](CGFloat x, const ASStackLayoutSpecItem &l) { - return x + stackDimension(style.direction, l.layout.size); - }); - return childStackDimensionSum; -} - -//TODO move this up near computeCrossViolation and make both methods share the same code path, to make sure they share the same concept of "negative" and "positive" violations. -/** - Computes the violation by comparing a stack dimension sum with the overall allowable size range for the stack. - - Violation is the distance you would have to add to the unbounded stack-direction length of the stack spec's - children in order to bring the stack within its allowed sizeRange. The diagram below shows 3 horizontal stacks with - the different types of violation. - - sizeRange - |------------| - +------+ +-------+ +-------+ +---------+ - | | | | | | | | | | - | | | | | | | | (zero violation) - | | | | | | | | | | - +------+ +-------+ +-------+ +---------+ - | | - +------+ +-------+ +-------+ - | | | | | | | | - | | | | | |<--> (positive violation) - | | | | | | | | - +------+ +-------+ +-------+ - | |<------> (negative violation) - +------+ +-------+ +-------+ +---------+ +-----------+ - | | | | | | | | | | | | - | | | | | | | | | | - | | | | | | | | | | | | - +------+ +-------+ +-------+ +---------+ +-----------+ - - @param stackDimensionSum the consumed length of the children in the stack along the stack dimension - @param style layout style to be applied to all children - @param sizeRange the range of allowable sizes for the stack layout spec - */ -CGFloat ASStackUnpositionedLayout::computeStackViolation(const CGFloat stackDimensionSum, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange) -{ - const CGFloat minStackDimension = stackDimension(style.direction, sizeRange.min); - const CGFloat maxStackDimension = stackDimension(style.direction, sizeRange.max); - if (stackDimensionSum < minStackDimension) { - return minStackDimension - stackDimensionSum; - } else if (stackDimensionSum > maxStackDimension) { - return maxStackDimension - stackDimensionSum; - } - return 0; -} - -/** - If we have a single flexible (both shrinkable and growable) child, and our allowed size range is set to a specific - number then we may avoid the first "intrinsic" size calculation. - */ -ASDISPLAYNODE_INLINE BOOL useOptimizedFlexing(const std::vector &children, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange) -{ - const NSUInteger flexibleChildren = std::count_if(children.begin(), children.end(), isFlexibleInBothDirections); - return ((flexibleChildren == 1) - && (stackDimension(style.direction, sizeRange.min) == - stackDimension(style.direction, sizeRange.max))); -} - -/** - Flexes children in the stack axis to resolve a min or max stack size violation. First, determines which children are - flexible (see computeStackViolation and isFlexibleInViolationDirection). Then computes how much to flex each flexible child - and performs re-layout. Note that there may still be a non-zero violation even after flexing. - - The actual CSS flexbox spec describes an iterative looping algorithm here, which may be adopted in t5837937: - http://www.w3.org/TR/css3-flexbox/#resolve-flexible-lengths - - @param lines reference to unpositioned lines and items from the original, unconstrained layout pass; modified in-place - @param style layout style to be applied to all children - @param sizeRange the range of allowable sizes for the stack layout component - @param parentSize Size of the stack layout component. May be undefined in either or both directions. - */ -static void flexLinesAlongStackDimension(std::vector &lines, - const ASStackLayoutSpecStyle &style, - const BOOL concurrent, - const ASSizeRange &sizeRange, - const CGSize parentSize, - const BOOL useOptimizedFlexing) -{ - for (auto &line : lines) { - auto &items = line.items; - const CGFloat violation = ASStackUnpositionedLayout::computeStackViolation(computeItemsStackDimensionSum(items, style), style, sizeRange); - std::function flexFactor = flexFactorInViolationDirection(violation); - // The flex factor sum is needed to determine if flexing is necessary. - // This value is also needed if the violation is positive and flexible items need to grow, so keep it around. - const CGFloat flexFactorSum = std::accumulate(items.begin(), items.end(), 0.0, [&](CGFloat x, const ASStackLayoutSpecItem &item) { - return x + flexFactor(item); - }); - - // If no items are able to flex then there is nothing left to do with this line. Bail. - if (flexFactorSum == 0) { - // If optimized flexing was used then we have to clean up the unsized items and lay them out at zero size. - if (useOptimizedFlexing) { - layoutFlexibleChildrenAtZeroSize(items, style, concurrent, sizeRange, parentSize); - } - continue; - } - - std::function flexAdjustment = flexAdjustmentInViolationDirection(items, - style, - violation, - flexFactorSum); - // Compute any remaining violation to the first flexible item. - const CGFloat remainingViolation = std::accumulate(items.begin(), items.end(), violation, [&](CGFloat x, const ASStackLayoutSpecItem &item) { - return x - flexAdjustment(item); - }); - - size_t firstFlexItem = -1; - for(size_t i = 0; i < items.size(); i++) { - // Items are consider inflexible if they do not need to make a flex adjustment. - if (flexAdjustment(items[i]) != 0) { - firstFlexItem = i; - break; - } - } - if (firstFlexItem == -1) { - continue; - } - - dispatchApplyIfNeeded(items.size(), concurrent, ^(size_t i) { - auto &item = items[i]; - const CGFloat currentFlexAdjustment = flexAdjustment(item); - // Items are consider inflexible if they do not need to make a flex adjustment. - if (currentFlexAdjustment != 0) { - const CGFloat originalStackSize = stackDimension(style.direction, item.layout.size); - // Only apply the remaining violation for the first flexible item that has a flex grow factor. - const CGFloat flexedStackSize = originalStackSize + currentFlexAdjustment + (i == firstFlexItem && item.child.style.flexGrow > 0 ? remainingViolation : 0); - item.layout = crossChildLayout(item.child, - style, - MAX(flexedStackSize, 0), - MAX(flexedStackSize, 0), - crossDimension(style.direction, sizeRange.min), - crossDimension(style.direction, sizeRange.max), - parentSize); - } - }); - } -} - -/** - https://www.w3.org/TR/css-flexbox-1/#algo-line-break - */ -static std::vector collectChildrenIntoLines(const std::vector &items, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange) -{ - //TODO if infinite max stack size, fast path - if (style.flexWrap == ASStackLayoutFlexWrapNoWrap) { - return std::vector (1, {.items = std::move(items)}); - } - - std::vector lines; - std::vector lineItems; - CGFloat lineStackDimensionSum = 0; - CGFloat interitemSpacing = 0; - - for(auto it = items.begin(); it != items.end(); ++it) { - const auto &item = *it; - const CGFloat itemStackDimension = stackDimension(style.direction, item.layout.size); - const CGFloat itemAndSpacingStackDimension = item.child.style.spacingBefore + itemStackDimension + item.child.style.spacingAfter; - const BOOL negativeViolationIfAddItem = (ASStackUnpositionedLayout::computeStackViolation(lineStackDimensionSum + interitemSpacing + itemAndSpacingStackDimension, style, sizeRange) < 0); - const BOOL breakCurrentLine = negativeViolationIfAddItem && !lineItems.empty(); - - if (breakCurrentLine) { - lines.push_back({.items = std::vector (lineItems)}); - lineItems.clear(); - lineStackDimensionSum = 0; - interitemSpacing = 0; - } - - lineItems.push_back(std::move(item)); - lineStackDimensionSum += interitemSpacing + itemAndSpacingStackDimension; - interitemSpacing = style.spacing; - } - - // Handle last line - lines.push_back({.items = std::vector (lineItems)}); - - return lines; -} - -/** - Performs the first unconstrained layout of the children, generating the unpositioned items that are then flexed and - stretched. - */ -static void layoutItemsAlongUnconstrainedStackDimension(std::vector &items, - const ASStackLayoutSpecStyle &style, - const BOOL concurrent, - const ASSizeRange &sizeRange, - const CGSize parentSize, - const BOOL useOptimizedFlexing) -{ - const CGFloat minCrossDimension = crossDimension(style.direction, sizeRange.min); - const CGFloat maxCrossDimension = crossDimension(style.direction, sizeRange.max); - - dispatchApplyIfNeeded(items.size(), concurrent, ^(size_t i) { - auto &item = items[i]; - if (useOptimizedFlexing && isFlexibleInBothDirections(item.child)) { - item.layout = [ASLayout layoutWithLayoutElement:item.child.element size:{0, 0}]; - } else { - item.layout = crossChildLayout(item.child, - style, - ASDimensionResolve(item.child.style.flexBasis, stackDimension(style.direction, parentSize), 0), - ASDimensionResolve(item.child.style.flexBasis, stackDimension(style.direction, parentSize), INFINITY), - minCrossDimension, - maxCrossDimension, - parentSize); - } - }); -} - -ASStackUnpositionedLayout ASStackUnpositionedLayout::compute(const std::vector &children, - const ASStackLayoutSpecStyle &style, - const ASSizeRange &sizeRange, - const BOOL concurrent) -{ - if (children.empty()) { - return {}; - } - - // If we have a fixed size in either dimension, pass it to children so they can resolve percentages against it. - // Otherwise, we pass ASLayoutElementParentDimensionUndefined since it will depend on the content. - const CGSize parentSize = { - (sizeRange.min.width == sizeRange.max.width) ? sizeRange.min.width : ASLayoutElementParentDimensionUndefined, - (sizeRange.min.height == sizeRange.max.height) ? sizeRange.min.height : ASLayoutElementParentDimensionUndefined, - }; - - // We may be able to avoid some redundant layout passes - const BOOL optimizedFlexing = useOptimizedFlexing(children, style, sizeRange); - - std::vector items = AS::map(children, [&](const ASStackLayoutSpecChild &child) -> ASStackLayoutSpecItem { - return {child, nil}; - }); - - // We do a first pass of all the children, generating an unpositioned layout for each with an unbounded range along - // the stack dimension. This allows us to compute the "intrinsic" size of each child and find the available violation - // which determines whether we must grow or shrink the flexible children. - layoutItemsAlongUnconstrainedStackDimension(items, - style, - concurrent, - sizeRange, - parentSize, - optimizedFlexing); - - // Collect items into lines (https://www.w3.org/TR/css-flexbox-1/#algo-line-break) - std::vector lines = collectChildrenIntoLines(items, style, sizeRange); - - // Resolve the flexible lengths (https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths) - flexLinesAlongStackDimension(lines, style, concurrent, sizeRange, parentSize, optimizedFlexing); - - // Calculate the cross size of each flex line (https://www.w3.org/TR/css-flexbox-1/#algo-cross-line) - computeLinesCrossSizeAndBaseline(lines, style, sizeRange); - - // Handle 'align-content: stretch' (https://www.w3.org/TR/css-flexbox-1/#algo-line-stretch) - // Determine the used cross size of each item (https://www.w3.org/TR/css-flexbox-1/#algo-stretch) - stretchLinesAlongCrossDimension(lines, style, concurrent, sizeRange, parentSize); - - // Compute stack dimension sum of each line and the whole stack - CGFloat layoutStackDimensionSum = 0; - for (auto &line : lines) { - line.stackDimensionSum = computeItemsStackDimensionSum(line.items, style); - // layoutStackDimensionSum is the max stackDimensionSum among all lines - layoutStackDimensionSum = MAX(line.stackDimensionSum, layoutStackDimensionSum); - } - // Compute cross dimension sum of the stack. - // This should be done before `lines` are moved to a new ASStackUnpositionedLayout struct (i.e `std::move(lines)`) - CGFloat layoutCrossDimensionSum = computeLinesCrossDimensionSum(lines, style); - - return {.lines = std::move(lines), .stackDimensionSum = layoutStackDimensionSum, .crossDimensionSum = layoutCrossDimensionSum}; -} diff --git a/submodules/AsyncDisplayKit/Source/ASTabBarController.h b/submodules/AsyncDisplayKit/Source/ASTabBarController.h deleted file mode 100644 index 7479f7ac85..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTabBarController.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// ASTabBarController.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 - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * ASTabBarController - * - * @discussion ASTabBarController is a drop in replacement for UITabBarController - * which implements the memory efficiency improving @c ASManagesChildVisibilityDepth protocol. - * - * @see ASManagesChildVisibilityDepth - */ -@interface ASTabBarController : UITabBarController - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTabBarController.mm b/submodules/AsyncDisplayKit/Source/ASTabBarController.mm deleted file mode 100644 index c0f9e39dd3..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTabBarController.mm +++ /dev/null @@ -1,87 +0,0 @@ -// -// ASTabBarController.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 -#import - -@implementation ASTabBarController -{ - BOOL _parentManagesVisibilityDepth; - NSInteger _visibilityDepth; -} - -ASVisibilityDidMoveToParentViewController; - -ASVisibilityViewWillAppear; - -ASVisibilityViewDidDisappearImplementation; - -ASVisibilitySetVisibilityDepth; - -ASVisibilityDepthImplementation; - -- (void)visibilityDepthDidChange -{ - for (UIViewController *viewController in self.viewControllers) { - if ([viewController conformsToProtocol:@protocol(ASVisibilityDepth)]) { - [(id )viewController visibilityDepthDidChange]; - } - } -} - -- (NSInteger)visibilityDepthOfChildViewController:(UIViewController *)childViewController -{ - NSUInteger viewControllerIndex = [self.viewControllers indexOfObjectIdenticalTo:childViewController]; - if (viewControllerIndex == NSNotFound) { - //If childViewController is not actually a child, return NSNotFound which is also a really large number. - return NSNotFound; - } - - if (self.selectedViewController == childViewController) { - return [self visibilityDepth]; - } - return [self visibilityDepth] + 1; -} - -#pragma mark - UIKit overrides - -- (void)setViewControllers:(NSArray<__kindof UIViewController *> *)viewControllers -{ - [super setViewControllers:viewControllers]; - [self visibilityDepthDidChange]; -} - -- (void)setViewControllers:(NSArray<__kindof UIViewController *> *)viewControllers animated:(BOOL)animated -{ - [super setViewControllers:viewControllers animated:animated]; - [self visibilityDepthDidChange]; -} - -- (void)setSelectedIndex:(NSUInteger)selectedIndex -{ - as_activity_create_for_scope("Set selected index of ASTabBarController"); - as_log_info(ASNodeLog(), "Selected tab %tu of %@", selectedIndex, self); - - [super setSelectedIndex:selectedIndex]; - [self visibilityDepthDidChange]; -} - -- (void)setSelectedViewController:(__kindof UIViewController *)selectedViewController -{ - as_activity_create_for_scope("Set selected view controller of ASTabBarController"); - as_log_info(ASNodeLog(), "Selected view controller %@ of %@", selectedViewController, self); - - [super setSelectedViewController:selectedViewController]; - [self visibilityDepthDidChange]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTableLayoutController.h b/submodules/AsyncDisplayKit/Source/ASTableLayoutController.h deleted file mode 100644 index c294c2ecb4..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTableLayoutController.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// ASTableLayoutController.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 -#import - -NS_ASSUME_NONNULL_BEGIN - -@class UITableView; - -/** - * A layout controller designed for use with UITableView. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASTableLayoutController : ASAbstractLayoutController - -@property (nonatomic, weak, readonly) UITableView *tableView; - -- (instancetype)initWithTableView:(UITableView *)tableView; - -@end - -NS_ASSUME_NONNULL_END -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTableLayoutController.mm b/submodules/AsyncDisplayKit/Source/ASTableLayoutController.mm deleted file mode 100644 index 0669c76131..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTableLayoutController.mm +++ /dev/null @@ -1,55 +0,0 @@ -// -// ASTableLayoutController.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 - -#import - -#import -#import - -@interface ASTableLayoutController() -@end - -@implementation ASTableLayoutController - -- (instancetype)initWithTableView:(UITableView *)tableView -{ - if (!(self = [super init])) { - return nil; - } - _tableView = tableView; - return self; -} - -#pragma mark - ASLayoutController - -- (NSHashTable *)elementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType map:(ASElementMap *)map -{ - CGRect bounds = _tableView.bounds; - - ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType]; - CGRect rangeBounds = CGRectExpandToRangeWithScrollableDirections(bounds, tuningParameters, ASScrollDirectionVerticalDirections, scrollDirection); - NSArray *array = [_tableView indexPathsForRowsInRect:rangeBounds]; - return ASPointerTableByFlatMapping(array, NSIndexPath *indexPath, [map elementForItemAtIndexPath:indexPath]); -} - -- (void)allElementsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode displaySet:(NSHashTable *__autoreleasing _Nullable *)displaySet preloadSet:(NSHashTable *__autoreleasing _Nullable *)preloadSet map:(ASElementMap *)map -{ - if (displaySet == NULL || preloadSet == NULL) { - return; - } - - *displaySet = [self elementsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypeDisplay map:map]; - *preloadSet = [self elementsForScrolling:scrollDirection rangeMode:rangeMode rangeType:ASLayoutRangeTypePreload map:map]; - return; -} - -@end -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTableNode+Beta.h b/submodules/AsyncDisplayKit/Source/ASTableNode+Beta.h deleted file mode 100644 index 6d580bdc43..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTableNode+Beta.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// ASTableNode+Beta.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@protocol ASBatchFetchingDelegate; - -NS_ASSUME_NONNULL_BEGIN - -@interface ASTableNode (Beta) - -@property (nonatomic, weak) id batchFetchingDelegate; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASTableNode.h b/submodules/AsyncDisplayKit/Source/ASTableNode.h deleted file mode 100644 index f05c9f1dc8..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTableNode.h +++ /dev/null @@ -1,759 +0,0 @@ -// -// ASTableNode.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 -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASTableDataSource; -@protocol ASTableDelegate; -@class ASTableView, ASBatchContext; - -/** - * ASTableNode is a node based class that wraps an ASTableView. It can be used - * as a subnode of another node, and provide room for many (great) features and improvements later on. - */ -@interface ASTableNode : ASDisplayNode - -- (instancetype)init; // UITableViewStylePlain -- (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER; - -@property (readonly) ASTableView *view; - -// These properties can be set without triggering the view to be created, so it's fine to set them in -init. -@property (nonatomic, weak) id delegate; -@property (nonatomic, weak) id dataSource; - -/** - * The number of screens left to scroll before the delegate -tableNode:beginBatchFetchingWithContext: is called. - * - * Defaults to two screenfuls. - */ -@property (nonatomic) CGFloat leadingScreensForBatching; - -/* - * A Boolean value that determines whether the table will be flipped. - * If the value of this property is YES, the first cell node will be at the bottom of the table (as opposed to the top by default). This is useful for chat/messaging apps. The default value is NO. - */ -@property (nonatomic) BOOL inverted; - -/** - * The distance that the content view is inset from the table node edges. Defaults to UIEdgeInsetsZero. - */ -@property (nonatomic) UIEdgeInsets contentInset; - -/** - * The offset of the content view's origin from the table node's origin. Defaults to CGPointZero. - */ -@property (nonatomic) CGPoint contentOffset; - -/** - * Sets the offset from the content node’s origin to the table node’s 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; - -/** - * YES to automatically adjust the contentOffset when cells are inserted or deleted above - * visible cells, maintaining the users' visible scroll position. - * - * @note This is only applied to non-animated updates. For animated updates, there is no way to - * synchronize or "cancel out" the appearance of a scroll due to UITableView API limitations. - * - * default is NO. - */ -@property (nonatomic) BOOL automaticallyAdjustsContentOffset; - -/* - * A Boolean value that determines whether users can select a row. - * If the value of this property is YES (the default), users can select rows. If you set it to NO, they cannot select rows. Setting this property affects cell selection only when the table view is not in editing mode. If you want to restrict selection of cells in editing mode, use `allowsSelectionDuringEditing`. - */ -@property (nonatomic) BOOL allowsSelection; -/* - * A Boolean value that determines whether users can select cells while the table view is in editing mode. - * If the value of this property is YES, users can select rows during editing. The default value is NO. If you want to restrict selection of cells regardless of mode, use allowsSelection. - */ -@property (nonatomic) BOOL allowsSelectionDuringEditing; -/* - * A Boolean value that determines whether users can select more than one row outside of editing mode. - * This property controls whether multiple rows can be selected simultaneously outside of editing mode. When the value of this property is YES, each row that is tapped acquires a selected appearance. Tapping the row again removes the selected appearance. If you access indexPathsForSelectedRows, you can get the index paths that identify the selected rows. - */ -@property (nonatomic) BOOL allowsMultipleSelection; -/* - * A Boolean value that controls whether users can select more than one cell simultaneously in editing mode. - * The default value of this property is NO. If you set it to YES, check marks appear next to selected rows in editing mode. In addition, UITableView does not query for editing styles when it goes into editing mode. If you access indexPathsForSelectedRows, you can get the index paths that identify the selected rows. - */ -@property (nonatomic) BOOL allowsMultipleSelectionDuringEditing; - -/** - * 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 table to the given row. - * - * @param indexPath The index path of the row. - * @param scrollPosition Where the row 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)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated; - -/** - * 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 UITableView'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 UITableView's version. - */ -- (void)reloadData; - -/** - * Triggers a relayout of all nodes. - * - * @discussion This method invalidates and lays out every cell node in the table view. - */ -- (void)relayoutItems; - -/** - * 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 with 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, with an option to animate the insertion. - * - * @param sections An index set that specifies the sections to insert. - * - * @param animation A constant that indicates how the insertion is to be animated. See UITableViewRowAnimation. - * - * @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 withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Deletes one or more sections, with an option to animate the deletion. - * - * @param sections An index set that specifies the sections to delete. - * - * @param animation A constant that indicates how the deletion is to be animated. See UITableViewRowAnimation. - * - * @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 withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Reloads the specified sections using a given animation effect. - * - * @param sections An index set that specifies the sections to reload. - * - * @param animation A constant that indicates how the reloading is to be animated. See UITableViewRowAnimation. - * - * @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 withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * 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; - -/** - * Inserts rows at the locations identified by an array of index paths, with an option to animate the insertion. - * - * @param indexPaths An array of NSIndexPath objects, each representing a row index and section index that together identify a row. - * - * @param animation A constant that indicates how the insertion is to be animated. See UITableViewRowAnimation. - * - * @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)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Deletes the rows specified by an array of index paths, with an option to animate the deletion. - * - * @param indexPaths An array of NSIndexPath objects identifying the rows to delete. - * - * @param animation A constant that indicates how the deletion is to be animated. See UITableViewRowAnimation. - * - * @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)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Reloads the specified rows using a given animation effect. - * - * @param indexPaths An array of NSIndexPath objects identifying the rows to reload. - * - * @param animation A constant that indicates how the reloading is to be animated. See UITableViewRowAnimation. - * - * @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)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Moves the row at a specified location to a destination location. - * - * @param indexPath The index path identifying the row to move. - * - * @param newIndexPath The index path that is the destination of the move for the row. - * - * @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)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath; - -#pragma mark - Selection - -/** - * Selects a row in the table view identified by index path, optionally scrolling the row to a location in the table view. - * This method does not cause any selection-related delegate methods to be called. - * - * @param indexPath An index path identifying a row in the table view. - * - * @param animated Specify YES to animate the change in the selection or NO to make the change without animating it. - * - * @param scrollPosition A constant that identifies a relative position in the table view (top, middle, bottom) for the row when scrolling concludes. See `UITableViewScrollPosition` for descriptions of valid constants. - * - * @discussion This method must be called from the main thread. - */ -- (void)selectRowAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition; - -/* - * Deselects a given row identified by index path, with an option to animate the deselection. - * This method does not cause any selection-related delegate methods to be called. - * Calling this method does not cause any scrolling to the deselected row. - * - * @param indexPath An index path identifying a row in the table view. - * - * @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)deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated; - -#pragma mark - Querying Data - -/** - * Retrieves the number of rows in the given section. - * - * @param section The section. - * - * @return The number of rows. - */ -- (NSInteger)numberOfRowsInSection:(NSInteger)section AS_WARN_UNUSED_RESULT; - -/** - * The number of sections in the table node. - */ -@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 row at the given index path. - */ -- (nullable __kindof ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; - -/** - * Similar to -indexPathForCell:. - * - * @param cellNode a node for a row. - * - * @return The index path to this row, if it exists. - * - * @discussion This method will return @c nil for a node that is still being - * displayed in the table view, if the data source has deleted the row. - * That is, the node is visible but it no longer corresponds - * to any item in the data source and will be removed soon. - */ -- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT; - -/** - * Similar to -[UITableView rectForRowAtIndexPath:] - * - * @param indexPath An index path identifying a row in the table view. - * - * @return A rectangle defining the area in which the table view draws the row or CGRectZero if indexPath is invalid. - * - * @discussion This method must be called from the main thread. - */ -- (CGRect)rectForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; - -/** - * Similar to -[UITableView cellForRowAtIndexPath:] - * - * @param indexPath An index path identifying a row in the table view. - * - * @return An object representing a cell of the table, or nil if the cell is not visible or indexPath is out of range. - * - * @discussion This method must be called from the main thread. - */ -- (nullable __kindof UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; - -/** - * Similar to UITableView.indexPathForSelectedRow - * - * @return The value of this property is an index path identifying the row and section - * indexes of the selected row, or nil if the index path is invalid. If there are multiple selections, - * this property contains the first index-path object in the array of row selections; - * this object has the lowest index values for section and row. - * - * @discussion This method must be called from the main thread. - */ -@property (nullable, nonatomic, copy, readonly) NSIndexPath *indexPathForSelectedRow; - -@property (nonatomic, readonly, nullable) NSArray *indexPathsForSelectedRows; - -/** - * Similar to -[UITableView indexPathForRowAtPoint:] - * - * @param point A point in the local coordinate system of the table view (the table view’€™s bounds). - * - * @return An index path representing the row and section associated with point, - * or nil if the point is out of the bounds of any row. - * - * @discussion This method must be called from the main thread. - */ -- (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point AS_WARN_UNUSED_RESULT; - -/** - * Similar to -[UITableView indexPathsForRowsInRect:] - * - * @param rect A rectangle defining an area of the table view in local coordinates. - * - * @return An array of NSIndexPath objects each representing a row and section index identifying a row within rect. - * Returns an empty array if there aren’t any rows to return. - * - * @discussion This method must be called from the main thread. - */ -- (nullable NSArray *)indexPathsForRowsInRect:(CGRect)rect AS_WARN_UNUSED_RESULT; - -/** - * Similar to -[UITableView indexPathsForVisibleRows] - * - * @return The value of this property is an array of NSIndexPath objects each representing a row index and section index - * that together identify a visible row in the table view. If no rows are visible, the value is nil. - * - * @discussion This method must be called from the main thread. - */ -- (NSArray *)indexPathsForVisibleRows AS_WARN_UNUSED_RESULT; - -@end - -/** - * This is a node-based UITableViewDataSource. - */ -@protocol ASTableDataSource - -@optional - -/** - * Asks the data source for the number of sections in the table node. - * - * @see @c numberOfSectionsInTableView: - */ -- (NSInteger)numberOfSectionsInTableNode:(ASTableNode *)tableNode; - -/** - * Asks the data source for the number of rows in the given section of the table node. - * - * @see @c numberOfSectionsInTableView: - */ -- (NSInteger)tableNode:(ASTableNode *)tableNode numberOfRowsInSection:(NSInteger)section; - -/** - * Asks the data source for a block to create a node to represent the row at the given index path. - * The block will be run by the table node concurrently in the background before the row is inserted - * into the table view. - * - * @param tableNode The sender. - * @param indexPath The index path of the row. - * - * @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). - * - * @note This method takes precedence over tableNode:nodeForRowAtIndexPath: if implemented. - */ -- (ASCellNodeBlock)tableNode:(ASTableNode *)tableNode nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Asks the data source for a node to represent the row at the given index path. - * - * @param tableNode The sender. - * @param indexPath The index path of the row. - * - * @return a node to display for this row. This will be called on the main thread and should not implement reuse (it will be called once per row). Unlike UITableView's version, this method - * is not called when the row is about to display. - */ -- (ASCellNode *)tableNode:(ASTableNode *)tableNode nodeForRowAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Similar to -tableView:cellForRowAtIndexPath:. - * - * @param tableView 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 UITableView's version, this method - * is not called when the row is about to display. - */ -- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Similar to -tableView:nodeForRowAtIndexPath: - * This method takes precedence over tableView:nodeForRowAtIndexPath: if implemented. - * @param tableView 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)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode'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 tableView The sender. - * @deprecated The data source is always accessed on the main thread, and this method will not be called. - */ -- (void)tableViewLockDataSource:(ASTableView *)tableView 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 asyn 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 tableView The sender. - * @deprecated The data source is always accessed on the main thread, and this method will not be called. - */ -- (void)tableViewUnlockDataSource:(ASTableView *)tableView ASDISPLAYNODE_DEPRECATED_MSG("Data source accesses are on the main thread. Method will not be called."); - -/** - * Generate a unique identifier for an element in a table. This helps state restoration persist the scroll position - * of a table 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 tableNode The sender. - * - * @return a unique identifier for the element at the given path. Return nil if the index path does not exist in the table. - */ -- (nullable NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)indexPath inNode:(ASTableNode *)tableNode; - -/** - * Similar to -tableView:cellForRowAtIndexPath:. See the documentation for UIDataSourceModelAssociation for more information. - * - * @param identifier The model identifier of the element, previously generated by a call to modelIdentifierForElementAtIndexPath. - * - * @param tableNode The sender. - * - * @return the index path to the current position of the matching element in the table. Return nil if the element is not found. - */ -- (nullable NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inNode:(ASTableNode *)tableNode; - -@end - -/** - * This is a node-based UITableViewDelegate. - * - * Note that -tableView:heightForRowAtIndexPath: has been removed; instead, your custom ASCellNode subclasses are - * responsible for deciding their preferred onscreen height in -calculateSizeThatFits:. - */ -@protocol ASTableDelegate - -@optional - -- (void)tableNode:(ASTableNode *)tableNode willDisplayRowWithNode:(ASCellNode *)node; - -- (void)tableNode:(ASTableNode *)tableNode didEndDisplayingRowWithNode:(ASCellNode *)node; - -- (nullable NSIndexPath *)tableNode:(ASTableNode *)tableNode willSelectRowAtIndexPath:(NSIndexPath *)indexPath; - -- (void)tableNode:(ASTableNode *)tableNode didSelectRowAtIndexPath:(NSIndexPath *)indexPath; - -- (nullable NSIndexPath *)tableNode:(ASTableNode *)tableNode willDeselectRowAtIndexPath:(NSIndexPath *)indexPath; - -- (void)tableNode:(ASTableNode *)tableNode didDeselectRowAtIndexPath:(NSIndexPath *)indexPath; - -- (BOOL)tableNode:(ASTableNode *)tableNode shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath; -- (void)tableNode:(ASTableNode *)tableNode didHighlightRowAtIndexPath:(NSIndexPath *)indexPath; -- (void)tableNode:(ASTableNode *)tableNode didUnhighlightRowAtIndexPath:(NSIndexPath *)indexPath; - -- (BOOL)tableNode:(ASTableNode *)tableNode shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath; -- (BOOL)tableNode:(ASTableNode *)tableNode canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender; -- (void)tableNode:(ASTableNode *)tableNode performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender; - -/** - * Provides the constrained size range for measuring the row at the index path. - * Note: the widths in the returned size range are ignored! - * - * @param tableNode The sender. - * - * @param indexPath The index path of the node. - * - * @return A constrained size range for layout the node at this index path. - */ -- (ASSizeRange)tableNode:(ASTableNode *)tableNode constrainedSizeForRowAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Receive a message that the tableView is near the end of its data set and more data should be fetched if necessary. - * - * @param tableNode 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. - * - * ASTableView currently only supports batch events for tail loads. If you require a head load, consider implementing a - * UIRefreshControl. - */ -- (void)tableNode:(ASTableNode *)tableNode willBeginBatchFetchWithContext:(ASBatchContext *)context; - -/** - * Tell the tableView if batch fetching should begin. - * - * @param tableNode 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 tableView assumes that it should notify its asyncDelegate when batch fetching - * should occur. - */ -- (BOOL)shouldBatchFetchForTableNode:(ASTableNode *)tableNode; - -/** - * Informs the delegate that the table view will add the given node - * at the given index path to the view hierarchy. - * - * @param tableView The sender. - * @param node The node that will be displayed. - * @param indexPath The index path of the row that will be displayed. - * - * @warning AsyncDisplayKit processes table 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)tableView:(ASTableView *)tableView willDisplayNode:(ASCellNode *)node forRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Informs the delegate that the table 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 row - * or its containing section with @c deleteRowsAtIndexPaths:withRowAnimation: or @c deleteSections:withRowAnimation: . - * - * @param tableView 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 the removal. - * - * @warning AsyncDisplayKit processes table 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)tableView:(ASTableView *)tableView didEndDisplayingNode:(ASCellNode *)node forRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Receive a message that the tableView is near the end of its data set and more data should be fetched if necessary. - * - * @param tableView 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. - * - * ASTableView currently only supports batch events for tail loads. If you require a head load, consider implementing a - * UIRefreshControl. - */ -- (void)tableView:(ASTableView *)tableView willBeginBatchFetchWithContext:(ASBatchContext *)context ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Tell the tableView if batch fetching should begin. - * - * @param tableView 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 tableView assumes that it should notify its asyncDelegate when batch fetching - * should occur. - */ -- (BOOL)shouldBatchFetchForTableView:(ASTableView *)tableView AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Provides the constrained size range for measuring the row at the index path. - * Note: the widths in the returned size range are ignored! - * - * @param tableView The sender. - * - * @param indexPath The index path of the node. - * - * @return A constrained size range for layout the node at this index path. - */ -- (ASSizeRange)tableView:(ASTableView *)tableView constrainedSizeForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Informs the delegate that the table view will add the node - * at the given index path to the view hierarchy. - * - * @param tableView The sender. - * @param indexPath The index path of the row that will be displayed. - * - * @warning AsyncDisplayKit processes table 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 tableView:willDisplayNode:forRowAtIndexPath: instead. - */ -- (void)tableView:(ASTableView *)tableView willDisplayNodeForRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -@end - -@interface ASTableNode (Deprecated) - -- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("This method has been renamed to -waitUntilAllUpdatesAreProcessed."); - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTableNode.mm b/submodules/AsyncDisplayKit/Source/ASTableNode.mm deleted file mode 100644 index 45d10ddffb..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTableNode.mm +++ /dev/null @@ -1,871 +0,0 @@ -// -// ASTableNode.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 -#import - -#import -#import -#import -#import -#import -#import "Private/ASInternalHelpers.h" -#import -#import -#import -#import -#import -#import -#import - -#pragma mark - _ASTablePendingState - -@interface _ASTablePendingState : NSObject { -@public - std::vector> _tuningParameters; -} -@property (nonatomic, weak) id delegate; -@property (nonatomic, weak) id dataSource; -@property (nonatomic) ASLayoutRangeMode rangeMode; -@property (nonatomic) BOOL allowsSelection; -@property (nonatomic) BOOL allowsSelectionDuringEditing; -@property (nonatomic) BOOL allowsMultipleSelection; -@property (nonatomic) BOOL allowsMultipleSelectionDuringEditing; -@property (nonatomic) BOOL inverted; -@property (nonatomic) CGFloat leadingScreensForBatching; -@property (nonatomic) UIEdgeInsets contentInset; -@property (nonatomic) CGPoint contentOffset; -@property (nonatomic) BOOL animatesContentOffset; -@property (nonatomic) BOOL automaticallyAdjustsContentOffset; - -@end - -@implementation _ASTablePendingState - -#pragma mark - Lifecycle - -- (instancetype)init -{ - self = [super init]; - if (self) { - _rangeMode = ASLayoutRangeModeUnspecified; - _tuningParameters = [ASAbstractLayoutController defaultTuningParameters]; - _allowsSelection = YES; - _allowsSelectionDuringEditing = NO; - _allowsMultipleSelection = NO; - _allowsMultipleSelectionDuringEditing = NO; - _inverted = NO; - _leadingScreensForBatching = 2; - _contentInset = UIEdgeInsetsZero; - _contentOffset = CGPointZero; - _animatesContentOffset = NO; - _automaticallyAdjustsContentOffset = NO; - } - return self; -} - -#pragma mark Tuning Parameters - -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType -{ - return [self tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType -{ - return [self setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Requesting a range that is OOB for the configured tuning parameters"); - return _tuningParameters[rangeMode][rangeType]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - ASDisplayNodeAssert(rangeMode < _tuningParameters.size() && rangeType < _tuningParameters[rangeMode].size(), @"Setting a range that is OOB for the configured tuning parameters"); - _tuningParameters[rangeMode][rangeType] = tuningParameters; -} - -@end - -#pragma mark - ASTableView - -@interface ASTableNode () -{ - AS::RecursiveMutex _environmentStateLock; - id _batchFetchingDelegate; -} - -@property (nonatomic) _ASTablePendingState *pendingState; -@property (nonatomic, weak) ASRangeController *rangeController; -@end - -@implementation ASTableNode - -#pragma mark Lifecycle - -- (instancetype)initWithStyle:(UITableViewStyle)style -{ - if (self = [super init]) { - __weak __typeof__(self) weakSelf = self; - [self setViewBlock:^{ - // Variable will be unused if event logging is off. - __unused __typeof__(self) strongSelf = weakSelf; - return [[ASTableView alloc] _initWithFrame:CGRectZero style:style dataControllerClass:nil owningNode:strongSelf eventLog:ASDisplayNodeGetEventLog(strongSelf)]; - }]; - } - return self; -} - -- (instancetype)init -{ - return [self initWithStyle:UITableViewStylePlain]; -} - -#if ASDISPLAYNODE_ASSERTIONS_ENABLED -- (void)dealloc -{ - if (self.nodeLoaded) { - __weak UIView *view = self.view; - ASPerformBlockOnMainThread(^{ - ASDisplayNodeCAssertNil(view.superview, @"Node's view should be removed from hierarchy."); - }); - } -} -#endif - -#pragma mark ASDisplayNode - -- (void)didLoad -{ - [super didLoad]; - - ASTableView *view = self.view; - view.tableNode = self; - - _rangeController = view.rangeController; - - if (_pendingState) { - _ASTablePendingState *pendingState = _pendingState; - self.pendingState = nil; - view.asyncDelegate = pendingState.delegate; - view.asyncDataSource = pendingState.dataSource; - view.inverted = pendingState.inverted; - view.allowsSelection = pendingState.allowsSelection; - view.allowsSelectionDuringEditing = pendingState.allowsSelectionDuringEditing; - view.allowsMultipleSelection = pendingState.allowsMultipleSelection; - view.allowsMultipleSelectionDuringEditing = pendingState.allowsMultipleSelectionDuringEditing; - view.automaticallyAdjustsContentOffset = pendingState.automaticallyAdjustsContentOffset; - - UIEdgeInsets contentInset = pendingState.contentInset; - if (!UIEdgeInsetsEqualToEdgeInsets(contentInset, UIEdgeInsetsZero)) { - view.contentInset = contentInset; - } - - CGPoint contentOffset = pendingState.contentOffset; - if (!CGPointEqualToPoint(contentOffset, CGPointZero)) { - [view setContentOffset:contentOffset animated:pendingState.animatesContentOffset]; - } - - const auto tuningParametersVector = pendingState->_tuningParameters; - const auto tuningParametersVectorSize = tuningParametersVector.size(); - for (NSInteger rangeMode = 0; rangeMode < tuningParametersVectorSize; rangeMode++) { - const auto tuningparametersRangeModeVector = tuningParametersVector[rangeMode]; - const auto tuningParametersVectorRangeModeSize = tuningparametersRangeModeVector.size(); - for (NSInteger rangeType = 0; rangeType < tuningParametersVectorRangeModeSize; rangeType++) { - ASRangeTuningParameters tuningParameters = tuningparametersRangeModeVector[rangeType]; - [_rangeController setTuningParameters:tuningParameters - forRangeMode:(ASLayoutRangeMode)rangeMode - rangeType:(ASLayoutRangeType)rangeType]; - } - } - - if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) { - [_rangeController updateCurrentRangeWithMode:pendingState.rangeMode]; - } - } -} - -- (ASTableView *)view -{ - return (ASTableView *)[super view]; -} - -- (void)clearContents -{ - [super clearContents]; - [self.rangeController clearContents]; -} - -- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState -{ - [super interfaceStateDidChange:newState fromState:oldState]; - [ASRangeController layoutDebugOverlayIfNeeded]; -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - // Intentionally allocate the view here and trigger a layout pass on it, which in turn will trigger the intial data load. - // We can get rid of this call later when ASDataController, ASRangeController and ASCollectionLayout can operate without the view. - [self.view layoutIfNeeded]; -} - -#if ASRangeControllerLoggingEnabled -- (void)didEnterVisibleState -{ - [super didEnterVisibleState]; - NSLog(@"%@ - visible: YES", self); -} - -- (void)didExitVisibleState -{ - [super didExitVisibleState]; - NSLog(@"%@ - visible: NO", self); -} -#endif - -- (void)didExitPreloadState -{ - [super didExitPreloadState]; - [self.rangeController clearPreloadedData]; -} - -#pragma mark Setter / Getter - -// TODO: Implement this without the view. Then revisit ASLayoutElementCollectionTableSetTraitCollection -- (ASDataController *)dataController -{ - return self.view.dataController; -} - -- (_ASTablePendingState *)pendingState -{ - if (!_pendingState && ![self isNodeLoaded]) { - _pendingState = [[_ASTablePendingState alloc] init]; - } - ASDisplayNodeAssert(![self isNodeLoaded] || !_pendingState, @"ASTableNode should not have a pendingState once it is loaded"); - return _pendingState; -} - -- (void)setInverted:(BOOL)inverted -{ - self.transform = inverted ? CATransform3DMakeScale(1, -1, 1) : CATransform3DIdentity; - if ([self pendingState]) { - _pendingState.inverted = inverted; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - self.view.inverted = inverted; - } -} - -- (BOOL)inverted -{ - if ([self pendingState]) { - return _pendingState.inverted; - } else { - return self.view.inverted; - } -} - -- (void)setLeadingScreensForBatching:(CGFloat)leadingScreensForBatching -{ - _ASTablePendingState *pendingState = self.pendingState; - if (pendingState) { - pendingState.leadingScreensForBatching = leadingScreensForBatching; - } else { - ASDisplayNodeAssert(self.nodeLoaded, @"ASTableNode should be loaded if pendingState doesn't exist"); - self.view.leadingScreensForBatching = leadingScreensForBatching; - } -} - -- (CGFloat)leadingScreensForBatching -{ - _ASTablePendingState *pendingState = self.pendingState; - if (pendingState) { - return pendingState.leadingScreensForBatching; - } else { - return self.view.leadingScreensForBatching; - } -} - -- (void)setContentInset:(UIEdgeInsets)contentInset -{ - _ASTablePendingState *pendingState = self.pendingState; - if (pendingState) { - pendingState.contentInset = contentInset; - } else { - ASDisplayNodeAssert(self.nodeLoaded, @"ASTableNode should be loaded if pendingState doesn't exist"); - self.view.contentInset = contentInset; - } -} - -- (UIEdgeInsets)contentInset -{ - _ASTablePendingState *pendingState = self.pendingState; - if (pendingState) { - return pendingState.contentInset; - } else { - return self.view.contentInset; - } -} - -- (void)setContentOffset:(CGPoint)contentOffset -{ - [self setContentOffset:contentOffset animated:NO]; -} - -- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated -{ - _ASTablePendingState *pendingState = self.pendingState; - if (pendingState) { - pendingState.contentOffset = contentOffset; - pendingState.animatesContentOffset = animated; - } else { - ASDisplayNodeAssert(self.nodeLoaded, @"ASTableNode should be loaded if pendingState doesn't exist"); - [self.view setContentOffset:contentOffset animated:animated]; - } -} - -- (CGPoint)contentOffset -{ - _ASTablePendingState *pendingState = self.pendingState; - if (pendingState) { - return pendingState.contentOffset; - } else { - return self.view.contentOffset; - } -} - -- (void)setAutomaticallyAdjustsContentOffset:(BOOL)automaticallyAdjustsContentOffset -{ - _ASTablePendingState *pendingState = self.pendingState; - if (pendingState) { - pendingState.automaticallyAdjustsContentOffset = automaticallyAdjustsContentOffset; - } else { - ASDisplayNodeAssert(self.nodeLoaded, @"ASTableNode should be loaded if pendingState doesn't exist"); - self.view.automaticallyAdjustsContentOffset = automaticallyAdjustsContentOffset; - } -} - -- (BOOL)automaticallyAdjustsContentOffset -{ - _ASTablePendingState *pendingState = self.pendingState; - if (pendingState) { - return pendingState.automaticallyAdjustsContentOffset; - } else { - return self.view.automaticallyAdjustsContentOffset; - } -} - -- (void)setDelegate:(id )delegate -{ - if ([self pendingState]) { - _pendingState.delegate = delegate; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - - // Manually trampoline to the main thread. The view requires this be called on main - // and asserting here isn't an option – it is a common pattern for users to clear - // the delegate/dataSource in dealloc, which may be running on a background thread. - // It is important that we avoid retaining self in this block, so that this method is dealloc-safe. - ASTableView *view = self.view; - ASPerformBlockOnMainThread(^{ - view.asyncDelegate = delegate; - }); - } -} - -- (id )delegate -{ - if ([self pendingState]) { - return _pendingState.delegate; - } else { - return self.view.asyncDelegate; - } -} - -- (void)setDataSource:(id )dataSource -{ - if ([self pendingState]) { - _pendingState.dataSource = dataSource; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - - // Manually trampoline to the main thread. The view requires this be called on main - // and asserting here isn't an option – it is a common pattern for users to clear - // the delegate/dataSource in dealloc, which may be running on a background thread. - // It is important that we avoid retaining self in this block, so that this method is dealloc-safe. - ASTableView *view = self.view; - ASPerformBlockOnMainThread(^{ - view.asyncDataSource = dataSource; - }); - } -} - -- (id )dataSource -{ - if ([self pendingState]) { - return _pendingState.dataSource; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - return self.view.asyncDataSource; - } -} - -- (void)setAllowsSelection:(BOOL)allowsSelection -{ - if ([self pendingState]) { - _pendingState.allowsSelection = allowsSelection; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - self.view.allowsSelection = allowsSelection; - } -} - -- (BOOL)allowsSelection -{ - if ([self pendingState]) { - return _pendingState.allowsSelection; - } else { - return self.view.allowsSelection; - } -} - -- (void)setAllowsSelectionDuringEditing:(BOOL)allowsSelectionDuringEditing -{ - if ([self pendingState]) { - _pendingState.allowsSelectionDuringEditing = allowsSelectionDuringEditing; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - self.view.allowsSelectionDuringEditing = allowsSelectionDuringEditing; - } -} - -- (BOOL)allowsSelectionDuringEditing -{ - if ([self pendingState]) { - return _pendingState.allowsSelectionDuringEditing; - } else { - return self.view.allowsSelectionDuringEditing; - } -} - -- (void)setAllowsMultipleSelection:(BOOL)allowsMultipleSelection -{ - if ([self pendingState]) { - _pendingState.allowsMultipleSelection = allowsMultipleSelection; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - self.view.allowsMultipleSelection = allowsMultipleSelection; - } -} - -- (BOOL)allowsMultipleSelection -{ - if ([self pendingState]) { - return _pendingState.allowsMultipleSelection; - } else { - return self.view.allowsMultipleSelection; - } -} - -- (void)setAllowsMultipleSelectionDuringEditing:(BOOL)allowsMultipleSelectionDuringEditing -{ - if ([self pendingState]) { - _pendingState.allowsMultipleSelectionDuringEditing = allowsMultipleSelectionDuringEditing; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - self.view.allowsMultipleSelectionDuringEditing = allowsMultipleSelectionDuringEditing; - } -} - -- (BOOL)allowsMultipleSelectionDuringEditing -{ - if ([self pendingState]) { - return _pendingState.allowsMultipleSelectionDuringEditing; - } else { - return self.view.allowsMultipleSelectionDuringEditing; - } -} - -- (void)setBatchFetchingDelegate:(id)batchFetchingDelegate -{ - _batchFetchingDelegate = batchFetchingDelegate; -} - -- (id)batchFetchingDelegate -{ - return _batchFetchingDelegate; -} - -#pragma mark ASRangeControllerUpdateRangeProtocol - -- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode -{ - if ([self pendingState]) { - _pendingState.rangeMode = rangeMode; - } else { - ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist"); - [self.rangeController updateCurrentRangeWithMode:rangeMode]; - } -} - -#pragma mark ASEnvironment - -ASLayoutElementCollectionTableSetTraitCollection(_environmentStateLock) - -#pragma mark - Range Tuning - -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType -{ - return [self tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType -{ - [self setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - if ([self pendingState]) { - return [_pendingState tuningParametersForRangeMode:rangeMode rangeType:rangeType]; - } else { - return [self.rangeController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; - } -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - if ([self pendingState]) { - [_pendingState setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; - } else { - return [self.rangeController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; - } -} - -#pragma mark - Selection - -- (void)selectRowAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition -{ - ASDisplayNodeAssertMainThread(); - ASTableView *tableView = self.view; - - indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES]; - if (indexPath != nil) { - [tableView selectRowAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition]; - } else { - NSLog(@"Failed to select row at index path %@ because the row never reached the view.", indexPath); - } - -} - -- (void)deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated -{ - ASDisplayNodeAssertMainThread(); - ASTableView *tableView = self.view; - - indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES]; - if (indexPath != nil) { - [tableView deselectRowAtIndexPath:indexPath animated:animated]; - } else { - NSLog(@"Failed to deselect row at index path %@ because the row never reached the view.", indexPath); - } -} - -- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated -{ - ASDisplayNodeAssertMainThread(); - ASTableView *tableView = self.view; - - indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES]; - - if (indexPath != nil) { - [tableView scrollToRowAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated]; - } else { - NSLog(@"Failed to scroll to row at index path %@ because the row never reached the view.", indexPath); - } -} - -#pragma mark - Querying Data - -- (void)reloadDataInitiallyIfNeeded -{ - ASDisplayNodeAssertMainThread(); - if (!self.dataController.initialReloadDataHasBeenCalled) { - // Note: Just calling reloadData isn't enough here – we need to - // ensure that _nodesConstrainedWidth is updated first. - [self.view layoutIfNeeded]; - } -} - -- (NSInteger)numberOfRowsInSection:(NSInteger)section -{ - ASDisplayNodeAssertMainThread(); - [self reloadDataInitiallyIfNeeded]; - return [self.dataController.pendingMap numberOfItemsInSection:section]; -} - -- (NSInteger)numberOfSections -{ - ASDisplayNodeAssertMainThread(); - [self reloadDataInitiallyIfNeeded]; - return [self.dataController.pendingMap numberOfSections]; -} - -- (NSArray<__kindof ASCellNode *> *)visibleNodes -{ - ASDisplayNodeAssertMainThread(); - return self.isNodeLoaded ? [self.view visibleNodes] : @[]; -} - -- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode -{ - return [self.dataController.pendingMap indexPathForElement:cellNode.collectionElement]; -} - -- (ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath -{ - [self reloadDataInitiallyIfNeeded]; - return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].node; -} - -- (CGRect)rectForRowAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - ASTableView *tableView = self.view; - - indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES]; - return [tableView rectForRowAtIndexPath:indexPath]; -} - -- (nullable __kindof UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - ASTableView *tableView = self.view; - - indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES]; - if (indexPath == nil) { - return nil; - } - return [tableView cellForRowAtIndexPath:indexPath]; -} - -- (nullable NSIndexPath *)indexPathForSelectedRow -{ - ASDisplayNodeAssertMainThread(); - ASTableView *tableView = self.view; - - NSIndexPath *indexPath = tableView.indexPathForSelectedRow; - if (indexPath != nil) { - return [tableView convertIndexPathToTableNode:indexPath]; - } - return indexPath; -} - -- (NSArray *)indexPathsForSelectedRows -{ - ASDisplayNodeAssertMainThread(); - ASTableView *tableView = self.view; - - return [tableView convertIndexPathsToTableNode:tableView.indexPathsForSelectedRows]; -} - -- (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point -{ - ASDisplayNodeAssertMainThread(); - ASTableView *tableView = self.view; - - NSIndexPath *indexPath = [tableView indexPathForRowAtPoint:point]; - if (indexPath != nil) { - return [tableView convertIndexPathToTableNode:indexPath]; - } - return indexPath; -} - -- (nullable NSArray *)indexPathsForRowsInRect:(CGRect)rect -{ - ASDisplayNodeAssertMainThread(); - ASTableView *tableView = self.view; - return [tableView convertIndexPathsToTableNode:[tableView indexPathsForRowsInRect:rect]]; -} - -- (NSArray *)indexPathsForVisibleRows -{ - ASDisplayNodeAssertMainThread(); - NSMutableArray *indexPathsArray = [NSMutableArray new]; - for (ASCellNode *cell in [self visibleNodes]) { - NSIndexPath *indexPath = [self indexPathForNode:cell]; - if (indexPath) { - [indexPathsArray addObject:indexPath]; - } - } - return indexPathsArray; -} - -#pragma mark - Editing - -- (void)reloadDataWithCompletion:(void (^)())completion -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view reloadDataWithCompletion:completion]; - } else { - if (completion) { - completion(); - } - } -} - -- (void)reloadData -{ - [self reloadDataWithCompletion:nil]; -} - -- (void)relayoutItems -{ - [self.view relayoutItems]; -} - -- (void)performBatchAnimated:(BOOL)animated updates:(NS_NOESCAPE void (^)())updates completion:(void (^)(BOOL))completion -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - ASTableView *tableView = self.view; - [tableView beginUpdates]; - if (updates) { - updates(); - } - [tableView endUpdatesAnimated:animated completion:completion]; - } else { - if (updates) { - updates(); - } - } -} - -- (void)performBatchUpdates:(NS_NOESCAPE void (^)())updates completion:(void (^)(BOOL))completion -{ - [self performBatchAnimated:YES updates:updates completion:completion]; -} - -- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view insertSections:sections withRowAnimation:animation]; - } -} - -- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view deleteSections:sections withRowAnimation:animation]; - } -} - -- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view reloadSections:sections withRowAnimation:animation]; - } -} - -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view moveSection:section toSection:newSection]; - } -} - -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view insertRowsAtIndexPaths:indexPaths withRowAnimation:animation]; - } -} - -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view deleteRowsAtIndexPaths:indexPaths withRowAnimation:animation]; - } -} - -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view reloadRowsAtIndexPaths:indexPaths withRowAnimation:animation]; - } -} - -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view moveRowAtIndexPath:indexPath toIndexPath:newIndexPath]; - } -} - -- (BOOL)isProcessingUpdates -{ - return (self.nodeLoaded ? [self.view isProcessingUpdates] : NO); -} - -- (void)onDidFinishProcessingUpdates:(void (^)())completion -{ - if (!completion) { - return; - } - if (!self.nodeLoaded) { - completion(); - } else { - [self.view onDidFinishProcessingUpdates:completion]; - } -} - -- (void)waitUntilAllUpdatesAreProcessed -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view waitUntilAllUpdatesAreCommitted]; - } -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)waitUntilAllUpdatesAreCommitted -{ - [self waitUntilAllUpdatesAreProcessed]; -} -#pragma clang diagnostic pop - -#pragma mark - Debugging (Private) - -- (NSMutableArray *)propertiesForDebugDescription -{ - NSMutableArray *result = [super propertiesForDebugDescription]; - [result addObject:@{ @"dataSource" : ASObjectDescriptionMakeTiny(self.dataSource) }]; - [result addObject:@{ @"delegate" : ASObjectDescriptionMakeTiny(self.delegate) }]; - return result; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTableView+Undeprecated.h b/submodules/AsyncDisplayKit/Source/ASTableView+Undeprecated.h deleted file mode 100644 index 46c54368a6..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTableView+Undeprecated.h +++ /dev/null @@ -1,298 +0,0 @@ -// -// ASTableView+Undeprecated.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 -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * Currently our public table API is on @c ASTableNode and the @c ASTableView - * API is deprecated, but the implementations still live in the view. - * - * This category lets us avoid deprecation warnings everywhere internally. - * In the future, the ASTableView public API will be eliminated and so will this file. - */ -@interface ASTableView (Undeprecated) - -@property (nullable, nonatomic, weak) id asyncDelegate; -@property (nullable, nonatomic, weak) id asyncDataSource; -@property (nonatomic) UIEdgeInsets contentInset; -@property (nonatomic) CGPoint contentOffset; -@property (nonatomic) BOOL automaticallyAdjustsContentOffset; -@property (nonatomic) BOOL inverted; -@property (nullable, nonatomic, readonly) NSArray *indexPathsForVisibleRows; -@property (nullable, nonatomic, readonly) NSArray *indexPathsForSelectedRows; -@property (nullable, nonatomic, readonly) NSIndexPath *indexPathForSelectedRow; - -/** - * The number of screens left to scroll before the delegate -tableView:beginBatchFetchingWithContext: is called. - * - * Defaults to two screenfuls. - */ -@property (nonatomic) CGFloat leadingScreensForBatching; - -/** - * Initializer. - * - * @param frame A rectangle specifying the initial location and size of the table view in its superview’€™s coordinates. - * The frame of the table view changes as table cells are added and deleted. - * - * @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants. - */ -- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style; - -/** - * 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; - -- (nullable __kindof UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Scrolls the table to the given row. - * - * @param indexPath The index path of the row. - * @param scrollPosition Where the row should end up after the scroll. - * @param animated Whether the scroll should be animated or not. - */ -- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated; - -- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition; - -- (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point; - -- (nullable NSArray *)indexPathsForRowsInRect:(CGRect)rect; - -/** - * Similar to -visibleCells. - * - * @return an array containing the cell nodes being displayed on screen. - */ -- (NSArray *)visibleNodes AS_WARN_UNUSED_RESULT; - -/** - * Similar to -indexPathForCell:. - * - * @param cellNode a cellNode part of the table view - * - * @return an indexPath for this cellNode - */ -- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT; - -/** - * 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 UITableView's version. - */ --(void)reloadDataWithCompletion:(void (^ _Nullable)(void))completion; - -/** - * Reload everything from scratch, destroying the working range and all cached nodes. - * - * @warning This method is substantially more expensive than UITableView's version. - */ -- (void)reloadData; - -/** - * Triggers a relayout of all nodes. - * - * @discussion This method invalidates and lays out every cell node in the table view. - */ -- (void)relayoutItems; - -/** - * Begins a series of method calls that insert, delete, select, or reload rows and sections of the table view, with animation enabled and no completion block. - * - * @discussion You call this method to bracket a series of method calls that ends with endUpdates and that consists of operations - * to insert, delete, select, and reload rows and sections of the table view. When you call endUpdates, ASTableView begins animating - * the operations simultaneously. It's important to remember that the ASTableView will be processing the updates asynchronously after this call is completed. - * - * @warning This method must be called from the main thread. - */ -- (void)beginUpdates; - -/** - * Concludes a series of method calls that insert, delete, select, or reload rows and sections of the table view, with animation enabled and no completion block. - * - * @discussion You call this method to bracket a series of method calls that begins with beginUpdates and that consists of operations - * to insert, delete, select, and reload rows and sections of the table view. When you call endUpdates, ASTableView begins animating - * the operations simultaneously. It's important to remember that the ASTableView will be processing the updates asynchronously after this call is completed. - * - * @warning This method is must be called from the main thread. - */ -- (void)endUpdates; - -/** - * Concludes a series of method calls that insert, delete, select, or reload rows and sections of the table view. - * You call this method to bracket a series of method calls that begins with beginUpdates and that consists of operations - * to insert, delete, select, and reload rows and sections of the table view. When you call endUpdates, ASTableView begins animating - * the operations simultaneously. This method is must be called from the main thread. It's important to remember that the ASTableView will - * be processing the updates asynchronously after this call and are not guaranteed to be reflected in the ASTableView until - * the completion block is executed. - * - * @param animated NO to disable all animations. - * @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)endUpdatesAnimated:(BOOL)animated completion:(void (^ _Nullable)(BOOL completed))completion; - -/** - * Blocks execution of the main thread until all section and row updates are committed. This method must be called from the main thread. - */ -- (void)waitUntilAllUpdatesAreCommitted; - -/** - * Inserts one or more sections, with an option to animate the insertion. - * - * @param sections An index set that specifies the sections to insert. - * - * @param animation A constant that indicates how the insertion is to be animated. See UITableViewRowAnimation. - * - * @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 withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Deletes one or more sections, with an option to animate the deletion. - * - * @param sections An index set that specifies the sections to delete. - * - * @param animation A constant that indicates how the deletion is to be animated. See UITableViewRowAnimation. - * - * @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 withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Reloads the specified sections using a given animation effect. - * - * @param sections An index set that specifies the sections to reload. - * - * @param animation A constant that indicates how the reloading is to be animated. See UITableViewRowAnimation. - * - * @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 withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * 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; - -/** - * Inserts rows at the locations identified by an array of index paths, with an option to animate the insertion. - * - * @param indexPaths An array of NSIndexPath objects, each representing a row index and section index that together identify a row. - * - * @param animation A constant that indicates how the insertion is to be animated. See UITableViewRowAnimation. - * - * @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)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Deletes the rows specified by an array of index paths, with an option to animate the deletion. - * - * @param indexPaths An array of NSIndexPath objects identifying the rows to delete. - * - * @param animation A constant that indicates how the deletion is to be animated. See UITableViewRowAnimation. - * - * @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)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Reloads the specified rows using a given animation effect. - * - * @param indexPaths An array of NSIndexPath objects identifying the rows to reload. - * - * @param animation A constant that indicates how the reloading is to be animated. See UITableViewRowAnimation. - * - * @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)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; - -/** - * Moves the row at a specified location to a destination location. - * - * @param indexPath The index path identifying the row to move. - * - * @param newIndexPath The index path that is the destination of the move for the row. - * - * @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)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath; - -- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; - -@end -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASTableView.h b/submodules/AsyncDisplayKit/Source/ASTableView.h deleted file mode 100644 index 3f627beb11..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTableView.h +++ /dev/null @@ -1,249 +0,0 @@ -// -// ASTableView.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 - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASCellNode; -@protocol ASTableDataSource; -@protocol ASTableDelegate; -@class ASTableNode; - -/** - * Asynchronous UITableView with Intelligent Preloading capabilities. - * - * @note ASTableNode is strongly recommended over ASTableView. This class is provided for adoption convenience. - */ -@interface ASTableView : UITableView - -/// The corresponding table node, or nil if one does not exist. -@property (nonatomic, weak, readonly) ASTableNode *tableNode; - -/** - * Retrieves the node for the row at the given index path. - */ -- (nullable ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; - -@end - -@interface ASTableView (Deprecated) - -@property (nonatomic, weak) id asyncDelegate ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's .delegate property instead."); -@property (nonatomic, weak) id asyncDataSource ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode .dataSource property instead."); - -/** - * Initializer. - * - * @param frame A rectangle specifying the initial location and size of the table view in its superview’€™s coordinates. - * The frame of the table view changes as table cells are added and deleted. - * - * @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants. - */ -- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style ASDISPLAYNODE_DEPRECATED_MSG("Please use ASTableNode instead of ASTableView."); - -/** - * The number of screens left to scroll before the delegate -tableView:beginBatchFetchingWithContext: is called. - * - * Defaults to two screenfuls. - */ -@property (nonatomic) CGFloat leadingScreensForBatching ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -/** - * The distance that the content view is inset from the table view edges. Defaults to UIEdgeInsetsZero. - */ -@property (nonatomic) UIEdgeInsets contentInset ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead"); - -/** - * The offset of the content view's origin from the table node's origin. Defaults to CGPointZero. - */ -@property (nonatomic) CGPoint contentOffset ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -/** - * YES to automatically adjust the contentOffset when cells are inserted or deleted above - * visible cells, maintaining the users' visible scroll position. - * - * @note This is only applied to non-animated updates. For animated updates, there is no way to - * synchronize or "cancel out" the appearance of a scroll due to UITableView API limitations. - * - * default is NO. - */ -@property (nonatomic) BOOL automaticallyAdjustsContentOffset ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -/* - * A Boolean value that determines whether the nodes that the data source renders will be flipped. - */ -@property (nonatomic) BOOL inverted ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -@property (nonatomic, readonly, nullable) NSIndexPath *indexPathForSelectedRow ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -@property (nonatomic, readonly, nullable) NSArray *indexPathsForSelectedRows ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -@property (nonatomic, readonly, nullable) NSArray *indexPathsForVisibleRows ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -/** - * 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 ASTableNode 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 ASTableNode 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 ASTableNode 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 ASTableNode method instead."); - -- (nullable __kindof UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (nullable NSArray *)indexPathsForRowsInRect:(CGRect)rect ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Similar to -visibleCells. - * - * @return an array containing the cell nodes being displayed on screen. - */ -- (NSArray *)visibleNodes AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Similar to -indexPathForCell:. - * - * @param cellNode a cellNode part of the table view - * - * @return an indexPath for this cellNode - */ -- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode 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 UITableView's version. - */ --(void)reloadDataWithCompletion:(void (^ _Nullable)(void))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Reload everything from scratch, destroying the working range and all cached nodes. - * - * @warning This method is substantially more expensive than UITableView's version. - */ -- (void)reloadData ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Triggers a relayout of all nodes. - * - * @discussion This method invalidates and lays out every cell node in the table view. - */ -- (void)relayoutItems ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)beginUpdates ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's -performBatchUpdates:completion: instead."); - -- (void)endUpdates ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's -performBatchUpdates:completion: instead."); - -/** - * Concludes a series of method calls that insert, delete, select, or reload rows and sections of the table view. - * You call this method to bracket a series of method calls that begins with beginUpdates and that consists of operations - * to insert, delete, select, and reload rows and sections of the table view. When you call endUpdates, ASTableView begins animating - * the operations simultaneously. This method is must be called from the main thread. It's important to remember that the ASTableView will - * be processing the updates asynchronously after this call and are not guaranteed to be reflected in the ASTableView until - * the completion block is executed. - * - * @param animated NO to disable all animations. - * @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)endUpdatesAnimated:(BOOL)animated completion:(void (^ _Nullable)(BOOL completed))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's -performBatchUpdates:completion: instead."); - -/** - * See ASTableNode.h for full documentation of these methods. - */ -@property (nonatomic, readonly) BOOL isProcessingUpdates; -- (void)onDidFinishProcessingUpdates:(void (^)(void))completion; -- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use -[ASTableNode waitUntilAllUpdatesAreProcessed] instead."); - -- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -@end - -ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASTableDataSource.") -@protocol ASTableViewDataSource -@end - -ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASTableDelegate.") -@protocol ASTableViewDelegate -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTableView.mm b/submodules/AsyncDisplayKit/Source/ASTableView.mm deleted file mode 100644 index c8ef4ef33e..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTableView.mm +++ /dev/null @@ -1,2044 +0,0 @@ -// -// ASTableView.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 - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import "Private/ASInternalHelpers.h" -#import -#import -#import -#import -#import -#import -#import - -static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - -//#define LOG(...) NSLog(__VA_ARGS__) -#define LOG(...) - -/** - * See note at the top of ASCollectionView.mm near declaration of macro GET_COLLECTIONNODE_OR_RETURN - */ -#define GET_TABLENODE_OR_RETURN(__var, __val) \ - ASTableNode *__var = self.tableNode; \ - if (__var == nil) { \ - return __val; \ - } - -#define UITABLEVIEW_RESPONDS_TO_SELECTOR() \ - ({ \ - static BOOL superResponds; \ - static dispatch_once_t onceToken; \ - dispatch_once(&onceToken, ^{ \ - superResponds = [UITableView instancesRespondToSelector:_cmd]; \ - }); \ - superResponds; \ - }) - -@interface UITableView (ScrollViewDelegate) - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView; -- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView; -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView; -- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset; -- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate; - -@end - -#pragma mark - -#pragma mark ASCellNode<->UITableViewCell bridging. - -@class _ASTableViewCell; - -@protocol _ASTableViewCellDelegate -- (void)didLayoutSubviewsOfTableViewCell:(_ASTableViewCell *)tableViewCell; -@end - -@interface _ASTableViewCell : UITableViewCell -@property (nonatomic, weak) id<_ASTableViewCellDelegate> delegate; -@property (nonatomic, readonly) ASCellNode *node; -@property (nonatomic) ASCollectionElement *element; -@end - -@implementation _ASTableViewCell -// TODO add assertions to prevent use of view-backed UITableViewCell properties (eg .textLabel) - -- (void)layoutSubviews -{ - [super layoutSubviews]; - [_delegate didLayoutSubviewsOfTableViewCell:self]; -} - -- (void)didTransitionToState:(UITableViewCellStateMask)state -{ - [self setNeedsLayout]; - [self layoutIfNeeded]; - [super didTransitionToState:state]; -} - -- (ASCellNode *)node -{ - return self.element.node; -} - -- (void)setElement:(ASCollectionElement *)element -{ - _element = element; - ASCellNode *node = element.node; - - if (node) { - self.backgroundColor = node.backgroundColor; - self.selectedBackgroundView = node.selectedBackgroundView; - self.backgroundView = node.backgroundView; -#if TARGET_OS_IOS - self.separatorInset = node.separatorInset; -#endif - self.selectionStyle = node.selectionStyle; - self.focusStyle = node.focusStyle; - self.accessoryType = node.accessoryType; - self.tintColor = node.tintColor; - - // the following ensures that we clip the entire cell to it's bounds if node.clipsToBounds is set (the default) - // This is actually a workaround for a bug we are seeing in some rare cases (selected background view - // overlaps other cells if size of ASCellNode has changed.) - self.clipsToBounds = node.clipsToBounds; - } - - [node __setSelectedFromUIKit:self.selected]; - [node __setHighlightedFromUIKit:self.highlighted]; -} - -- (BOOL)consumesCellNodeVisibilityEvents -{ - ASCellNode *node = self.node; - if (node == nil) { - return NO; - } - return ASSubclassOverridesSelector([ASCellNode class], [node class], @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:)); -} - -- (void)setSelected:(BOOL)selected animated:(BOOL)animated -{ - [super setSelected:selected animated:animated]; - [self.node __setSelectedFromUIKit:selected]; -} - -- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated -{ - [super setHighlighted:highlighted animated:animated]; - [self.node __setHighlightedFromUIKit:highlighted]; -} - -- (void)prepareForReuse -{ - // Need to clear element before UIKit calls setSelected:NO / setHighlighted:NO on its cells - self.element = nil; - [super prepareForReuse]; -} - -@end - -#pragma mark - -#pragma mark ASTableView - -@interface ASTableView () -{ - ASTableViewProxy *_proxyDataSource; - ASTableViewProxy *_proxyDelegate; - - ASTableLayoutController *_layoutController; - - ASRangeController *_rangeController; - - ASBatchContext *_batchContext; - - // When we update our data controller in response to an interactive move, - // we don't want to tell the table view about the change (it knows!) - BOOL _updatingInResponseToInteractiveMove; - BOOL _inverted; - - // The top cell node that was visible before the update. - __weak ASCellNode *_contentOffsetAdjustmentTopVisibleNode; - // The y-offset of the top visible row's origin before the update. - CGFloat _contentOffsetAdjustmentTopVisibleNodeOffset; - CGFloat _leadingScreensForBatching; - BOOL _automaticallyAdjustsContentOffset; - - CGPoint _deceleratingVelocity; - - CGFloat _nodesConstrainedWidth; - BOOL _queuedNodeHeightUpdate; - BOOL _isDeallocating; - NSHashTable<_ASTableViewCell *> *_cellsForVisibilityUpdates; - - // CountedSet because UIKit may display the same element in multiple cells e.g. during animations. - NSCountedSet *_visibleElements; - - NSHashTable *_cellsForLayoutUpdates; - - // See documentation on same property in ASCollectionView - BOOL _hasEverCheckedForBatchFetchingDueToUpdate; - - // The section index overlay view, if there is one present. - // This is useful because we need to measure our row nodes against (width - indexView.width). - __weak UIView *_sectionIndexView; - - /** - * The change set that we're currently building, if any. - */ - _ASHierarchyChangeSet *_changeSet; - - /** - * Counter used to keep track of nested batch updates. - */ - NSInteger _batchUpdateCount; - - /** - * Keep a strong reference to node till view is ready to release. - */ - ASTableNode *_keepalive_node; - - struct { - unsigned int scrollViewDidScroll:1; - unsigned int scrollViewWillBeginDragging:1; - unsigned int scrollViewDidEndDragging:1; - unsigned int scrollViewWillEndDragging:1; - unsigned int scrollViewDidEndDecelerating:1; - unsigned int tableNodeWillDisplayNodeForRow:1; - unsigned int tableViewWillDisplayNodeForRow:1; - unsigned int tableViewWillDisplayNodeForRowDeprecated:1; - unsigned int tableNodeDidEndDisplayingNodeForRow:1; - unsigned int tableViewDidEndDisplayingNodeForRow:1; - unsigned int tableNodeWillBeginBatchFetch:1; - unsigned int tableViewWillBeginBatchFetch:1; - unsigned int shouldBatchFetchForTableView:1; - unsigned int shouldBatchFetchForTableNode:1; - unsigned int tableViewConstrainedSizeForRow:1; - unsigned int tableNodeConstrainedSizeForRow:1; - unsigned int tableViewWillSelectRow:1; - unsigned int tableNodeWillSelectRow:1; - unsigned int tableViewDidSelectRow:1; - unsigned int tableNodeDidSelectRow:1; - unsigned int tableViewWillDeselectRow:1; - unsigned int tableNodeWillDeselectRow:1; - unsigned int tableViewDidDeselectRow:1; - unsigned int tableNodeDidDeselectRow:1; - unsigned int tableViewShouldHighlightRow:1; - unsigned int tableNodeShouldHighlightRow:1; - unsigned int tableViewDidHighlightRow:1; - unsigned int tableNodeDidHighlightRow:1; - unsigned int tableViewDidUnhighlightRow:1; - unsigned int tableNodeDidUnhighlightRow:1; - unsigned int tableViewShouldShowMenuForRow:1; - unsigned int tableNodeShouldShowMenuForRow:1; - unsigned int tableViewCanPerformActionForRow:1; - unsigned int tableNodeCanPerformActionForRow:1; - unsigned int tableViewPerformActionForRow:1; - unsigned int tableNodePerformActionForRow:1; - } _asyncDelegateFlags; - - struct { - unsigned int numberOfSectionsInTableView:1; - unsigned int numberOfSectionsInTableNode:1; - unsigned int tableNodeNumberOfRowsInSection:1; - unsigned int tableViewNumberOfRowsInSection:1; - unsigned int tableViewNodeBlockForRow:1; - unsigned int tableNodeNodeBlockForRow:1; - unsigned int tableViewNodeForRow:1; - unsigned int tableNodeNodeForRow:1; - unsigned int tableViewCanMoveRow:1; - unsigned int tableNodeCanMoveRow:1; - unsigned int tableViewMoveRow:1; - unsigned int tableNodeMoveRow:1; - unsigned int sectionIndexMethods:1; // if both section index methods are implemented - unsigned int modelIdentifierMethods:1; // if both modelIdentifierForElementAtIndexPath and indexPathForElementWithModelIdentifier are implemented - } _asyncDataSourceFlags; -} - -@property (nonatomic) ASDataController *dataController; - -@property (nonatomic, weak) ASTableNode *tableNode; - -@property (nonatomic) BOOL test_enableSuperUpdateCallLogging; -@end - -@implementation ASTableView -{ - __weak id _asyncDelegate; - __weak id _asyncDataSource; -} - -// Using _ASDisplayLayer ensures things like -layout are properly forwarded to ASTableNode. -+ (Class)layerClass -{ - return [_ASDisplayLayer class]; -} - -+ (Class)dataControllerClass -{ - return [ASDataController class]; -} - -#pragma mark - -#pragma mark Lifecycle - -- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style -{ - return [self _initWithFrame:frame style:style dataControllerClass:nil owningNode:nil eventLog:nil]; -} - -- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass owningNode:(ASTableNode *)tableNode eventLog:(ASEventLog *)eventLog -{ - if (!(self = [super initWithFrame:frame style:style])) { - return nil; - } - _cellsForVisibilityUpdates = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; - _cellsForLayoutUpdates = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; - if (!dataControllerClass) { - dataControllerClass = [[self class] dataControllerClass]; - } - - _layoutController = [[ASTableLayoutController alloc] initWithTableView:self]; - - _rangeController = [[ASRangeController alloc] init]; - _rangeController.layoutController = _layoutController; - _rangeController.dataSource = self; - _rangeController.delegate = self; - - _dataController = [[dataControllerClass alloc] initWithDataSource:self node:tableNode eventLog:eventLog]; - _dataController.delegate = _rangeController; - - _leadingScreensForBatching = 2.0; - _batchContext = [[ASBatchContext alloc] init]; - _visibleElements = [[NSCountedSet alloc] init]; - - _automaticallyAdjustsContentOffset = NO; - - _nodesConstrainedWidth = self.bounds.size.width; - - _proxyDelegate = [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; - super.delegate = (id)_proxyDelegate; - - _proxyDataSource = [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; - super.dataSource = (id)_proxyDataSource; - - [self registerClass:_ASTableViewCell.class forCellReuseIdentifier:kCellReuseIdentifier]; - - // iOS 11 automatically uses estimated heights, so disable those (see PR #485) - if (AS_AT_LEAST_IOS11) { - super.estimatedRowHeight = 0.0; - super.estimatedSectionHeaderHeight = 0.0; - super.estimatedSectionFooterHeight = 0.0; - } - - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)aDecoder -{ - NSLog(@"Warning: AsyncDisplayKit is not designed to be used with Interface Builder. Table properties set in IB will be lost."); - return [self initWithFrame:CGRectZero style:UITableViewStylePlain]; -} - -- (void)dealloc -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeCAssert(_batchUpdateCount == 0, @"ASTableView deallocated in the middle of a batch update."); - - // Sometimes the UIKit classes can call back to their delegate even during deallocation. - _isDeallocating = YES; - if (!ASActivateExperimentalFeature(ASExperimentalCollectionTeardown)) { - [self setAsyncDelegate:nil]; - [self setAsyncDataSource:nil]; - } - - // Data controller & range controller may own a ton of nodes, let's deallocate those off-main - ASPerformBackgroundDeallocation(&_dataController); - ASPerformBackgroundDeallocation(&_rangeController); -} - -#pragma mark - -#pragma mark Overrides - -- (void)setDataSource:(id)dataSource -{ - // UIKit can internally generate a call to this method upon changing the asyncDataSource; only assert for non-nil. - ASDisplayNodeAssert(dataSource == nil, @"ASTableView uses asyncDataSource, not UITableView's dataSource property."); -} - -- (void)setDelegate:(id)delegate -{ - // Our UIScrollView superclass sets its delegate to nil on dealloc. Only assert if we get a non-nil value here. - ASDisplayNodeAssert(delegate == nil, @"ASTableView uses asyncDelegate, not UITableView's delegate property."); -} - -- (id)asyncDataSource -{ - return _asyncDataSource; -} - -- (void)setAsyncDataSource:(id)asyncDataSource -{ - // Changing super.dataSource will trigger a setNeedsLayout, so this must happen on the main thread. - ASDisplayNodeAssertMainThread(); - - // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle - // the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource - // will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to hold a strong - // reference to the old dataSource in this case because calls to ASTableViewProxy will start failing and cause crashes. - NS_VALID_UNTIL_END_OF_SCOPE id oldDataSource = self.dataSource; - - if (asyncDataSource == nil) { - _asyncDataSource = nil; - _proxyDataSource = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; - - memset(&_asyncDataSourceFlags, 0, sizeof(_asyncDataSourceFlags)); - } else { - _asyncDataSource = asyncDataSource; - _proxyDataSource = [[ASTableViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; - - _asyncDataSourceFlags.numberOfSectionsInTableView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]; - _asyncDataSourceFlags.numberOfSectionsInTableNode = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableNode:)]; - _asyncDataSourceFlags.tableViewNumberOfRowsInSection = [_asyncDataSource respondsToSelector:@selector(tableView:numberOfRowsInSection:)]; - _asyncDataSourceFlags.tableNodeNumberOfRowsInSection = [_asyncDataSource respondsToSelector:@selector(tableNode:numberOfRowsInSection:)]; - _asyncDataSourceFlags.tableViewNodeForRow = [_asyncDataSource respondsToSelector:@selector(tableView:nodeForRowAtIndexPath:)]; - _asyncDataSourceFlags.tableNodeNodeForRow = [_asyncDataSource respondsToSelector:@selector(tableNode:nodeForRowAtIndexPath:)]; - _asyncDataSourceFlags.tableViewNodeBlockForRow = [_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)]; - _asyncDataSourceFlags.tableNodeNodeBlockForRow = [_asyncDataSource respondsToSelector:@selector(tableNode:nodeBlockForRowAtIndexPath:)]; - _asyncDataSourceFlags.tableViewCanMoveRow = [_asyncDataSource respondsToSelector:@selector(tableView:canMoveRowAtIndexPath:)]; - _asyncDataSourceFlags.tableViewMoveRow = [_asyncDataSource respondsToSelector:@selector(tableView:moveRowAtIndexPath:toIndexPath:)]; - _asyncDataSourceFlags.sectionIndexMethods = [_asyncDataSource respondsToSelector:@selector(sectionIndexTitlesForTableView:)] && [_asyncDataSource respondsToSelector:@selector(tableView:sectionForSectionIndexTitle:atIndex:)]; - _asyncDataSourceFlags.modelIdentifierMethods = [_asyncDataSource respondsToSelector:@selector(modelIdentifierForElementAtIndexPath:inNode:)] && [_asyncDataSource respondsToSelector:@selector(indexPathForElementWithModelIdentifier:inNode:)]; - - ASDisplayNodeAssert(_asyncDataSourceFlags.tableViewNodeBlockForRow - || _asyncDataSourceFlags.tableViewNodeForRow - || _asyncDataSourceFlags.tableNodeNodeBlockForRow - || _asyncDataSourceFlags.tableNodeNodeForRow, @"Data source must implement tableNode:nodeBlockForRowAtIndexPath: or tableNode:nodeForRowAtIndexPath:"); - ASDisplayNodeAssert(_asyncDataSourceFlags.tableNodeNumberOfRowsInSection || _asyncDataSourceFlags.tableViewNumberOfRowsInSection, @"Data source must implement tableNode:numberOfRowsInSection:"); - } - - _dataController.validationErrorSource = asyncDataSource; - super.dataSource = (id)_proxyDataSource; - [self _asyncDelegateOrDataSourceDidChange]; -} - -- (id)asyncDelegate -{ - return _asyncDelegate; -} - -- (void)setAsyncDelegate:(id)asyncDelegate -{ - // Changing super.delegate will trigger a setNeedsLayout, so this must happen on the main thread. - ASDisplayNodeAssertMainThread(); - - // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle - // the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate - // will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to hold a strong - // reference to the old delegate in this case because calls to ASTableViewProxy will start failing and cause crashes. - NS_VALID_UNTIL_END_OF_SCOPE id oldDelegate = super.delegate; - - if (asyncDelegate == nil) { - _asyncDelegate = nil; - _proxyDelegate = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self]; - - memset(&_asyncDelegateFlags, 0, sizeof(_asyncDelegateFlags)); - } else { - _asyncDelegate = asyncDelegate; - _proxyDelegate = [[ASTableViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; - - _asyncDelegateFlags.scrollViewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)]; - - _asyncDelegateFlags.tableViewWillDisplayNodeForRow = [_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNode:forRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeWillDisplayNodeForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:willDisplayRowWithNode:)]; - if (_asyncDelegateFlags.tableViewWillDisplayNodeForRow == NO) { - _asyncDelegateFlags.tableViewWillDisplayNodeForRowDeprecated = [_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNodeForRowAtIndexPath:)]; - } - _asyncDelegateFlags.tableViewDidEndDisplayingNodeForRow = [_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNode:forRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeDidEndDisplayingNodeForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:didEndDisplayingRowWithNode:)]; - _asyncDelegateFlags.scrollViewWillEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]; - _asyncDelegateFlags.scrollViewDidEndDecelerating = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]; - _asyncDelegateFlags.tableViewWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]; - _asyncDelegateFlags.tableNodeWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(tableNode:willBeginBatchFetchWithContext:)]; - _asyncDelegateFlags.shouldBatchFetchForTableView = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForTableView:)]; - _asyncDelegateFlags.shouldBatchFetchForTableNode = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForTableNode:)]; - _asyncDelegateFlags.scrollViewWillBeginDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]; - _asyncDelegateFlags.scrollViewDidEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]; - _asyncDelegateFlags.tableViewConstrainedSizeForRow = [_asyncDelegate respondsToSelector:@selector(tableView:constrainedSizeForRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeConstrainedSizeForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:constrainedSizeForRowAtIndexPath:)]; - - _asyncDelegateFlags.tableViewWillSelectRow = [_asyncDelegate respondsToSelector:@selector(tableView:willSelectRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeWillSelectRow = [_asyncDelegate respondsToSelector:@selector(tableNode:willSelectRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewDidSelectRow = [_asyncDelegate respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeDidSelectRow = [_asyncDelegate respondsToSelector:@selector(tableNode:didSelectRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewWillDeselectRow = [_asyncDelegate respondsToSelector:@selector(tableView:willDeselectRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeWillDeselectRow = [_asyncDelegate respondsToSelector:@selector(tableNode:willDeselectRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewDidDeselectRow = [_asyncDelegate respondsToSelector:@selector(tableView:didDeselectRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeDidDeselectRow = [_asyncDelegate respondsToSelector:@selector(tableNode:didDeselectRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewShouldHighlightRow = [_asyncDelegate respondsToSelector:@selector(tableView:shouldHighlightRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeShouldHighlightRow = [_asyncDelegate respondsToSelector:@selector(tableNode:shouldHighlightRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewDidHighlightRow = [_asyncDelegate respondsToSelector:@selector(tableView:didHighlightRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeDidHighlightRow = [_asyncDelegate respondsToSelector:@selector(tableNode:didHighlightRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewDidUnhighlightRow = [_asyncDelegate respondsToSelector:@selector(tableView:didUnhighlightRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeDidUnhighlightRow = [_asyncDelegate respondsToSelector:@selector(tableNode:didUnhighlightRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewShouldShowMenuForRow = [_asyncDelegate respondsToSelector:@selector(tableView:shouldShowMenuForRowAtIndexPath:)]; - _asyncDelegateFlags.tableNodeShouldShowMenuForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:shouldShowMenuForRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewCanPerformActionForRow = [_asyncDelegate respondsToSelector:@selector(tableView:canPerformAction:forRowAtIndexPath:withSender:)]; - _asyncDelegateFlags.tableNodeCanPerformActionForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:canPerformAction:forRowAtIndexPath:withSender:)]; - _asyncDelegateFlags.tableViewPerformActionForRow = [_asyncDelegate respondsToSelector:@selector(tableView:performAction:forRowAtIndexPath:withSender:)]; - _asyncDelegateFlags.tableNodePerformActionForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:performAction:forRowAtIndexPath:withSender:)]; - } - - super.delegate = (id)_proxyDelegate; - [self _asyncDelegateOrDataSourceDidChange]; -} - -- (void)_asyncDelegateOrDataSourceDidChange -{ - ASDisplayNodeAssertMainThread(); - - if (_asyncDataSource == nil && _asyncDelegate == nil && !ASActivateExperimentalFeature(ASExperimentalSkipClearData)) { - [_dataController clearData]; - } -} - -- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy -{ - if (proxy == _proxyDelegate) { - [self setAsyncDelegate:nil]; - } else if (proxy == _proxyDataSource) { - [self setAsyncDataSource:nil]; - } -} - -- (void)reloadDataWithCompletion:(void (^)())completion -{ - ASDisplayNodeAssertMainThread(); - - if (! _dataController.initialReloadDataHasBeenCalled) { - // If this is the first reload, forward to super immediately to prevent it from triggering more "initial" loads while our data controller is working. - [super reloadData]; - } - - void (^batchUpdatesCompletion)(BOOL); - if (completion) { - batchUpdatesCompletion = ^(BOOL) { - completion(); - }; - } - - [self beginUpdates]; - [_changeSet reloadData]; - [self endUpdatesWithCompletion:batchUpdatesCompletion]; -} - -- (void)reloadData -{ - [self reloadDataWithCompletion:nil]; -} - -- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated -{ - if ([self validateIndexPath:indexPath]) { - [super scrollToRowAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated]; - } -} - -- (void)relayoutItems -{ - [_dataController relayoutAllNodesWithInvalidationBlock:nil]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType -{ - [_rangeController setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType -{ - return [_rangeController tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType]; -} - -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - [_rangeController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType]; -} - -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType -{ - return [_rangeController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; -} - -- (ASElementMap *)elementMapForRangeController:(ASRangeController *)rangeController -{ - return _dataController.visibleMap; -} - -- (ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath -{ - return [_dataController.visibleMap elementForItemAtIndexPath:indexPath].node; -} - -- (NSIndexPath *)convertIndexPathFromTableNode:(NSIndexPath *)indexPath waitingIfNeeded:(BOOL)wait -{ - NSIndexPath *viewIndexPath = [_dataController.visibleMap convertIndexPath:indexPath fromMap:_dataController.pendingMap]; - if (viewIndexPath == nil && wait) { - [self waitUntilAllUpdatesAreCommitted]; - return [self convertIndexPathFromTableNode:indexPath waitingIfNeeded:NO]; - } - return viewIndexPath; -} - -- (NSIndexPath *)convertIndexPathToTableNode:(NSIndexPath *)indexPath -{ - if ([self validateIndexPath:indexPath] == nil) { - return nil; - } - - return [_dataController.pendingMap convertIndexPath:indexPath fromMap:_dataController.visibleMap]; -} - -- (NSArray *)convertIndexPathsToTableNode:(NSArray *)indexPaths -{ - if (indexPaths == nil) { - return nil; - } - - NSMutableArray *indexPathsArray = [NSMutableArray new]; - - for (NSIndexPath *indexPathInView in indexPaths) { - NSIndexPath *indexPath = [self convertIndexPathToTableNode:indexPathInView]; - if (indexPath != nil) { - [indexPathsArray addObject:indexPath]; - } - } - return indexPathsArray; -} - -- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode -{ - return [self indexPathForNode:cellNode waitingIfNeeded:NO]; -} - -/** - * Asserts that the index path is a valid view-index-path, and returns it if so, nil otherwise. - */ -- (nullable NSIndexPath *)validateIndexPath:(nullable NSIndexPath *)indexPath -{ - if (indexPath == nil) { - return nil; - } - - NSInteger section = indexPath.section; - if (section >= self.numberOfSections) { - ASDisplayNodeFailAssert(@"Table view index path has invalid section %lu, section count = %lu", (unsigned long)section, (unsigned long)self.numberOfSections); - return nil; - } - - NSInteger item = indexPath.item; - // item == NSNotFound means e.g. "scroll to this section" and is acceptable - if (item != NSNotFound && item >= [self numberOfRowsInSection:section]) { - ASDisplayNodeFailAssert(@"Table view index path has invalid item %lu in section %lu, item count = %lu", (unsigned long)indexPath.item, (unsigned long)section, (unsigned long)[self numberOfRowsInSection:section]); - return nil; - } - - return indexPath; -} - -- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode waitingIfNeeded:(BOOL)wait -{ - if (cellNode == nil) { - return nil; - } - - NSIndexPath *indexPath = [_dataController.visibleMap indexPathForElement:cellNode.collectionElement]; - indexPath = [self validateIndexPath:indexPath]; - if (indexPath == nil && wait) { - [self waitUntilAllUpdatesAreCommitted]; - return [self indexPathForNode:cellNode waitingIfNeeded:NO]; - } - return indexPath; -} - -- (NSArray *)visibleNodes -{ - const auto elements = [self visibleElementsForRangeController:_rangeController]; - return ASArrayByFlatMapping(elements, ASCollectionElement *e, e.node); -} - -- (void)beginUpdates -{ - ASDisplayNodeAssertMainThread(); - // _changeSet must be available during batch update - ASDisplayNodeAssertTrue((_batchUpdateCount > 0) == (_changeSet != nil)); - - if (_batchUpdateCount == 0) { - _changeSet = [[_ASHierarchyChangeSet alloc] initWithOldData:[_dataController itemCountsFromDataSource]]; - } - _batchUpdateCount++; -} - -- (void)endUpdates -{ - [self endUpdatesWithCompletion:nil]; -} - -- (void)endUpdatesWithCompletion:(void (^)(BOOL completed))completion -{ - // We capture the current state of whether animations are enabled if they don't provide us with one. - [self endUpdatesAnimated:[UIView areAnimationsEnabled] completion:completion]; -} - -- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL completed))completion -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssertNotNil(_changeSet, @"_changeSet must be available when batch update ends"); - - _batchUpdateCount--; - // Prevent calling endUpdatesAnimated:completion: in an unbalanced way - NSAssert(_batchUpdateCount >= 0, @"endUpdatesAnimated:completion: called without having a balanced beginUpdates call"); - - [_changeSet addCompletionHandler:completion]; - - if (_batchUpdateCount == 0) { - _ASHierarchyChangeSet *changeSet = _changeSet; - // Nil out _changeSet before forwarding to _dataController to allow the change set to cause subsequent batch updates on the same run loop - _changeSet = nil; - changeSet.animated = animated; - [_dataController updateWithChangeSet:changeSet]; - } -} - -- (BOOL)isProcessingUpdates -{ - return [_dataController isProcessingUpdates]; -} - -- (void)onDidFinishProcessingUpdates:(void (^)())completion -{ - [_dataController onDidFinishProcessingUpdates:completion]; -} - -- (void)waitUntilAllUpdatesAreCommitted -{ - ASDisplayNodeAssertMainThread(); - if (_batchUpdateCount > 0) { - // This assertion will be enabled soon. - // ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd)); - return; - } - - [_dataController waitUntilAllUpdatesAreProcessed]; -} - -- (void)layoutSubviews -{ - // Remeasure all rows if our row width has changed. - UIEdgeInsets contentInset = self.contentInset; - CGFloat constrainedWidth = self.bounds.size.width - [self sectionIndexWidth] - contentInset.left - contentInset.right; - if (constrainedWidth > 0 && _nodesConstrainedWidth != constrainedWidth) { - _nodesConstrainedWidth = constrainedWidth; - [_cellsForLayoutUpdates removeAllObjects]; - - [self beginUpdates]; - [_dataController relayoutAllNodesWithInvalidationBlock:nil]; - [self endUpdatesAnimated:(ASDisplayNodeLayerHasAnimations(self.layer) == NO) completion:nil]; - } else { - if (_cellsForLayoutUpdates.count > 0) { - NSArray *nodes = [_cellsForLayoutUpdates allObjects]; - [_cellsForLayoutUpdates removeAllObjects]; - - const auto nodesSizeChanged = [[NSMutableArray alloc] init]; - [_dataController relayoutNodes:nodes nodesSizeChanged:nodesSizeChanged]; - if (nodesSizeChanged.count > 0) { - [self requeryNodeHeights]; - } - } - } - - // To ensure _nodesConstrainedWidth is up-to-date for every usage, this call to super must be done last - [super layoutSubviews]; - [_rangeController updateIfNeeded]; -} - -#pragma mark - -#pragma mark Editing - -- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (sections.count == 0) { return; } - [self beginUpdates]; - [_changeSet insertSections:sections animationOptions:animation]; - [self endUpdates]; -} - -- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (sections.count == 0) { return; } - [self beginUpdates]; - [_changeSet deleteSections:sections animationOptions:animation]; - [self endUpdates]; -} - -- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (sections.count == 0) { return; } - [self beginUpdates]; - [_changeSet reloadSections:sections animationOptions:animation]; - [self endUpdates]; -} - -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet moveSection:section toSection:newSection animationOptions:UITableViewRowAnimationNone]; - [self endUpdates]; -} - -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (indexPaths.count == 0) { return; } - [self beginUpdates]; - [_changeSet insertItems:indexPaths animationOptions:animation]; - [self endUpdates]; -} - -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (indexPaths.count == 0) { return; } - [self beginUpdates]; - [_changeSet deleteItems:indexPaths animationOptions:animation]; - [self endUpdates]; -} - -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation -{ - ASDisplayNodeAssertMainThread(); - if (indexPaths.count == 0) { return; } - [self beginUpdates]; - [_changeSet reloadItems:indexPaths animationOptions:animation]; - [self endUpdates]; -} - -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath -{ - ASDisplayNodeAssertMainThread(); - [self beginUpdates]; - [_changeSet moveItemAtIndexPath:indexPath toIndexPath:newIndexPath animationOptions:UITableViewRowAnimationNone]; - [self endUpdates]; -} - -#pragma mark - -#pragma mark adjust content offset - -- (void)beginAdjustingContentOffset -{ - NSIndexPath *firstVisibleIndexPath = [self.indexPathsForVisibleRows sortedArrayUsingSelector:@selector(compare:)].firstObject; - if (firstVisibleIndexPath) { - ASCellNode *node = [self nodeForRowAtIndexPath:firstVisibleIndexPath]; - if (node) { - _contentOffsetAdjustmentTopVisibleNode = node; - _contentOffsetAdjustmentTopVisibleNodeOffset = [self rectForRowAtIndexPath:firstVisibleIndexPath].origin.y - self.bounds.origin.y; - } - } -} - -- (void)endAdjustingContentOffsetAnimated:(BOOL)animated -{ - // We can't do this for animated updates. - if (animated) { - return; - } - - // We can't do this if we didn't have a top visible row before. - if (_contentOffsetAdjustmentTopVisibleNode == nil) { - return; - } - - NSIndexPath *newIndexPathForTopVisibleRow = [self indexPathForNode:_contentOffsetAdjustmentTopVisibleNode]; - // We can't do this if our top visible row was deleted - if (newIndexPathForTopVisibleRow == nil) { - return; - } - - CGFloat newRowOriginYInSelf = [self rectForRowAtIndexPath:newIndexPathForTopVisibleRow].origin.y - self.bounds.origin.y; - CGPoint newContentOffset = self.contentOffset; - newContentOffset.y += (newRowOriginYInSelf - _contentOffsetAdjustmentTopVisibleNodeOffset); - self.contentOffset = newContentOffset; - _contentOffsetAdjustmentTopVisibleNode = nil; -} - -#pragma mark - Intercepted selectors - -- (void)setTableHeaderView:(UIView *)tableHeaderView -{ - // Typically the view will be nil before setting it, but reset state if it is being re-hosted. - [self.tableHeaderView.asyncdisplaykit_node exitHierarchyState:ASHierarchyStateRangeManaged]; - [super setTableHeaderView:tableHeaderView]; - [self.tableHeaderView.asyncdisplaykit_node enterHierarchyState:ASHierarchyStateRangeManaged]; -} - -- (void)setTableFooterView:(UIView *)tableFooterView -{ - // Typically the view will be nil before setting it, but reset state if it is being re-hosted. - [self.tableFooterView.asyncdisplaykit_node exitHierarchyState:ASHierarchyStateRangeManaged]; - [super setTableFooterView:tableFooterView]; - [self.tableFooterView.asyncdisplaykit_node enterHierarchyState:ASHierarchyStateRangeManaged]; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - _ASTableViewCell *cell = [self dequeueReusableCellWithIdentifier:kCellReuseIdentifier forIndexPath:indexPath]; - cell.delegate = self; - - ASCollectionElement *element = [_dataController.visibleMap elementForItemAtIndexPath:indexPath]; - cell.element = element; - ASCellNode *node = element.node; - if (node) { - [_rangeController configureContentView:cell.contentView forCellNode:node]; - } - - return cell; -} - -- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath -{ - CGFloat height = 0.0; - - ASCollectionElement *element = [_dataController.visibleMap elementForItemAtIndexPath:indexPath]; - if (element != nil) { - ASCellNode *node = element.node; - ASDisplayNodeAssertNotNil(node, @"Node must not be nil!"); - height = [node layoutThatFits:element.constrainedSize].size.height; - } - -#if TARGET_OS_IOS - /** - * Weirdly enough, Apple expects the return value here to _include_ the height - * of the separator, if there is one! So if our node wants to be 43.5, we need - * to return 44. UITableView will make a cell of height 44 with a content view - * of height 43.5. - */ - if (tableView.separatorStyle != UITableViewCellSeparatorStyleNone) { - height += 1.0 / ASScreenScale(); - } -#endif - - return height; -} - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView -{ - return _dataController.visibleMap.numberOfSections; -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - return [_dataController.visibleMap numberOfItemsInSection:section]; -} - -- (nullable NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)indexPath inView:(UIView *)view { - if (_asyncDataSourceFlags.modelIdentifierMethods) { - GET_TABLENODE_OR_RETURN(tableNode, nil); - NSIndexPath *convertedPath = [self convertIndexPathToTableNode:indexPath]; - if (convertedPath == nil) { - return nil; - } else { - return [_asyncDataSource modelIdentifierForElementAtIndexPath:convertedPath inNode:tableNode]; - } - } else { - return nil; - } -} - -- (nullable NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inView:(UIView *)view { - if (_asyncDataSourceFlags.modelIdentifierMethods) { - GET_TABLENODE_OR_RETURN(tableNode, nil); - return [_asyncDataSource indexPathForElementWithModelIdentifier:identifier inNode:tableNode]; - } else { - return nil; - } -} - -- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath -{ - if (_asyncDataSourceFlags.tableViewCanMoveRow) { - return [_asyncDataSource tableView:self canMoveRowAtIndexPath:indexPath]; - } else { - return NO; - } -} - -- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath -{ - if (_asyncDataSourceFlags.tableViewMoveRow) { - [_asyncDataSource tableView:self moveRowAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; - } - - // Move node after informing data source in case they call nodeAtIndexPath: - // Get up to date - [self waitUntilAllUpdatesAreCommitted]; - // Set our flag to suppress informing super about the change. - _updatingInResponseToInteractiveMove = YES; - // Submit the move - [self moveRowAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; - // Wait for it to finish – should be fast! - [self waitUntilAllUpdatesAreCommitted]; - // Clear the flag - _updatingInResponseToInteractiveMove = NO; -} - -- (void)tableView:(UITableView *)tableView willDisplayCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath -{ - ASCollectionElement *element = cell.element; - if (element) { - ASDisplayNodeAssertTrue([_dataController.visibleMap elementForItemAtIndexPath:indexPath] == element); - [_visibleElements addObject:element]; - } else { - ASDisplayNodeAssert(NO, @"Unexpected nil element for willDisplayCell: %@, %@, %@", cell, self, indexPath); - return; - } - - ASCellNode *cellNode = element.node; - cellNode.scrollView = tableView; - - ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with cell that will be displayed not to be nil. indexPath: %@", indexPath); - - if (_asyncDelegateFlags.tableNodeWillDisplayNodeForRow) { - GET_TABLENODE_OR_RETURN(tableNode, (void)0); - [_asyncDelegate tableNode:tableNode willDisplayRowWithNode:cellNode]; - } else if (_asyncDelegateFlags.tableViewWillDisplayNodeForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self willDisplayNode:cellNode forRowAtIndexPath:indexPath]; - } else if (_asyncDelegateFlags.tableViewWillDisplayNodeForRowDeprecated) { - [_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath]; - } -#pragma clang diagnostic pop - - [_rangeController setNeedsUpdate]; - - if ([cell consumesCellNodeVisibilityEvents]) { - [_cellsForVisibilityUpdates addObject:cell]; - } -} - -- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath -{ - // Retrieve the element from cell instead of visible map because at this point visible map could have been updated and no longer holds the element. - ASCollectionElement *element = cell.element; - if (element) { - [_visibleElements removeObject:element]; - } else { - ASDisplayNodeAssert(NO, @"Unexpected nil element for didEndDisplayingCell: %@, %@, %@", cell, self, indexPath); - return; - } - - ASCellNode *cellNode = element.node; - - [_rangeController setNeedsUpdate]; - - ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil."); - if (_asyncDelegateFlags.tableNodeDidEndDisplayingNodeForRow) { - if (ASTableNode *tableNode = self.tableNode) { - [_asyncDelegate tableNode:tableNode didEndDisplayingRowWithNode:cellNode]; - } - } else if (_asyncDelegateFlags.tableViewDidEndDisplayingNodeForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self didEndDisplayingNode:cellNode forRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } - - [_cellsForVisibilityUpdates removeObject:cell]; - - cellNode.scrollView = nil; -} - -- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.tableNodeWillSelectRow) { - GET_TABLENODE_OR_RETURN(tableNode, indexPath); - NSIndexPath *result = [self convertIndexPathToTableNode:indexPath]; - // If this item was is gone, just let the table view do its default behavior and select. - if (result == nil) { - return indexPath; - } else { - result = [_asyncDelegate tableNode:tableNode willSelectRowAtIndexPath:result]; - result = [self convertIndexPathFromTableNode:result waitingIfNeeded:YES]; - return result; - } - } else if (_asyncDelegateFlags.tableViewWillSelectRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate tableView:self willSelectRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } else { - return indexPath; - } -} - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.tableNodeDidSelectRow) { - GET_TABLENODE_OR_RETURN(tableNode, (void)0); - indexPath = [self convertIndexPathToTableNode:indexPath]; - if (indexPath != nil) { - [_asyncDelegate tableNode:tableNode didSelectRowAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.tableViewDidSelectRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self didSelectRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } -} - -- (NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.tableNodeWillDeselectRow) { - GET_TABLENODE_OR_RETURN(tableNode, indexPath); - NSIndexPath *result = [self convertIndexPathToTableNode:indexPath]; - // If this item was is gone, just let the table view do its default behavior and deselect. - if (result == nil) { - return indexPath; - } else { - result = [_asyncDelegate tableNode:tableNode willDeselectRowAtIndexPath:result]; - result = [self convertIndexPathFromTableNode:result waitingIfNeeded:YES]; - return result; - } - } else if (_asyncDelegateFlags.tableViewWillDeselectRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate tableView:self willDeselectRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } - return indexPath; -} - -- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.tableNodeDidDeselectRow) { - GET_TABLENODE_OR_RETURN(tableNode, (void)0); - indexPath = [self convertIndexPathToTableNode:indexPath]; - if (indexPath != nil) { - [_asyncDelegate tableNode:tableNode didDeselectRowAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.tableViewDidDeselectRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self didDeselectRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } -} - -- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.tableNodeShouldHighlightRow) { - GET_TABLENODE_OR_RETURN(tableNode, NO); - indexPath = [self convertIndexPathToTableNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate tableNode:tableNode shouldHighlightRowAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.tableViewShouldHighlightRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate tableView:self shouldHighlightRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } - return YES; -} - -- (void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.tableNodeDidHighlightRow) { - GET_TABLENODE_OR_RETURN(tableNode, (void)0); - indexPath = [self convertIndexPathToTableNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate tableNode:tableNode didHighlightRowAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.tableViewDidHighlightRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self didHighlightRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } -} - -- (void)tableView:(UITableView *)tableView didUnhighlightRowAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.tableNodeDidHighlightRow) { - GET_TABLENODE_OR_RETURN(tableNode, (void)0); - indexPath = [self convertIndexPathToTableNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate tableNode:tableNode didUnhighlightRowAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.tableViewDidUnhighlightRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self didUnhighlightRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } -} - -- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - if (_asyncDelegateFlags.tableNodeShouldShowMenuForRow) { - GET_TABLENODE_OR_RETURN(tableNode, NO); - indexPath = [self convertIndexPathToTableNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate tableNode:tableNode shouldShowMenuForRowAtIndexPath:indexPath]; - } - } else if (_asyncDelegateFlags.tableViewShouldShowMenuForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate tableView:self shouldShowMenuForRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - } - return NO; -} - -- (BOOL)tableView:(UITableView *)tableView canPerformAction:(nonnull SEL)action forRowAtIndexPath:(nonnull NSIndexPath *)indexPath withSender:(nullable id)sender -{ - if (_asyncDelegateFlags.tableNodeCanPerformActionForRow) { - GET_TABLENODE_OR_RETURN(tableNode, NO); - indexPath = [self convertIndexPathToTableNode:indexPath]; - if (indexPath != nil) { - return [_asyncDelegate tableNode:tableNode canPerformAction:action forRowAtIndexPath:indexPath withSender:sender]; - } - } else if (_asyncDelegateFlags.tableViewCanPerformActionForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate tableView:self canPerformAction:action forRowAtIndexPath:indexPath withSender:sender]; -#pragma clang diagnostic pop - } - return NO; -} - -- (void)tableView:(UITableView *)tableView performAction:(nonnull SEL)action forRowAtIndexPath:(nonnull NSIndexPath *)indexPath withSender:(nullable id)sender -{ - if (_asyncDelegateFlags.tableNodePerformActionForRow) { - GET_TABLENODE_OR_RETURN(tableNode, (void)0); - indexPath = [self convertIndexPathToTableNode:indexPath]; - if (indexPath != nil) { - [_asyncDelegate tableNode:tableNode performAction:action forRowAtIndexPath:indexPath withSender:sender]; - } - } else if (_asyncDelegateFlags.tableViewPerformActionForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self performAction:action forRowAtIndexPath:indexPath withSender:sender]; -#pragma clang diagnostic pop - } -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - if (scrollView != self && UITABLEVIEW_RESPONDS_TO_SELECTOR()) { - [super scrollViewDidScroll:scrollView]; - return; - } - ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; - if (ASInterfaceStateIncludesVisible(interfaceState)) { - [self _checkForBatchFetching]; - } - for (_ASTableViewCell *tableCell in _cellsForVisibilityUpdates) { - [[tableCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged - inScrollView:scrollView - withCellFrame:tableCell.frame]; - } - if (_asyncDelegateFlags.scrollViewDidScroll) { - [_asyncDelegate scrollViewDidScroll:scrollView]; - } -} - -- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset -{ - if (scrollView != self && UITABLEVIEW_RESPONDS_TO_SELECTOR()) { - [super scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; - return; - } - CGPoint contentOffset = scrollView.contentOffset; - _deceleratingVelocity = CGPointMake( - contentOffset.x - ((targetContentOffset != NULL) ? targetContentOffset->x : 0), - contentOffset.y - ((targetContentOffset != NULL) ? targetContentOffset->y : 0) - ); - - if (targetContentOffset != NULL) { - ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist"); - [self _beginBatchFetchingIfNeededWithContentOffset:*targetContentOffset velocity:velocity]; - } - - if (_asyncDelegateFlags.scrollViewWillEndDragging) { - [_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:(targetContentOffset ? : &contentOffset)]; - } -} - -- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView -{ - if (scrollView != self && UITABLEVIEW_RESPONDS_TO_SELECTOR()) { - [super scrollViewDidEndDecelerating:scrollView]; - return; - } - _deceleratingVelocity = CGPointZero; - - if (_asyncDelegateFlags.scrollViewDidEndDecelerating) { - [_asyncDelegate scrollViewDidEndDecelerating:scrollView]; - } -} - -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView -{ - if (scrollView != self && UITABLEVIEW_RESPONDS_TO_SELECTOR()) { - [super scrollViewWillBeginDragging:scrollView]; - return; - } - // If a scroll happens the current range mode needs to go to full - _rangeController.contentHasBeenScrolled = YES; - [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; - - for (_ASTableViewCell *tableViewCell in _cellsForVisibilityUpdates) { - [[tableViewCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventWillBeginDragging - inScrollView:scrollView - withCellFrame:tableViewCell.frame]; - } - if (_asyncDelegateFlags.scrollViewWillBeginDragging) { - [_asyncDelegate scrollViewWillBeginDragging:scrollView]; - } -} - -- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate -{ - if (scrollView != self && UITABLEVIEW_RESPONDS_TO_SELECTOR()) { - [super scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; - return; - } - for (_ASTableViewCell *tableViewCell in _cellsForVisibilityUpdates) { - [[tableViewCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventDidEndDragging - inScrollView:scrollView - withCellFrame:tableViewCell.frame]; - } - if (_asyncDelegateFlags.scrollViewDidEndDragging) { - [_asyncDelegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; - } -} - -#pragma mark - Misc - -- (BOOL)inverted -{ - return _inverted; -} - -- (void)setInverted:(BOOL)inverted -{ - _inverted = inverted; -} - -- (CGFloat)leadingScreensForBatching -{ - return _leadingScreensForBatching; -} - -- (void)setLeadingScreensForBatching:(CGFloat)leadingScreensForBatching -{ - if (_leadingScreensForBatching != leadingScreensForBatching) { - _leadingScreensForBatching = leadingScreensForBatching; - ASPerformBlockOnMainThread(^{ - [self _checkForBatchFetching]; - }); - } -} - -- (BOOL)automaticallyAdjustsContentOffset -{ - return _automaticallyAdjustsContentOffset; -} - -- (void)setAutomaticallyAdjustsContentOffset:(BOOL)automaticallyAdjustsContentOffset -{ - _automaticallyAdjustsContentOffset = automaticallyAdjustsContentOffset; -} - -#pragma mark - Scroll Direction - -- (ASScrollDirection)scrollDirection -{ - CGPoint scrollVelocity; - if (self.isTracking) { - scrollVelocity = [self.panGestureRecognizer velocityInView:self.superview]; - } else { - scrollVelocity = _deceleratingVelocity; - } - - ASScrollDirection scrollDirection = [self _scrollDirectionForVelocity:scrollVelocity]; - return ASScrollDirectionApplyTransform(scrollDirection, self.transform); -} - -- (ASScrollDirection)_scrollDirectionForVelocity:(CGPoint)scrollVelocity -{ - ASScrollDirection direction = ASScrollDirectionNone; - ASScrollDirection scrollableDirections = [self scrollableDirections]; - - if (ASScrollDirectionContainsVerticalDirection(scrollableDirections)) { // Can scroll vertically. - if (scrollVelocity.y < 0.0) { - direction |= ASScrollDirectionDown; - } else if (scrollVelocity.y > 0.0) { - direction |= ASScrollDirectionUp; - } - } - - return direction; -} - -- (ASScrollDirection)scrollableDirections -{ - ASScrollDirection scrollableDirection = ASScrollDirectionNone; - CGFloat totalContentWidth = self.contentSize.width + self.contentInset.left + self.contentInset.right; - CGFloat totalContentHeight = self.contentSize.height + self.contentInset.top + self.contentInset.bottom; - - if (self.alwaysBounceHorizontal || totalContentWidth > self.bounds.size.width) { // Can scroll horizontally. - scrollableDirection |= ASScrollDirectionHorizontalDirections; - } - if (self.alwaysBounceVertical || totalContentHeight > self.bounds.size.height) { // Can scroll vertically. - scrollableDirection |= ASScrollDirectionVerticalDirections; - } - return scrollableDirection; -} - - -#pragma mark - Batch Fetching - -- (ASBatchContext *)batchContext -{ - return _batchContext; -} - -- (BOOL)canBatchFetch -{ - // if the delegate does not respond to this method, there is no point in starting to fetch - BOOL canFetch = _asyncDelegateFlags.tableNodeWillBeginBatchFetch || _asyncDelegateFlags.tableViewWillBeginBatchFetch; - if (canFetch && _asyncDelegateFlags.shouldBatchFetchForTableNode) { - GET_TABLENODE_OR_RETURN(tableNode, NO); - return [_asyncDelegate shouldBatchFetchForTableNode:tableNode]; - } else if (canFetch && _asyncDelegateFlags.shouldBatchFetchForTableView) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate shouldBatchFetchForTableView:self]; -#pragma clang diagnostic pop - } else { - return canFetch; - } -} - -- (id)batchFetchingDelegate -{ - return self.tableNode.batchFetchingDelegate; -} - -- (void)_scheduleCheckForBatchFetchingForNumberOfChanges:(NSUInteger)changes -{ - // Prevent fetching will continually trigger in a loop after reaching end of content and no new content was provided - if (changes == 0 && _hasEverCheckedForBatchFetchingDueToUpdate) { - return; - } - _hasEverCheckedForBatchFetchingDueToUpdate = YES; - - // Push this to the next runloop to be sure the scroll view has the right content size - dispatch_async(dispatch_get_main_queue(), ^{ - [self _checkForBatchFetching]; - }); -} - -- (void)_checkForBatchFetching -{ - // Dragging will be handled in scrollViewWillEndDragging:withVelocity:targetContentOffset: - if (self.isDragging || self.isTracking) { - return; - } - - [self _beginBatchFetchingIfNeededWithContentOffset:self.contentOffset velocity:CGPointZero]; -} - -- (void)_beginBatchFetchingIfNeededWithContentOffset:(CGPoint)contentOffset velocity:(CGPoint)velocity -{ - if (ASDisplayShouldFetchBatchForScrollView(self, self.scrollDirection, ASScrollDirectionVerticalDirections, contentOffset, velocity)) { - [self _beginBatchFetching]; - } -} - -- (void)_beginBatchFetching -{ - [_batchContext beginBatchFetching]; - if (_asyncDelegateFlags.tableNodeWillBeginBatchFetch) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - GET_TABLENODE_OR_RETURN(tableNode, (void)0); - [_asyncDelegate tableNode:tableNode willBeginBatchFetchWithContext:_batchContext]; - }); - } else if (_asyncDelegateFlags.tableViewWillBeginBatchFetch) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext]; -#pragma clang diagnostic pop - }); - } -} - -#pragma mark - ASRangeControllerDataSource - -- (ASRangeController *)rangeController -{ - return _rangeController; -} - -- (NSHashTable *)visibleElementsForRangeController:(ASRangeController *)rangeController -{ - return ASPointerTableByFlatMapping(_visibleElements, id element, element); -} - -- (ASScrollDirection)scrollDirectionForRangeController:(ASRangeController *)rangeController -{ - return self.scrollDirection; -} - -- (ASInterfaceState)interfaceStateForRangeController:(ASRangeController *)rangeController -{ - return ASInterfaceStateForDisplayNode(self.tableNode, self.window); -} - -- (NSString *)nameForRangeControllerDataSource -{ - return self.asyncDataSource ? NSStringFromClass([self.asyncDataSource class]) : NSStringFromClass([self class]); -} - -#pragma mark - ASRangeControllerDelegate - -- (BOOL)rangeControllerShouldUpdateRanges:(ASRangeController *)rangeController -{ - return YES; -} - -- (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates -{ - ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _updatingInResponseToInteractiveMove) { - updates(); - [changeSet executeCompletionHandlerWithFinished:NO]; - return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes - } - - if (changeSet.includesReloadData) { - LOG(@"UITableView reloadData"); - ASPerformBlockWithoutAnimation(!changeSet.animated, ^{ - if (self.test_enableSuperUpdateCallLogging) { - NSLog(@"-[super reloadData]"); - } - updates(); - [super reloadData]; - // Flush any range changes that happened as part of submitting the reload. - [_rangeController updateIfNeeded]; - [self _scheduleCheckForBatchFetchingForNumberOfChanges:1]; - [changeSet executeCompletionHandlerWithFinished:YES]; - }); - return; - } - - BOOL shouldAdjustContentOffset = (_automaticallyAdjustsContentOffset && !changeSet.includesReloadData); - if (shouldAdjustContentOffset) { - [self beginAdjustingContentOffset]; - } - - NSUInteger numberOfUpdates = 0; - - LOG(@"--- UITableView beginUpdates"); - [super beginUpdates]; - - updates(); - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeReload]) { - NSArray *indexPaths = change.indexPaths; - UITableViewRowAnimation animationOptions = (UITableViewRowAnimation)change.animationOptions; - - LOG(@"UITableView reloadRows:%ld rows", indexPaths.count); - BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - if (self.test_enableSuperUpdateCallLogging) { - NSLog(@"-[super reloadRowsAtIndexPaths]: %@", indexPaths); - } - [super reloadRowsAtIndexPaths:indexPaths withRowAnimation:animationOptions]; - }); - - numberOfUpdates++; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload]) { - NSIndexSet *sectionIndexes = change.indexSet; - UITableViewRowAnimation animationOptions = (UITableViewRowAnimation)change.animationOptions; - - LOG(@"UITableView reloadSections:%@", sectionIndexes); - BOOL preventAnimation = (animationOptions == UITableViewRowAnimationNone); - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - if (self.test_enableSuperUpdateCallLogging) { - NSLog(@"-[super reloadSections]: %@", sectionIndexes); - } - [super reloadSections:sectionIndexes withRowAnimation:animationOptions]; - }); - - numberOfUpdates++; - } - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeOriginalDelete]) { - NSArray *indexPaths = change.indexPaths; - UITableViewRowAnimation animationOptions = (UITableViewRowAnimation)change.animationOptions; - - LOG(@"UITableView deleteRows:%ld rows", indexPaths.count); - BOOL preventAnimation = animationOptions == UITableViewRowAnimationNone; - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - if (self.test_enableSuperUpdateCallLogging) { - NSLog(@"-[super deleteRowsAtIndexPaths]: %@", indexPaths); - } - [super deleteRowsAtIndexPaths:indexPaths withRowAnimation:animationOptions]; - }); - - numberOfUpdates++; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeOriginalDelete]) { - NSIndexSet *sectionIndexes = change.indexSet; - UITableViewRowAnimation animationOptions = (UITableViewRowAnimation)change.animationOptions; - - LOG(@"UITableView deleteSections:%@", sectionIndexes); - BOOL preventAnimation = (animationOptions == UITableViewRowAnimationNone); - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - if (self.test_enableSuperUpdateCallLogging) { - NSLog(@"-[super deleteSections]: %@", sectionIndexes); - } - [super deleteSections:sectionIndexes withRowAnimation:animationOptions]; - }); - - numberOfUpdates++; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeOriginalInsert]) { - NSIndexSet *sectionIndexes = change.indexSet; - UITableViewRowAnimation animationOptions = (UITableViewRowAnimation)change.animationOptions; - - LOG(@"UITableView insertSections:%@", sectionIndexes); - BOOL preventAnimation = (animationOptions == UITableViewRowAnimationNone); - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - if (self.test_enableSuperUpdateCallLogging) { - NSLog(@"-[super insertSections]: %@", sectionIndexes); - } - [super insertSections:sectionIndexes withRowAnimation:animationOptions]; - }); - - numberOfUpdates++; - } - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeOriginalInsert]) { - NSArray *indexPaths = change.indexPaths; - UITableViewRowAnimation animationOptions = (UITableViewRowAnimation)change.animationOptions; - - LOG(@"UITableView insertRows:%ld rows", indexPaths.count); - BOOL preventAnimation = (animationOptions == UITableViewRowAnimationNone); - ASPerformBlockWithoutAnimation(preventAnimation, ^{ - if (self.test_enableSuperUpdateCallLogging) { - NSLog(@"-[super insertRowsAtIndexPaths]: %@", indexPaths); - } - [super insertRowsAtIndexPaths:indexPaths withRowAnimation:animationOptions]; - }); - - numberOfUpdates++; - } - - LOG(@"--- UITableView endUpdates"); - ASPerformBlockWithoutAnimation(!changeSet.animated, ^{ - [super endUpdates]; - [_rangeController updateIfNeeded]; - [self _scheduleCheckForBatchFetchingForNumberOfChanges:numberOfUpdates]; - }); - if (shouldAdjustContentOffset) { - [self endAdjustingContentOffsetAnimated:changeSet.animated]; - } - [changeSet executeCompletionHandlerWithFinished:YES]; -} - -#pragma mark - ASDataControllerSource - -- (BOOL)dataController:(ASDataController *)dataController shouldEagerlyLayoutNode:(ASCellNode *)node -{ - return YES; -} - -- (BOOL)dataControllerShouldSerializeNodeCreation:(ASDataController *)dataController -{ - return NO; -} - -- (BOOL)dataController:(ASDataController *)dataController shouldSynchronouslyProcessChangeSet:(_ASHierarchyChangeSet *)changeSet -{ - // Reload data is expensive, don't block main while doing so. - if (changeSet.includesReloadData) { - return NO; - } - // For more details on this method, see the comment in the ASCollectionView implementation. - if (changeSet.countForAsyncLayout < 2) { - return YES; - } - CGSize contentSize = self.contentSize; - CGSize boundsSize = self.bounds.size; - if (contentSize.height <= boundsSize.height && contentSize.width <= boundsSize.width) { - return YES; - } - return NO; -} - -- (void)dataControllerDidFinishWaiting:(ASDataController *)dataController -{ - // ASCellLayoutMode is not currently supported on ASTableView (see ASCollectionView for details). -} - -- (id)dataController:(ASDataController *)dataController nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath -{ - // Not currently supported for tables. Will be added when the collection API stabilizes. - return nil; -} - -- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath shouldAsyncLayout:(BOOL *)shouldAsyncLayout -{ - ASCellNodeBlock block = nil; - - if (_asyncDataSourceFlags.tableNodeNodeBlockForRow) { - if (ASTableNode *tableNode = self.tableNode) { - block = [_asyncDataSource tableNode:tableNode nodeBlockForRowAtIndexPath:indexPath]; - } - } else if (_asyncDataSourceFlags.tableNodeNodeForRow) { - ASCellNode *node = nil; - if (ASTableNode *tableNode = self.tableNode) { - node = [_asyncDataSource tableNode:tableNode nodeForRowAtIndexPath:indexPath]; - } - if ([node isKindOfClass:[ASCellNode class]]) { - block = ^{ - return node; - }; - } else { - ASDisplayNodeFailAssert(@"Data source returned invalid node from tableNode:nodeForRowAtIndexPath:. Node: %@", node); - } - } else if (_asyncDataSourceFlags.tableViewNodeBlockForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - block = [_asyncDataSource tableView:self nodeBlockForRowAtIndexPath:indexPath]; - } else if (_asyncDataSourceFlags.tableViewNodeForRow) { - ASCellNode *node = [_asyncDataSource tableView:self nodeForRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - if ([node isKindOfClass:[ASCellNode class]]) { - block = ^{ - return node; - }; - } else { - ASDisplayNodeFailAssert(@"Data source returned invalid node from tableView:nodeForRowAtIndexPath:. Node: %@", node); - } - } - - // Handle nil node block - if (block == nil) { - ASDisplayNodeFailAssert(@"ASTableNode could not get a node block for row at index path %@", indexPath); - block = ^{ - return [[ASCellNode alloc] init]; - }; - } - - // Wrap the node block - __weak __typeof__(self) weakSelf = self; - return ^{ - __typeof__(self) strongSelf = weakSelf; - ASCellNode *node = (block != nil ? block() : [[ASCellNode alloc] init]); - ASDisplayNodeAssert([node isKindOfClass:[ASCellNode class]], @"ASTableNode provided a non-ASCellNode! %@, %@", node, strongSelf); - - [node enterHierarchyState:ASHierarchyStateRangeManaged]; - if (node.interactionDelegate == nil) { - node.interactionDelegate = strongSelf; - } - if (_inverted) { - node.transform = CATransform3DMakeScale(1, -1, 1) ; - } - return node; - }; - return block; -} - -- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath -{ - ASSizeRange constrainedSize = ASSizeRangeZero; - if (_asyncDelegateFlags.tableNodeConstrainedSizeForRow) { - GET_TABLENODE_OR_RETURN(tableNode, constrainedSize); - ASSizeRange delegateConstrainedSize = [_asyncDelegate tableNode:tableNode constrainedSizeForRowAtIndexPath:indexPath]; - // ignore widths in the returned size range (for TableView) - constrainedSize = ASSizeRangeMake(CGSizeMake(_nodesConstrainedWidth, delegateConstrainedSize.min.height), - CGSizeMake(_nodesConstrainedWidth, delegateConstrainedSize.max.height)); - } else if (_asyncDelegateFlags.tableViewConstrainedSizeForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - ASSizeRange delegateConstrainedSize = [_asyncDelegate tableView:self constrainedSizeForRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - // ignore widths in the returned size range (for TableView) - constrainedSize = ASSizeRangeMake(CGSizeMake(_nodesConstrainedWidth, delegateConstrainedSize.min.height), - CGSizeMake(_nodesConstrainedWidth, delegateConstrainedSize.max.height)); - } else { - constrainedSize = ASSizeRangeMake(CGSizeMake(_nodesConstrainedWidth, 0), - CGSizeMake(_nodesConstrainedWidth, CGFLOAT_MAX)); - } - return constrainedSize; -} - -- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section -{ - if (_asyncDataSourceFlags.tableNodeNumberOfRowsInSection) { - GET_TABLENODE_OR_RETURN(tableNode, 0); - return [_asyncDataSource tableNode:tableNode numberOfRowsInSection:section]; - } else if (_asyncDataSourceFlags.tableViewNumberOfRowsInSection) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDataSource tableView:self numberOfRowsInSection:section]; -#pragma clang diagnostic pop - } else { - return 0; - } -} - -- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController -{ - if (_asyncDataSourceFlags.numberOfSectionsInTableNode) { - GET_TABLENODE_OR_RETURN(tableNode, 0); - return [_asyncDataSource numberOfSectionsInTableNode:tableNode]; - } else if (_asyncDataSourceFlags.numberOfSectionsInTableView) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDataSource numberOfSectionsInTableView:self]; -#pragma clang diagnostic pop - } else { - return 1; // default section number - } -} - -- (BOOL)dataController:(ASDataController *)dataController presentedSizeForElement:(ASCollectionElement *)element matchesSize:(CGSize)size -{ - NSIndexPath *indexPath = [self indexPathForNode:element.node]; - if (indexPath == nil) { - ASDisplayNodeFailAssert(@"Data controller should not ask for presented size for element that is not presented."); - return YES; - } - CGRect rect = [self rectForRowAtIndexPath:indexPath]; - -#if TARGET_OS_IOS - /** - * Weirdly enough, Apple expects the return value in tableView:heightForRowAtIndexPath: to _include_ the height - * of the separator, if there is one! So if rectForRow would return 44.0 we need to use 43.5. - */ - if (self.separatorStyle != UITableViewCellSeparatorStyleNone) { - rect.size.height -= 1.0 / ASScreenScale(); - } -#endif - - return (fabs(rect.size.height - size.height) < FLT_EPSILON); -} - -#pragma mark - _ASTableViewCellDelegate - -- (void)didLayoutSubviewsOfTableViewCell:(_ASTableViewCell *)tableViewCell -{ - ASCellNode *node = tableViewCell.node; - if (node == nil || _asyncDataSource == nil) { - return; - } - - CGFloat contentViewWidth = tableViewCell.contentView.bounds.size.width; - ASSizeRange constrainedSize = node.constrainedSizeForCalculatedLayout; - - // Table view cells should always fill its content view width. - // Normally the content view width equals to the constrained size width (which equals to the table view width). - // If there is a mismatch between these values, for example after the table view entered or left editing mode, - // content view width is preferred and used to re-measure the cell node. - if (CGSizeEqualToSize(node.calculatedSize, CGSizeZero) == NO && contentViewWidth != constrainedSize.max.width) { - constrainedSize.min.width = contentViewWidth; - constrainedSize.max.width = contentViewWidth; - - // Re-measurement is done on main to ensure thread affinity. In the worst case, this is as fast as UIKit's implementation. - // - // Unloaded nodes *could* be re-measured off the main thread, but only with the assumption that content view width - // is the same for all cells (because there is no easy way to get that individual value before the node being assigned to a _ASTableViewCell). - // Also, in many cases, some nodes may not need to be re-measured at all, such as when user enters and then immediately leaves editing mode. - // To avoid premature optimization and making such assumption, as well as to keep ASTableView simple, re-measurement is strictly done on main. - CGSize oldSize = node.bounds.size; - const CGSize calculatedSize = [node layoutThatFits:constrainedSize].size; - node.frame = { .size = calculatedSize }; - - // After the re-measurement, set the new constrained size to the node's backing colleciton element. - node.collectionElement.constrainedSize = constrainedSize; - - // If the node height changed, trigger a height requery. - if (oldSize.height != calculatedSize.height) { - [self beginUpdates]; - [self endUpdatesAnimated:(ASDisplayNodeLayerHasAnimations(self.layer) == NO) completion:nil]; - } - } -} - -#pragma mark - ASCellNodeDelegate - -- (void)nodeSelectedStateDidChange:(ASCellNode *)node -{ - NSIndexPath *indexPath = [self indexPathForNode:node]; - if (indexPath) { - if (node.isSelected) { - [self selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; - } else { - [self deselectRowAtIndexPath:indexPath animated:NO]; - } - } -} - -- (void)nodeHighlightedStateDidChange:(ASCellNode *)node -{ - NSIndexPath *indexPath = [self indexPathForNode:node]; - if (indexPath) { - [self cellForRowAtIndexPath:indexPath].highlighted = node.isHighlighted; - } -} - -- (void)nodeDidInvalidateSize:(ASCellNode *)node -{ - [_cellsForLayoutUpdates addObject:node]; - [self setNeedsLayout]; -} - -// Cause UITableView to requery for the new height of this node -- (void)requeryNodeHeights -{ - _queuedNodeHeightUpdate = NO; - - [super beginUpdates]; - [super endUpdates]; -} - -#pragma mark - Helper Methods - -// Note: This is called every layout, and so it is very performance sensitive. -- (CGFloat)sectionIndexWidth -{ - // If they don't implement the methods, then there's no section index. - if (_asyncDataSourceFlags.sectionIndexMethods == NO) { - return 0; - } - - UIView *indexView = _sectionIndexView; - if (indexView.superview == self) { - return indexView.frame.size.width; - } - - CGRect bounds = self.bounds; - for (UIView *view in self.subviews) { - CGRect frame = view.frame; - // Section index is right-aligned and less than half-width. - if (CGRectGetMaxX(frame) == CGRectGetMaxX(bounds) && frame.size.width * 2 < bounds.size.width) { - _sectionIndexView = view; - return frame.size.width; - } - } - return 0; -} - -#pragma mark - _ASDisplayView behavior substitutions -// Need these to drive interfaceState so we know when we are visible, if not nested in another range-managing element. -// Because our superclass is a true UIKit class, we cannot also subclass _ASDisplayView. -- (void)willMoveToWindow:(UIWindow *)newWindow -{ - BOOL visible = (newWindow != nil); - ASDisplayNode *node = self.tableNode; - if (visible && !node.inHierarchy) { - [node __enterHierarchy]; - } -} - -- (void)didMoveToWindow -{ - BOOL visible = (self.window != nil); - ASDisplayNode *node = self.tableNode; - BOOL rangeControllerNeedsUpdate = ![node supportsRangeManagedInterfaceState];; - - if (!visible && node.inHierarchy) { - if (rangeControllerNeedsUpdate) { - rangeControllerNeedsUpdate = NO; - // Exit CellNodes first before Table to match UIKit behaviors (tear down bottom up). - // Although we have not yet cleared the interfaceState's Visible bit (this happens in __exitHierarchy), - // the ASRangeController will get the correct value from -interfaceStateForRangeController:. - [_rangeController updateRanges]; - } - [node __exitHierarchy]; - } - - // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their - // their update in the layout pass - if (rangeControllerNeedsUpdate) { - [_rangeController updateRanges]; - } - - // When we aren't visible, we will only fetch up to the visible area. Now that we are visible, - // we will fetch visible area + leading screens, so we need to check. - if (visible) { - [self _checkForBatchFetching]; - } -} - -- (void)willMoveToSuperview:(UIView *)newSuperview -{ - if (self.superview == nil && newSuperview != nil) { - _keepalive_node = self.tableNode; - } -} - -- (void)didMoveToSuperview -{ - if (self.superview == nil) { - _keepalive_node = nil; - } -} - -#pragma mark - Accessibility overrides - -- (NSArray *)accessibilityElements -{ - [self waitUntilAllUpdatesAreCommitted]; - return [super accessibilityElements]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTableViewInternal.h b/submodules/AsyncDisplayKit/Source/ASTableViewInternal.h deleted file mode 100644 index b887e9323b..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTableViewInternal.h +++ /dev/null @@ -1,69 +0,0 @@ -// -// ASTableViewInternal.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 - -@class ASDataController; -@class ASTableNode; -@class ASRangeController; -@class ASEventLog; - -@interface ASTableView (Internal) - -@property (nonatomic, readonly) ASDataController *dataController; -@property (nonatomic, weak) ASTableNode *tableNode; -@property (nonatomic, readonly) ASRangeController *rangeController; - -/** - * Initializer. - * - * @param frame A rectangle specifying the initial location and size of the table view in its superview’€™s coordinates. - * The frame of the table view changes as table cells are added and deleted. - * - * @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants. - * - * @param dataControllerClass A controller class injected to and used to create a data controller for the table view. - * - * @param eventLog An event log passed through to the data controller. - */ -- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass owningNode:(ASTableNode *)tableNode eventLog:(ASEventLog *)eventLog; - -/// Set YES and we'll log every time we call [super insertRows…] etc -@property (nonatomic) BOOL test_enableSuperUpdateCallLogging; - -/** - * Attempt to get the view-layer index path for the row with the given index path. - * - * @param indexPath The index path of the row. - * @param wait If the item hasn't reached the view yet, this attempts to wait for updates to commit. - */ -- (NSIndexPath *)convertIndexPathFromTableNode:(NSIndexPath *)indexPath waitingIfNeeded:(BOOL)wait; - -/** - * Attempt to get the node index path given the view-layer index path. - * - * @param indexPath The index path of the row. - */ -- (NSIndexPath *)convertIndexPathToTableNode:(NSIndexPath *)indexPath; - -/** - * Attempt to get the node index paths given the view-layer index paths. - * - * @param indexPaths An array of index paths in the view space - */ -- (NSArray *)convertIndexPathsToTableNode:(NSArray *)indexPaths; - -/// Returns the width of the section index view on the right-hand side of the table, if one is present. -- (CGFloat)sectionIndexWidth; - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTableViewProtocols.h b/submodules/AsyncDisplayKit/Source/ASTableViewProtocols.h deleted file mode 100644 index 56de956724..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTableViewProtocols.h +++ /dev/null @@ -1,98 +0,0 @@ -// -// ASTableViewProtocols.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 - -NS_ASSUME_NONNULL_BEGIN - -/** - * This is a subset of UITableViewDataSource. - * - * @see ASTableDataSource - */ -@protocol ASCommonTableDataSource - -@optional - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:numberOfRowsInSection: instead."); - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView ASDISPLAYNODE_DEPRECATED_MSG("Implement numberOfSectionsInTableNode: instead."); - -- (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section; -- (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section; - -- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath; - -- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath; - -- (nullable NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView; -- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index; - -- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath; - -- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath; - -@end - - -/** - * This is a subset of UITableViewDelegate. - * - * @see ASTableDelegate - */ -@protocol ASCommonTableViewDelegate - -@optional - -- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section; -- (void)tableView:(UITableView *)tableView willDisplayFooterView:(UIView *)view forSection:(NSInteger)section; -- (void)tableView:(UITableView *)tableView didEndDisplayingHeaderView:(UIView *)view forSection:(NSInteger)section; -- (void)tableView:(UITableView *)tableView didEndDisplayingFooterView:(UIView *)view forSection:(NSInteger)section; - -- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section; -- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section; - -- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section; -- (nullable UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section; - -- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath; - -- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:shouldHighlightRowAtIndexPath: instead."); -- (void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:didHighlightRowAtIndexPath: instead."); -- (void)tableView:(UITableView *)tableView didUnhighlightRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:didUnhighlightRowAtIndexPath: instead."); - -- (nullable NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:willSelectRowAtIndexPath: instead."); -- (nullable NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:willDeselectRowAtIndexPath: instead."); -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:didSelectRowAtIndexPath: instead."); -- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:didDeselectRowAtIndexPath: instead."); - -- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath; -- (nullable NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath; -#if TARGET_OS_IOS -- (nullable NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath; -#endif -- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath; - -- (void)tableView:(UITableView*)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath; -- (void)tableView:(UITableView*)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath; - -- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath; - -- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath; - -- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:shouldShowMenuForRowAtIndexPath: instead."); -- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:canPerformAction:forRowAtIndexPath:withSender: instead."); -- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:performAction:forRowAtIndexPath:withSender: instead."); - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTextLayout.h b/submodules/AsyncDisplayKit/Source/ASTextLayout.h deleted file mode 100644 index 46bc8ccf52..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextLayout.h +++ /dev/null @@ -1,547 +0,0 @@ -// -// ASTextLayout.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 -#import - -#import "ASTextDebugOption.h" -#import "ASTextLine.h" -#import "ASTextInput.h" - -@protocol ASTextLinePositionModifier; - -NS_ASSUME_NONNULL_BEGIN - -/** - The max text container size in layout. - */ -AS_EXTERN const CGSize ASTextContainerMaxSize; - -/** - The ASTextContainer class defines a region in which text is laid out. - ASTextLayout class uses one or more ASTextContainer objects to generate layouts. - - A ASTextContainer defines rectangular regions (`size` and `insets`) or - nonrectangular shapes (`path`), and you can define exclusion paths inside the - text container's bounding rectangle so that text flows around the exclusion - path as it is laid out. - - All methods in this class is thread-safe. - - Example: - - ┌─────────────────────────────┐ <------- container - │ │ - │ asdfasdfasdfasdfasdfa <------------ container insets - │ asdfasdfa asdfasdfa │ - │ asdfas asdasd │ - │ asdfa <----------------------- container exclusion path - │ asdfas adfasd │ - │ asdfasdfa asdfasdfa │ - │ asdfasdfasdfasdfasdfa │ - │ │ - └─────────────────────────────┘ - */ -@interface ASTextContainer : NSObject - -/// Creates a container with the specified size. @param size The size. -+ (instancetype)containerWithSize:(CGSize)size NS_RETURNS_RETAINED; - -/// Creates a container with the specified size and insets. @param size The size. @param insets The text insets. -+ (instancetype)containerWithSize:(CGSize)size insets:(UIEdgeInsets)insets NS_RETURNS_RETAINED; - -/// Creates a container with the specified path. @param path The path. -+ (instancetype)containerWithPath:(nullable UIBezierPath *)path NS_RETURNS_RETAINED; - -/// Mark this immutable, so you get free copies going forward. -- (void)makeImmutable; - -/// The constrained size. (if the size is larger than ASTextContainerMaxSize, it will be clipped) -@property CGSize size; - -/// The insets for constrained size. The inset value should not be negative. Default is UIEdgeInsetsZero. -@property UIEdgeInsets insets; - -/// Custom constrained path. Set this property to ignore `size` and `insets`. Default is nil. -@property (nullable, copy) UIBezierPath *path; - -/// An array of `UIBezierPath` for path exclusion. Default is nil. -@property (nullable, copy) NSArray *exclusionPaths; - -/// Path line width. Default is 0; -@property CGFloat pathLineWidth; - -/// YES:(PathFillEvenOdd) Text is filled in the area that would be painted if the path were given to CGContextEOFillPath. -/// NO: (PathFillWindingNumber) Text is fill in the area that would be painted if the path were given to CGContextFillPath. -/// Default is YES; -@property (getter=isPathFillEvenOdd) BOOL pathFillEvenOdd; - -/// Whether the text is vertical form (may used for CJK text layout). Default is NO. -@property (getter=isVerticalForm) BOOL verticalForm; - -/// Maximum number of rows, 0 means no limit. Default is 0. -@property NSUInteger maximumNumberOfRows; - -/// The line truncation type, default is none. -@property ASTextTruncationType truncationType; - -/// The truncation token. If nil, the layout will use "…" instead. Default is nil. -@property (nullable, copy) NSAttributedString *truncationToken; - -/// This modifier is applied to the lines before the layout is completed, -/// give you a chance to modify the line position. Default is nil. -@property (nullable, copy) id linePositionModifier; -@end - - -/** - The ASTextLinePositionModifier protocol declares the required method to modify - the line position in text layout progress. See `ASTextLinePositionSimpleModifier` for example. - */ -@protocol ASTextLinePositionModifier -@required -/** - This method will called before layout is completed. The method should be thread-safe. - @param lines An array of ASTextLine. - @param text The full text. - @param container The layout container. - */ -- (void)modifyLines:(NSArray *)lines fromText:(NSAttributedString *)text inContainer:(ASTextContainer *)container; -@end - - -/** - A simple implementation of `ASTextLinePositionModifier`. It can fix each line's position - to a specified value, lets each line of height be the same. - */ -@interface ASTextLinePositionSimpleModifier : NSObject -@property CGFloat fixedLineHeight; ///< The fixed line height (distance between two baseline). -@end - - - -/** - ASTextLayout class is a readonly class stores text layout result. - All the property in this class is readonly, and should not be changed. - The methods in this class is thread-safe (except some of the draw methods). - - example: (layout with a circle exclusion path) - - ┌──────────────────────────┐ <------ container - │ [--------Line0--------] │ <- Row0 - │ [--------Line1--------] │ <- Row1 - │ [-Line2-] [-Line3-] │ <- Row2 - │ [-Line4] [Line5-] │ <- Row3 - │ [-Line6-] [-Line7-] │ <- Row4 - │ [--------Line8--------] │ <- Row5 - │ [--------Line9--------] │ <- Row6 - └──────────────────────────┘ - */ -@interface ASTextLayout : NSObject - - -#pragma mark - Generate text layout -///============================================================================= -/// @name Generate text layout -///============================================================================= - -/** - Generate a layout with the given container size and text. - - @param size The text container's size - @param text The text (if nil, returns nil). - @return A new layout, or nil when an error occurs. - */ -+ (nullable ASTextLayout *)layoutWithContainerSize:(CGSize)size text:(NSAttributedString *)text; - -/** - Generate a layout with the given container and text. - - @param container The text container (if nil, returns nil). - @param text The text (if nil, returns nil). - @return A new layout, or nil when an error occurs. - */ -+ (nullable ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttributedString *)text; - -/** - Generate a layout with the given container and text. - - @param container The text container (if nil, returns nil). - @param text The text (if nil, returns nil). - @param range The text range (if out of range, returns nil). If the - length of the range is 0, it means the length is no limit. - @return A new layout, or nil when an error occurs. - */ -+ (nullable ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range; - -/** - Generate layouts with the given containers and text. - - @param containers An array of ASTextContainer object (if nil, returns nil). - @param text The text (if nil, returns nil). - @return An array of ASTextLayout object (the count is same as containers), - or nil when an error occurs. - */ -+ (nullable NSArray *)layoutWithContainers:(NSArray *)containers - text:(NSAttributedString *)text; - -/** - Generate layouts with the given containers and text. - - @param containers An array of ASTextContainer object (if nil, returns nil). - @param text The text (if nil, returns nil). - @param range The text range (if out of range, returns nil). If the - length of the range is 0, it means the length is no limit. - @return An array of ASTextLayout object (the count is same as containers), - or nil when an error occurs. - */ -+ (nullable NSArray *)layoutWithContainers:(NSArray *)containers - text:(NSAttributedString *)text - range:(NSRange)range; - -- (instancetype)init UNAVAILABLE_ATTRIBUTE; -+ (instancetype)new UNAVAILABLE_ATTRIBUTE; - - -#pragma mark - Text layout attributes -///============================================================================= -/// @name Text layout attributes -///============================================================================= - -///< The text container -@property (nonatomic, readonly) ASTextContainer *container; -///< The full text -@property (nonatomic, readonly) NSAttributedString *text; -///< The text range in full text -@property (nonatomic, readonly) NSRange range; -///< CTFrame -@property (nonatomic, readonly) CTFrameRef frame; -///< Array of `ASTextLine`, no truncated -@property (nonatomic, readonly) NSArray *lines; -///< ASTextLine with truncated token, or nil -@property (nullable, nonatomic, readonly) ASTextLine *truncatedLine; -///< Array of `ASTextAttachment` -@property (nullable, nonatomic, readonly) NSArray *attachments; -///< Array of NSRange(wrapped by NSValue) in text -@property (nullable, nonatomic, readonly) NSArray *attachmentRanges; -///< Array of CGRect(wrapped by NSValue) in container -@property (nullable, nonatomic, readonly) NSArray *attachmentRects; -///< Set of Attachment (UIImage/UIView/CALayer) -@property (nullable, nonatomic, readonly) NSSet *attachmentContentsSet; -///< Number of rows -@property (nonatomic, readonly) NSUInteger rowCount; -///< Visible text range -@property (nonatomic, readonly) NSRange visibleRange; -///< Bounding rect (glyphs) -@property (nonatomic, readonly) CGRect textBoundingRect; -///< Bounding size (glyphs and insets, ceil to pixel) -@property (nonatomic, readonly) CGSize textBoundingSize; -///< Has highlight attribute -@property (nonatomic, readonly) BOOL containsHighlight; -///< Has block border attribute -@property (nonatomic, readonly) BOOL needDrawBlockBorder; -///< Has background border attribute -@property (nonatomic, readonly) BOOL needDrawBackgroundBorder; -///< Has shadow attribute -@property (nonatomic, readonly) BOOL needDrawShadow; -///< Has underline attribute -@property (nonatomic, readonly) BOOL needDrawUnderline; -///< Has visible text -@property (nonatomic, readonly) BOOL needDrawText; -///< Has attachment attribute -@property (nonatomic, readonly) BOOL needDrawAttachment; -///< Has inner shadow attribute -@property (nonatomic, readonly) BOOL needDrawInnerShadow; -///< Has strickthrough attribute -@property (nonatomic, readonly) BOOL needDrawStrikethrough; -///< Has border attribute -@property (nonatomic, readonly) BOOL needDrawBorder; - - -#pragma mark - Query information from text layout -///============================================================================= -/// @name Query information from text layout -///============================================================================= - -/** - The first line index for row. - - @param row A row index. - @return The line index, or NSNotFound if not found. - */ -- (NSUInteger)lineIndexForRow:(NSUInteger)row; - -/** - The number of lines for row. - - @param row A row index. - @return The number of lines, or NSNotFound when an error occurs. - */ -- (NSUInteger)lineCountForRow:(NSUInteger)row; - -/** - The row index for line. - - @param line A row index. - - @return The row index, or NSNotFound if not found. - */ -- (NSUInteger)rowIndexForLine:(NSUInteger)line; - -/** - The line index for a specified point. - - @discussion It returns NSNotFound if there's no text at the point. - - @param point A point in the container. - @return The line index, or NSNotFound if not found. - */ -- (NSUInteger)lineIndexForPoint:(CGPoint)point; - -/** - The line index closest to a specified point. - - @param point A point in the container. - @return The line index, or NSNotFound if no line exist in layout. - */ -- (NSUInteger)closestLineIndexForPoint:(CGPoint)point; - -/** - The offset in container for a text position in a specified line. - - @discussion The offset is the text position's baseline point.x. - If the container is vertical form, the offset is the baseline point.y; - - @param position The text position in string. - @param lineIndex The line index. - @return The offset in container, or CGFLOAT_MAX if not found. - */ -- (CGFloat)offsetForTextPosition:(NSUInteger)position lineIndex:(NSUInteger)lineIndex; - -/** - The text position for a point in a specified line. - - @discussion This method just call CTLineGetStringIndexForPosition() and does - NOT consider the emoji, line break character, binding text... - - @param point A point in the container. - @param lineIndex The line index. - @return The text position, or NSNotFound if not found. - */ -- (NSUInteger)textPositionForPoint:(CGPoint)point lineIndex:(NSUInteger)lineIndex; - -/** - The closest text position to a specified point. - - @discussion This method takes into account the restrict of emoji, line break - character, binding text and text affinity. - - @param point A point in the container. - @return A text position, or nil if not found. - */ -- (nullable ASTextPosition *)closestPositionToPoint:(CGPoint)point; - -/** - Returns the new position when moving selection grabber in text view. - - @discussion There are two grabber in the text selection period, user can only - move one grabber at the same time. - - @param point A point in the container. - @param oldPosition The old text position for the moving grabber. - @param otherPosition The other position in text selection view. - - @return A text position, or nil if not found. - */ -- (nullable ASTextPosition *)positionForPoint:(CGPoint)point - oldPosition:(ASTextPosition *)oldPosition - otherPosition:(ASTextPosition *)otherPosition; - -/** - Returns the character or range of characters that is at a given point in the container. - If there is no text at the point, returns nil. - - @discussion This method takes into account the restrict of emoji, line break - character, binding text and text affinity. - - @param point A point in the container. - @return An object representing a range that encloses a character (or characters) - at point. Or nil if not found. - */ -- (nullable ASTextRange *)textRangeAtPoint:(CGPoint)point; - -/** - Returns the closest character or range of characters that is at a given point in - the container. - - @discussion This method takes into account the restrict of emoji, line break - character, binding text and text affinity. - - @param point A point in the container. - @return An object representing a range that encloses a character (or characters) - at point. Or nil if not found. - */ -- (nullable ASTextRange *)closestTextRangeAtPoint:(CGPoint)point; - -/** - If the position is inside an emoji, composed character sequences, line break '\\r\\n' - or custom binding range, then returns the range by extend the position. Otherwise, - returns a zero length range from the position. - - @param position A text-position object that identifies a location in layout. - - @return A text-range object that extend the position. Or nil if an error occurs - */ -- (nullable ASTextRange *)textRangeByExtendingPosition:(ASTextPosition *)position; - -/** - Returns a text range at a given offset in a specified direction from another - text position to its farthest extent in a certain direction of layout. - - @param position A text-position object that identifies a location in layout. - @param direction A constant that indicates a direction of layout (right, left, up, down). - @param offset A character offset from position. - - @return A text-range object that represents the distance from position to the - farthest extent in direction. Or nil if an error occurs. - */ -- (nullable ASTextRange *)textRangeByExtendingPosition:(ASTextPosition *)position - inDirection:(UITextLayoutDirection)direction - offset:(NSInteger)offset; - -/** - Returns the line index for a given text position. - - @discussion This method takes into account the text affinity. - - @param position A text-position object that identifies a location in layout. - @return The line index, or NSNotFound if not found. - */ -- (NSUInteger)lineIndexForPosition:(ASTextPosition *)position; - -/** - Returns the baseline position for a given text position. - - @param position An object that identifies a location in the layout. - @return The baseline position for text, or CGPointZero if not found. - */ -- (CGPoint)linePositionForPosition:(ASTextPosition *)position; - -/** - Returns a rectangle used to draw the caret at a given insertion point. - - @param position An object that identifies a location in the layout. - @return A rectangle that defines the area for drawing the caret. The width is - always zero in normal container, the height is always zero in vertical form container. - If not found, it returns CGRectNull. - */ -- (CGRect)caretRectForPosition:(ASTextPosition *)position; - -/** - Returns the first rectangle that encloses a range of text in the layout. - - @param range An object that represents a range of text in layout. - - @return The first rectangle in a range of text. You might use this rectangle to - draw a correction rectangle. The "first" in the name refers the rectangle - enclosing the first line when the range encompasses multiple lines of text. - If not found, it returns CGRectNull. - */ -- (CGRect)firstRectForRange:(ASTextRange *)range; - -/** - Returns the rectangle union that encloses a range of text in the layout. - - @param range An object that represents a range of text in layout. - - @return A rectangle that defines the area than encloses the range. - If not found, it returns CGRectNull. - */ -- (CGRect)rectForRange:(ASTextRange *)range; - -/** - Returns an array of selection rects corresponding to the range of text. - The start and end rect can be used to show grabber. - - @param range An object representing a range in text. - @return An array of `ASTextSelectionRect` objects that encompass the selection. - If not found, the array is empty. - */ -- (NSArray *)selectionRectsForRange:(ASTextRange *)range; - -/** - Returns an array of selection rects corresponding to the range of text. - - @param range An object representing a range in text. - @return An array of `ASTextSelectionRect` objects that encompass the selection. - If not found, the array is empty. - */ -- (NSArray *)selectionRectsWithoutStartAndEndForRange:(ASTextRange *)range; - -/** - Returns the start and end selection rects corresponding to the range of text. - The start and end rect can be used to show grabber. - - @param range An object representing a range in text. - @return An array of `ASTextSelectionRect` objects contains the start and end to - the selection. If not found, the array is empty. - */ -- (NSArray *)selectionRectsWithOnlyStartAndEndForRange:(ASTextRange *)range; - - -#pragma mark - Draw text layout -///============================================================================= -/// @name Draw text layout -///============================================================================= - -/** - Draw the layout and show the attachments. - - @discussion If the `view` parameter is not nil, then the attachment views will - add to this `view`, and if the `layer` parameter is not nil, then the attachment - layers will add to this `layer`. - - @warning This method should be called on main thread if `view` or `layer` parameter - is not nil and there's UIView or CALayer attachments in layout. - Otherwise, it can be called on any thread. - - @param context The draw context. Pass nil to avoid text and image drawing. - @param size The context size. - @param point The point at which to draw the layout. - @param view The attachment views will add to this view. - @param layer The attachment layers will add to this layer. - @param debug The debug option. Pass nil to avoid debug drawing. - @param cancel The cancel checker block. It will be called in drawing progress. - If it returns YES, the further draw progress will be canceled. - Pass nil to ignore this feature. - */ -- (void)drawInContext:(nullable CGContextRef)context - size:(CGSize)size - point:(CGPoint)point - view:(nullable UIView *)view - layer:(nullable CALayer *)layer - debug:(nullable ASTextDebugOption *)debug - cancel:(nullable BOOL (^)(void))cancel; - -/** - Draw the layout text and image (without view or layer attachments). - - @discussion This method is thread safe and can be called on any thread. - - @param context The draw context. Pass nil to avoid text and image drawing. - @param size The context size. - @param debug The debug option. Pass nil to avoid debug drawing. - */ -- (void)drawInContext:(nullable CGContextRef)context - size:(CGSize)size - debug:(nullable ASTextDebugOption *)debug; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASTextLayout.mm b/submodules/AsyncDisplayKit/Source/ASTextLayout.mm deleted file mode 100644 index 9259f625b3..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextLayout.mm +++ /dev/null @@ -1,3485 +0,0 @@ -// -// ASTextLayout.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 - -#import -#import -#import -#import -#import -#import "Private/ASInternalHelpers.h" - -#import - -const CGSize ASTextContainerMaxSize = (CGSize){0x100000, 0x100000}; - -typedef struct { - CGFloat head; - CGFloat foot; -} ASRowEdge; - -static inline CGSize ASTextClipCGSize(CGSize size) { - if (size.width > ASTextContainerMaxSize.width) size.width = ASTextContainerMaxSize.width; - if (size.height > ASTextContainerMaxSize.height) size.height = ASTextContainerMaxSize.height; - return size; -} - -static inline UIEdgeInsets UIEdgeInsetRotateVertical(UIEdgeInsets insets) { - UIEdgeInsets one; - one.top = insets.left; - one.left = insets.bottom; - one.bottom = insets.right; - one.right = insets.top; - return one; -} - -/** - Sometimes CoreText may convert CGColor to UIColor for `kCTForegroundColorAttributeName` - attribute in iOS7. This should be a bug of CoreText, and may cause crash. Here's a workaround. - */ -static CGColorRef ASTextGetCGColor(CGColorRef color) { - static UIColor *defaultColor; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - defaultColor = [UIColor blackColor]; - }); - if (!color) return defaultColor.CGColor; - if ([((__bridge NSObject *)color) respondsToSelector:@selector(CGColor)]) { - return ((__bridge UIColor *)color).CGColor; - } - return color; -} - -@implementation ASTextLinePositionSimpleModifier -- (void)modifyLines:(NSArray *)lines fromText:(NSAttributedString *)text inContainer:(ASTextContainer *)container { - if (container.verticalForm) { - for (NSUInteger i = 0, max = lines.count; i < max; i++) { - ASTextLine *line = lines[i]; - CGPoint pos = line.position; - pos.x = container.size.width - container.insets.right - line.row * _fixedLineHeight - _fixedLineHeight * 0.9; - line.position = pos; - } - } else { - for (NSUInteger i = 0, max = lines.count; i < max; i++) { - ASTextLine *line = lines[i]; - CGPoint pos = line.position; - pos.y = line.row * _fixedLineHeight + _fixedLineHeight * 0.9 + container.insets.top; - line.position = pos; - } - } -} - -- (id)copyWithZone:(NSZone *)zone { - ASTextLinePositionSimpleModifier *one = [self.class new]; - one.fixedLineHeight = _fixedLineHeight; - return one; -} -@end - - -@implementation ASTextContainer { - @package - BOOL _readonly; ///< used only in ASTextLayout.implementation - dispatch_semaphore_t _lock; - - CGSize _size; - UIEdgeInsets _insets; - UIBezierPath *_path; - NSArray *_exclusionPaths; - BOOL _pathFillEvenOdd; - CGFloat _pathLineWidth; - BOOL _verticalForm; - NSUInteger _maximumNumberOfRows; - ASTextTruncationType _truncationType; - NSAttributedString *_truncationToken; - id _linePositionModifier; -} - -- (NSString *)description -{ - return [NSString - stringWithFormat:@"immutable: %@, insets: %@, size: %@", self->_readonly ? @"YES" : @"NO", - NSStringFromUIEdgeInsets(self->_insets), NSStringFromCGSize(self->_size)]; -} - -+ (instancetype)containerWithSize:(CGSize)size NS_RETURNS_RETAINED { - return [self containerWithSize:size insets:UIEdgeInsetsZero]; -} - -+ (instancetype)containerWithSize:(CGSize)size insets:(UIEdgeInsets)insets NS_RETURNS_RETAINED { - ASTextContainer *one = [self new]; - one.size = ASTextClipCGSize(size); - one.insets = insets; - return one; -} - -+ (instancetype)containerWithPath:(UIBezierPath *)path NS_RETURNS_RETAINED { - ASTextContainer *one = [self new]; - one.path = path; - return one; -} - -- (instancetype)init { - self = [super init]; - if (!self) return nil; - _lock = dispatch_semaphore_create(1); - _pathFillEvenOdd = YES; - return self; -} - -- (id)copyForced:(BOOL)forceCopy -{ - dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); - if (_readonly && !forceCopy) { - dispatch_semaphore_signal(_lock); - return self; - } - - ASTextContainer *one = [self.class new]; - one->_size = _size; - one->_insets = _insets; - one->_path = _path; - one->_exclusionPaths = [_exclusionPaths copy]; - one->_pathFillEvenOdd = _pathFillEvenOdd; - one->_pathLineWidth = _pathLineWidth; - one->_verticalForm = _verticalForm; - one->_maximumNumberOfRows = _maximumNumberOfRows; - one->_truncationType = _truncationType; - one->_truncationToken = [_truncationToken copy]; - one->_linePositionModifier = [(NSObject *)_linePositionModifier copy]; - dispatch_semaphore_signal(_lock); - return one; -} - -- (id)copyWithZone:(NSZone *)zone { - return [self copyForced:NO]; -} - -- (id)mutableCopyWithZone:(NSZone *)zone { - return [self copyForced:YES]; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:[NSValue valueWithCGSize:_size] forKey:@"size"]; - [aCoder encodeObject:[NSValue valueWithUIEdgeInsets:_insets] forKey:@"insets"]; - [aCoder encodeObject:_path forKey:@"path"]; - [aCoder encodeObject:_exclusionPaths forKey:@"exclusionPaths"]; - [aCoder encodeBool:_pathFillEvenOdd forKey:@"pathFillEvenOdd"]; - [aCoder encodeDouble:_pathLineWidth forKey:@"pathLineWidth"]; - [aCoder encodeBool:_verticalForm forKey:@"verticalForm"]; - [aCoder encodeInteger:_maximumNumberOfRows forKey:@"maximumNumberOfRows"]; - [aCoder encodeInteger:_truncationType forKey:@"truncationType"]; - [aCoder encodeObject:_truncationToken forKey:@"truncationToken"]; - if ([_linePositionModifier respondsToSelector:@selector(encodeWithCoder:)] && - [_linePositionModifier respondsToSelector:@selector(initWithCoder:)]) { - [aCoder encodeObject:_linePositionModifier forKey:@"linePositionModifier"]; - } -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - self = [self init]; - _size = ((NSValue *)[aDecoder decodeObjectForKey:@"size"]).CGSizeValue; - _insets = ((NSValue *)[aDecoder decodeObjectForKey:@"insets"]).UIEdgeInsetsValue; - _path = [aDecoder decodeObjectForKey:@"path"]; - _exclusionPaths = [aDecoder decodeObjectForKey:@"exclusionPaths"]; - _pathFillEvenOdd = [aDecoder decodeBoolForKey:@"pathFillEvenOdd"]; - _pathLineWidth = [aDecoder decodeDoubleForKey:@"pathLineWidth"]; - _verticalForm = [aDecoder decodeBoolForKey:@"verticalForm"]; - _maximumNumberOfRows = [aDecoder decodeIntegerForKey:@"maximumNumberOfRows"]; - _truncationType = (ASTextTruncationType)[aDecoder decodeIntegerForKey:@"truncationType"]; - _truncationToken = [aDecoder decodeObjectForKey:@"truncationToken"]; - _linePositionModifier = [aDecoder decodeObjectForKey:@"linePositionModifier"]; - return self; -} - -- (void)makeImmutable -{ - dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); - _readonly = YES; - dispatch_semaphore_signal(_lock); -} - -#define Getter(...) \ -dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \ -__VA_ARGS__; \ -dispatch_semaphore_signal(_lock); - -#define Setter(...) \ -dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \ -if (__builtin_expect(_readonly, NO)) { \ - ASDisplayNodeFailAssert(@"Attempt to modify immutable text container."); \ - dispatch_semaphore_signal(_lock); \ - return; \ -} \ -__VA_ARGS__; \ -dispatch_semaphore_signal(_lock); - -- (CGSize)size { - Getter(CGSize size = _size) return size; -} - -- (void)setSize:(CGSize)size { - Setter(if(!_path) _size = ASTextClipCGSize(size)); -} - -- (UIEdgeInsets)insets { - Getter(UIEdgeInsets insets = _insets) return insets; -} - -- (void)setInsets:(UIEdgeInsets)insets { - Setter(if(!_path){ - if (insets.top < 0) insets.top = 0; - if (insets.left < 0) insets.left = 0; - if (insets.bottom < 0) insets.bottom = 0; - if (insets.right < 0) insets.right = 0; - _insets = insets; - }); -} - -- (UIBezierPath *)path { - Getter(UIBezierPath *path = _path) return path; -} - -- (void)setPath:(UIBezierPath *)path { - Setter( - _path = path.copy; - if (_path) { - CGRect bounds = _path.bounds; - CGSize size = bounds.size; - UIEdgeInsets insets = UIEdgeInsetsZero; - if (bounds.origin.x < 0) size.width += bounds.origin.x; - if (bounds.origin.x > 0) insets.left = bounds.origin.x; - if (bounds.origin.y < 0) size.height += bounds.origin.y; - if (bounds.origin.y > 0) insets.top = bounds.origin.y; - _size = size; - _insets = insets; - } - ); -} - -- (NSArray *)exclusionPaths { - Getter(NSArray *paths = _exclusionPaths) return paths; -} - -- (void)setExclusionPaths:(NSArray *)exclusionPaths { - Setter(_exclusionPaths = exclusionPaths.copy); -} - -- (BOOL)isPathFillEvenOdd { - Getter(BOOL is = _pathFillEvenOdd) return is; -} - -- (void)setPathFillEvenOdd:(BOOL)pathFillEvenOdd { - Setter(_pathFillEvenOdd = pathFillEvenOdd); -} - -- (CGFloat)pathLineWidth { - Getter(CGFloat width = _pathLineWidth) return width; -} - -- (void)setPathLineWidth:(CGFloat)pathLineWidth { - Setter(_pathLineWidth = pathLineWidth); -} - -- (BOOL)isVerticalForm { - Getter(BOOL v = _verticalForm) return v; -} - -- (void)setVerticalForm:(BOOL)verticalForm { - Setter(_verticalForm = verticalForm); -} - -- (NSUInteger)maximumNumberOfRows { - Getter(NSUInteger num = _maximumNumberOfRows) return num; -} - -- (void)setMaximumNumberOfRows:(NSUInteger)maximumNumberOfRows { - Setter(_maximumNumberOfRows = maximumNumberOfRows); -} - -- (ASTextTruncationType)truncationType { - Getter(ASTextTruncationType type = _truncationType) return type; -} - -- (void)setTruncationType:(ASTextTruncationType)truncationType { - Setter(_truncationType = truncationType); -} - -- (NSAttributedString *)truncationToken { - Getter(NSAttributedString *token = _truncationToken) return token; -} - -- (void)setTruncationToken:(NSAttributedString *)truncationToken { - Setter(_truncationToken = truncationToken.copy); -} - -- (void)setLinePositionModifier:(id)linePositionModifier { - Setter(_linePositionModifier = [(NSObject *)linePositionModifier copy]); -} - -- (id)linePositionModifier { - Getter(id m = _linePositionModifier) return m; -} - -#undef Getter -#undef Setter -@end - - - - -@interface ASTextLayout () - -@property (nonatomic) ASTextContainer *container; -@property (nonatomic) NSAttributedString *text; -@property (nonatomic) NSRange range; - -@property (nonatomic) CTFrameRef frame; -@property (nonatomic) NSArray *lines; -@property (nonatomic) ASTextLine *truncatedLine; -@property (nonatomic) NSArray *attachments; -@property (nonatomic) NSArray *attachmentRanges; -@property (nonatomic) NSArray *attachmentRects; -@property (nonatomic) NSSet *attachmentContentsSet; -@property (nonatomic) NSUInteger rowCount; -@property (nonatomic) NSRange visibleRange; -@property (nonatomic) CGRect textBoundingRect; -@property (nonatomic) CGSize textBoundingSize; - -@property (nonatomic) BOOL containsHighlight; -@property (nonatomic) BOOL needDrawBlockBorder; -@property (nonatomic) BOOL needDrawBackgroundBorder; -@property (nonatomic) BOOL needDrawShadow; -@property (nonatomic) BOOL needDrawUnderline; -@property (nonatomic) BOOL needDrawText; -@property (nonatomic) BOOL needDrawAttachment; -@property (nonatomic) BOOL needDrawInnerShadow; -@property (nonatomic) BOOL needDrawStrikethrough; -@property (nonatomic) BOOL needDrawBorder; - -@property (nonatomic) NSUInteger *lineRowsIndex; -@property (nonatomic) ASRowEdge *lineRowsEdge; ///< top-left origin - -@end - - - -@implementation ASTextLayout - -#pragma mark - Layout - -- (instancetype)_init { - self = [super init]; - return self; -} - -- (NSString *)description -{ - return [NSString stringWithFormat:@"lines: %ld, visibleRange:%@, textBoundingRect:%@", - [self.lines count], - NSStringFromRange(self.visibleRange), - NSStringFromCGRect(self.textBoundingRect)]; -} - -+ (ASTextLayout *)layoutWithContainerSize:(CGSize)size text:(NSAttributedString *)text { - ASTextContainer *container = [ASTextContainer containerWithSize:size]; - return [self layoutWithContainer:container text:text]; -} - -+ (ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttributedString *)text { - return [self layoutWithContainer:container text:text range:NSMakeRange(0, text.length)]; -} - -+ (ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range { - ASTextLayout *layout = NULL; - CGPathRef cgPath = nil; - CGRect cgPathBox = {0}; - BOOL isVerticalForm = NO; - BOOL rowMaySeparated = NO; - NSMutableDictionary *frameAttrs = nil; - CTFramesetterRef ctSetter = NULL; - CTFrameRef ctFrame = NULL; - CFArrayRef ctLines = nil; - CGPoint *lineOrigins = NULL; - NSUInteger lineCount = 0; - NSMutableArray *lines = nil; - NSMutableArray *attachments = nil; - NSMutableArray *attachmentRanges = nil; - NSMutableArray *attachmentRects = nil; - NSMutableSet *attachmentContentsSet = nil; - BOOL needTruncation = NO; - NSAttributedString *truncationToken = nil; - ASTextLine *truncatedLine = nil; - ASRowEdge *lineRowsEdge = NULL; - NSUInteger *lineRowsIndex = NULL; - NSRange visibleRange; - NSUInteger maximumNumberOfRows = 0; - BOOL constraintSizeIsExtended = NO; - CGRect constraintRectBeforeExtended = {0}; -#define FAIL_AND_RETURN {\ - if (cgPath) CFRelease(cgPath); \ - if (ctSetter) CFRelease(ctSetter); \ - if (ctFrame) CFRelease(ctFrame); \ - if (lineOrigins) free(lineOrigins); \ - if (lineRowsEdge) free(lineRowsEdge); \ - if (lineRowsIndex) free(lineRowsIndex); \ - return nil; } - - container = [container copy]; - if (!text || !container) return nil; - if (range.location + range.length > text.length) return nil; - [container makeImmutable]; - maximumNumberOfRows = container.maximumNumberOfRows; - - // It may use larger constraint size when create CTFrame with - // CTFramesetterCreateFrame in iOS 10. - BOOL needFixLayoutSizeBug = AS_AT_LEAST_IOS10; - - layout = [[ASTextLayout alloc] _init]; - layout.text = text; - layout.container = container; - layout.range = range; - isVerticalForm = container.verticalForm; - - // set cgPath and cgPathBox - if (container.path == nil && container.exclusionPaths.count == 0) { - if (container.size.width <= 0 || container.size.height <= 0) FAIL_AND_RETURN - CGRect rect = (CGRect) {CGPointZero, container.size }; - if (needFixLayoutSizeBug) { - constraintSizeIsExtended = YES; - constraintRectBeforeExtended = UIEdgeInsetsInsetRect(rect, container.insets); - constraintRectBeforeExtended = CGRectStandardize(constraintRectBeforeExtended); - if (container.isVerticalForm) { - rect.size.width = ASTextContainerMaxSize.width; - } else { - rect.size.height = ASTextContainerMaxSize.height; - } - } - rect = UIEdgeInsetsInsetRect(rect, container.insets); - rect = CGRectStandardize(rect); - cgPathBox = rect; - rect = CGRectApplyAffineTransform(rect, CGAffineTransformMakeScale(1, -1)); - cgPath = CGPathCreateWithRect(rect, NULL); // let CGPathIsRect() returns true - } else if (container.path && CGPathIsRect(container.path.CGPath, &cgPathBox) && container.exclusionPaths.count == 0) { - CGRect rect = CGRectApplyAffineTransform(cgPathBox, CGAffineTransformMakeScale(1, -1)); - cgPath = CGPathCreateWithRect(rect, NULL); // let CGPathIsRect() returns true - } else { - rowMaySeparated = YES; - CGMutablePathRef path = NULL; - if (container.path) { - path = CGPathCreateMutableCopy(container.path.CGPath); - } else { - CGRect rect = (CGRect) {CGPointZero, container.size }; - rect = UIEdgeInsetsInsetRect(rect, container.insets); - CGPathRef rectPath = CGPathCreateWithRect(rect, NULL); - if (rectPath) { - path = CGPathCreateMutableCopy(rectPath); - CGPathRelease(rectPath); - } - } - if (path) { - [layout.container.exclusionPaths enumerateObjectsUsingBlock: ^(UIBezierPath *onePath, NSUInteger idx, BOOL *stop) { - CGPathAddPath(path, NULL, onePath.CGPath); - }]; - - cgPathBox = CGPathGetPathBoundingBox(path); - CGAffineTransform trans = CGAffineTransformMakeScale(1, -1); - CGMutablePathRef transPath = CGPathCreateMutableCopyByTransformingPath(path, &trans); - CGPathRelease(path); - path = transPath; - } - cgPath = path; - } - if (!cgPath) FAIL_AND_RETURN - - // frame setter config - frameAttrs = [[NSMutableDictionary alloc] init]; - if (container.isPathFillEvenOdd == NO) { - frameAttrs[(id)kCTFramePathFillRuleAttributeName] = @(kCTFramePathFillWindingNumber); - } - if (container.pathLineWidth > 0) { - frameAttrs[(id)kCTFramePathWidthAttributeName] = @(container.pathLineWidth); - } - if (container.isVerticalForm == YES) { - frameAttrs[(id)kCTFrameProgressionAttributeName] = @(kCTFrameProgressionRightToLeft); - } - - /* - * Framesetter cache. - * Framesetters can only be used by one thread at a time. - * Create a CFSet with no callbacks (raw pointers) to keep track of which - * framesetters are in use on other threads. If the one for our string is already in use, - * just create a new one. This should be pretty rare. - */ - static pthread_mutex_t busyFramesettersLock = PTHREAD_MUTEX_INITIALIZER; - static NSCache *framesetterCache; - static CFMutableSetRef busyFramesetters; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - if (ASActivateExperimentalFeature(ASExperimentalFramesetterCache)) { - framesetterCache = [[NSCache alloc] init]; - framesetterCache.name = @"org.TextureGroup.Texture.framesetterCache"; - busyFramesetters = CFSetCreateMutable(NULL, 0, NULL); - } - }); - - BOOL haveCached = NO, useCached = NO; - if (framesetterCache) { - // Check if there's one in the cache. - ctSetter = (__bridge_retained CTFramesetterRef)[framesetterCache objectForKey:text]; - - if (ctSetter) { - haveCached = YES; - - // Check-and-set busy on the cached one. - pthread_mutex_lock(&busyFramesettersLock); - BOOL busy = CFSetContainsValue(busyFramesetters, ctSetter); - if (!busy) { - CFSetAddValue(busyFramesetters, ctSetter); - useCached = YES; - } - pthread_mutex_unlock(&busyFramesettersLock); - - // Release if it was busy. - if (busy) { - CFRelease(ctSetter); - ctSetter = NULL; - } - } - } - - // Create a framesetter if needed. - if (!ctSetter) { - ctSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)text); - } - - if (!ctSetter) FAIL_AND_RETURN - ctFrame = CTFramesetterCreateFrame(ctSetter, ASTextCFRangeFromNSRange(range), cgPath, (CFDictionaryRef)frameAttrs); - - // Return to cache. - if (framesetterCache) { - if (useCached) { - // If reused: mark available. - pthread_mutex_lock(&busyFramesettersLock); - CFSetRemoveValue(busyFramesetters, ctSetter); - pthread_mutex_unlock(&busyFramesettersLock); - } else if (!haveCached) { - // If first framesetter, add to cache. - [framesetterCache setObject:(__bridge id)ctSetter forKey:text]; - } - } - - if (!ctFrame) FAIL_AND_RETURN - lines = [NSMutableArray new]; - ctLines = CTFrameGetLines(ctFrame); - lineCount = CFArrayGetCount(ctLines); - if (lineCount > 0) { - lineOrigins = (CGPoint *)malloc(lineCount * sizeof(CGPoint)); - if (lineOrigins == NULL) FAIL_AND_RETURN - CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, lineCount), lineOrigins); - } - - CGRect textBoundingRect = CGRectZero; - CGSize textBoundingSize = CGSizeZero; - NSInteger rowIdx = -1; - NSUInteger rowCount = 0; - CGRect lastRect = CGRectMake(0, -FLT_MAX, 0, 0); - CGPoint lastPosition = CGPointMake(0, -FLT_MAX); - if (isVerticalForm) { - lastRect = CGRectMake(FLT_MAX, 0, 0, 0); - lastPosition = CGPointMake(FLT_MAX, 0); - } - - // calculate line frame - NSUInteger lineCurrentIdx = 0; - BOOL measuringBeyondConstraints = NO; - for (NSUInteger i = 0; i < lineCount; i++) { - CTLineRef ctLine = (CTLineRef)CFArrayGetValueAtIndex(ctLines, i); - CFArrayRef ctRuns = CTLineGetGlyphRuns(ctLine); - if (!ctRuns || CFArrayGetCount(ctRuns) == 0) continue; - - // CoreText coordinate system - CGPoint ctLineOrigin = lineOrigins[i]; - - // UIKit coordinate system - CGPoint position; - position.x = cgPathBox.origin.x + ctLineOrigin.x; - position.y = cgPathBox.size.height + cgPathBox.origin.y - ctLineOrigin.y; - - ASTextLine *line = [ASTextLine lineWithCTLine:ctLine position:position vertical:isVerticalForm]; - - [lines addObject:line]; - } - - // Give user a chance to modify the line's position. - [container.linePositionModifier modifyLines:lines fromText:text inContainer:container]; - - BOOL first = YES; - for (ASTextLine *line in lines) { - CGPoint position = line.position; - CGRect rect = line.bounds; - if (constraintSizeIsExtended) { - if (isVerticalForm) { - if (rect.origin.x + rect.size.width > - constraintRectBeforeExtended.origin.x + - constraintRectBeforeExtended.size.width) { - measuringBeyondConstraints = YES; - } - } else { - if (rect.origin.y + rect.size.height > - constraintRectBeforeExtended.origin.y + - constraintRectBeforeExtended.size.height) { - measuringBeyondConstraints = YES; - } - } - } - - BOOL newRow = !measuringBeyondConstraints; - if (newRow && rowMaySeparated && position.x != lastPosition.x) { - if (isVerticalForm) { - if (rect.size.width > lastRect.size.width) { - if (rect.origin.x > lastPosition.x && lastPosition.x > rect.origin.x - rect.size.width) newRow = NO; - } else { - if (lastRect.origin.x > position.x && position.x > lastRect.origin.x - lastRect.size.width) newRow = NO; - } - } else { - if (rect.size.height > lastRect.size.height) { - if (rect.origin.y < lastPosition.y && lastPosition.y < rect.origin.y + rect.size.height) newRow = NO; - } else { - if (lastRect.origin.y < position.y && position.y < lastRect.origin.y + lastRect.size.height) newRow = NO; - } - } - } - - if (newRow) rowIdx++; - lastRect = rect; - lastPosition = position; - - line.index = lineCurrentIdx; - line.row = rowIdx; - - rowCount = rowIdx + 1; - lineCurrentIdx ++; - - if (first) { - first = NO; - textBoundingRect = rect; - } else if (!measuringBeyondConstraints) { - if (maximumNumberOfRows == 0 || rowIdx < maximumNumberOfRows) { - textBoundingRect = CGRectUnion(textBoundingRect, rect); - } - } - } - - { - NSMutableArray *removedLines = [NSMutableArray new]; - if (rowCount > 0) { - if (maximumNumberOfRows > 0) { - if (rowCount > maximumNumberOfRows) { - needTruncation = YES; - rowCount = maximumNumberOfRows; - do { - ASTextLine *line = lines.lastObject; - if (!line) break; - if (line.row < rowCount) break; // we have removed down to an allowed # of lines now - [lines removeLastObject]; - [removedLines addObject:line]; - } while (1); - } - } - ASTextLine *lastLine = rowCount < lines.count ? lines[rowCount - 1] : lines.lastObject; - if (!needTruncation && lastLine.range.location + lastLine.range.length < text.length) { - needTruncation = YES; - while (lines.count > rowCount) { - ASTextLine *line = lines.lastObject; - [lines removeLastObject]; - [removedLines addObject:line]; - } - } - - lineRowsEdge = (ASRowEdge *) calloc(rowCount, sizeof(ASRowEdge)); - if (lineRowsEdge == NULL) FAIL_AND_RETURN - lineRowsIndex = (NSUInteger *) calloc(rowCount, sizeof(NSUInteger)); - if (lineRowsIndex == NULL) FAIL_AND_RETURN - NSInteger lastRowIdx = -1; - CGFloat lastHead = 0; - CGFloat lastFoot = 0; - for (NSUInteger i = 0, max = lines.count; i < max; i++) { - ASTextLine *line = lines[i]; - CGRect rect = line.bounds; - if ((NSInteger) line.row != lastRowIdx) { - if (lastRowIdx >= 0) { - lineRowsEdge[lastRowIdx] = (ASRowEdge) {.head = lastHead, .foot = lastFoot}; - } - lastRowIdx = line.row; - lineRowsIndex[lastRowIdx] = i; - if (isVerticalForm) { - lastHead = rect.origin.x + rect.size.width; - lastFoot = lastHead - rect.size.width; - } else { - lastHead = rect.origin.y; - lastFoot = lastHead + rect.size.height; - } - } else { - if (isVerticalForm) { - lastHead = MAX(lastHead, rect.origin.x + rect.size.width); - lastFoot = MIN(lastFoot, rect.origin.x); - } else { - lastHead = MIN(lastHead, rect.origin.y); - lastFoot = MAX(lastFoot, rect.origin.y + rect.size.height); - } - } - } - lineRowsEdge[lastRowIdx] = (ASRowEdge) {.head = lastHead, .foot = lastFoot}; - - for (NSUInteger i = 1; i < rowCount; i++) { - ASRowEdge v0 = lineRowsEdge[i - 1]; - ASRowEdge v1 = lineRowsEdge[i]; - lineRowsEdge[i - 1].foot = lineRowsEdge[i].head = (v0.foot + v1.head) * 0.5; - } - } - - { // calculate bounding size - CGRect rect = textBoundingRect; - if (container.path) { - if (container.pathLineWidth > 0) { - CGFloat inset = container.pathLineWidth / 2; - rect = CGRectInset(rect, -inset, -inset); - } - } else { - rect = UIEdgeInsetsInsetRect(rect, ASTextUIEdgeInsetsInvert(container.insets)); - } - rect = CGRectStandardize(rect); - CGSize size = rect.size; - if (container.verticalForm) { - size.width += container.size.width - (rect.origin.x + rect.size.width); - } else { - size.width += rect.origin.x; - } - size.height += rect.origin.y; - if (size.width < 0) size.width = 0; - if (size.height < 0) size.height = 0; - size.width = ceil(size.width); - size.height = ceil(size.height); - textBoundingSize = size; - } - - visibleRange = ASTextNSRangeFromCFRange(CTFrameGetVisibleStringRange(ctFrame)); - if (needTruncation) { - ASTextLine *lastLine = lines.lastObject; - NSRange lastRange = lastLine.range; - visibleRange.length = lastRange.location + lastRange.length - visibleRange.location; - - // create truncated line - if (container.truncationType != ASTextTruncationTypeNone) { - CTLineRef truncationTokenLine = NULL; - if (container.truncationToken) { - truncationToken = container.truncationToken; - truncationTokenLine = CTLineCreateWithAttributedString((CFAttributedStringRef) truncationToken); - } else { - CFArrayRef runs = CTLineGetGlyphRuns(lastLine.CTLine); - NSUInteger runCount = CFArrayGetCount(runs); - NSMutableDictionary *attrs = nil; - if (runCount > 0) { - CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex(runs, runCount - 1); - attrs = (id) CTRunGetAttributes(run); - attrs = attrs ? attrs.mutableCopy : [NSMutableArray new]; - [attrs removeObjectsForKeys:[NSMutableAttributedString as_allDiscontinuousAttributeKeys]]; - CTFontRef font = (__bridge CTFontRef) attrs[(id) kCTFontAttributeName]; - CGFloat fontSize = font ? CTFontGetSize(font) : 12.0; - UIFont *uiFont = [UIFont systemFontOfSize:fontSize * 0.9]; - if (uiFont) { - font = CTFontCreateWithName((__bridge CFStringRef) uiFont.fontName, uiFont.pointSize, NULL); - } else { - font = NULL; - } - if (font) { - attrs[(id) kCTFontAttributeName] = (__bridge id) (font); - uiFont = nil; - CFRelease(font); - } - CGColorRef color = (__bridge CGColorRef) (attrs[(id) kCTForegroundColorAttributeName]); - if (color && CFGetTypeID(color) == CGColorGetTypeID() && CGColorGetAlpha(color) == 0) { - // ignore clear color - [attrs removeObjectForKey:(id) kCTForegroundColorAttributeName]; - } - if (!attrs) attrs = [NSMutableDictionary new]; - } - truncationToken = [[NSAttributedString alloc] initWithString:ASTextTruncationToken attributes:attrs]; - truncationTokenLine = CTLineCreateWithAttributedString((CFAttributedStringRef) truncationToken); - } - if (truncationTokenLine) { - CTLineTruncationType type = kCTLineTruncationEnd; - if (container.truncationType == ASTextTruncationTypeStart) { - type = kCTLineTruncationStart; - } else if (container.truncationType == ASTextTruncationTypeMiddle) { - type = kCTLineTruncationMiddle; - } - NSMutableAttributedString *lastLineText = [text attributedSubstringFromRange:lastLine.range].mutableCopy; - CGFloat truncatedWidth = lastLine.width; - CGFloat atLeastOneLine = lastLine.width; - CGRect cgPathRect = CGRectZero; - if (CGPathIsRect(cgPath, &cgPathRect)) { - if (isVerticalForm) { - truncatedWidth = cgPathRect.size.height; - } else { - truncatedWidth = cgPathRect.size.width; - } - } - int i = 0; - if (type != kCTLineTruncationStart) { // Middle or End/Tail wants to collect some text (at least one line's - // worth) preceding the truncated content, with which to construct a "truncated line". - i = (int)removedLines.count - 1; - while (atLeastOneLine < truncatedWidth && i >= 0) { - if (lastLineText.length > 0 && [lastLineText.string characterAtIndex:lastLineText.string.length - 1] == '\n') { // Explicit newlines are always "long enough". - [lastLineText deleteCharactersInRange:NSMakeRange(lastLineText.string.length - 1, 1)]; - break; - } - [lastLineText appendAttributedString:[text attributedSubstringFromRange:removedLines[i].range]]; - atLeastOneLine += removedLines[i--].width; - } - [lastLineText appendAttributedString:truncationToken]; - } - if (type != kCTLineTruncationEnd && removedLines.count > 0) { // Middle or Start/Head wants to collect some - // text following the truncated content. - i = 0; - atLeastOneLine = removedLines[i].width; - while (atLeastOneLine < truncatedWidth && i < removedLines.count) { - atLeastOneLine += removedLines[i++].width; - } - for (i--; i >= 0; i--) { - NSAttributedString *nextLine = [text attributedSubstringFromRange:removedLines[i].range]; - if ([nextLine.string characterAtIndex:nextLine.string.length - 1] == '\n') { // Explicit newlines are always "long enough". - lastLineText = [NSMutableAttributedString new]; - } else { - [lastLineText appendAttributedString:nextLine]; - } - } - if (type == kCTLineTruncationStart) { - [lastLineText insertAttributedString:truncationToken atIndex:0]; - } - } - - CTLineRef ctLastLineExtend = CTLineCreateWithAttributedString((CFAttributedStringRef) lastLineText); - if (ctLastLineExtend) { - CTLineRef ctTruncatedLine = CTLineCreateTruncatedLine(ctLastLineExtend, truncatedWidth, type, truncationTokenLine); - CFRelease(ctLastLineExtend); - if (ctTruncatedLine) { - truncatedLine = [ASTextLine lineWithCTLine:ctTruncatedLine position:lastLine.position vertical:isVerticalForm]; - truncatedLine.index = lastLine.index; - truncatedLine.row = lastLine.row; - CFRelease(ctTruncatedLine); - } - } - CFRelease(truncationTokenLine); - } - } - } - } - - if (isVerticalForm) { - NSCharacterSet *rotateCharset = ASTextVerticalFormRotateCharacterSet(); - NSCharacterSet *rotateMoveCharset = ASTextVerticalFormRotateAndMoveCharacterSet(); - - void (^lineBlock)(ASTextLine *) = ^(ASTextLine *line){ - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - if (!runs) return; - NSUInteger runCount = CFArrayGetCount(runs); - if (runCount == 0) return; - NSMutableArray *lineRunRanges = [NSMutableArray new]; - line.verticalRotateRange = lineRunRanges; - for (NSUInteger r = 0; r < runCount; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - NSMutableArray *runRanges = [NSMutableArray new]; - [lineRunRanges addObject:runRanges]; - NSUInteger glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - - CFIndex runStrIdx[glyphCount + 1]; - CTRunGetStringIndices(run, CFRangeMake(0, 0), runStrIdx); - CFRange runStrRange = CTRunGetStringRange(run); - runStrIdx[glyphCount] = runStrRange.location + runStrRange.length; - CFDictionaryRef runAttrs = CTRunGetAttributes(run); - CTFontRef font = (CTFontRef)CFDictionaryGetValue(runAttrs, kCTFontAttributeName); - BOOL isColorGlyph = ASTextCTFontContainsColorBitmapGlyphs(font); - - NSUInteger prevIdx = 0; - ASTextRunGlyphDrawMode prevMode = ASTextRunGlyphDrawModeHorizontal; - NSString *layoutStr = layout.text.string; - for (NSUInteger g = 0; g < glyphCount; g++) { - BOOL glyphRotate = 0, glyphRotateMove = NO; - CFIndex runStrLen = runStrIdx[g + 1] - runStrIdx[g]; - if (isColorGlyph) { - glyphRotate = YES; - } else if (runStrLen == 1) { - unichar c = [layoutStr characterAtIndex:runStrIdx[g]]; - glyphRotate = [rotateCharset characterIsMember:c]; - if (glyphRotate) glyphRotateMove = [rotateMoveCharset characterIsMember:c]; - } else if (runStrLen > 1){ - NSString *glyphStr = [layoutStr substringWithRange:NSMakeRange(runStrIdx[g], runStrLen)]; - BOOL glyphRotate = [glyphStr rangeOfCharacterFromSet:rotateCharset].location != NSNotFound; - if (glyphRotate) glyphRotateMove = [glyphStr rangeOfCharacterFromSet:rotateMoveCharset].location != NSNotFound; - } - - ASTextRunGlyphDrawMode mode = glyphRotateMove ? ASTextRunGlyphDrawModeVerticalRotateMove : (glyphRotate ? ASTextRunGlyphDrawModeVerticalRotate : ASTextRunGlyphDrawModeHorizontal); - if (g == 0) { - prevMode = mode; - } else if (mode != prevMode) { - ASTextRunGlyphRange *aRange = [ASTextRunGlyphRange rangeWithRange:NSMakeRange(prevIdx, g - prevIdx) drawMode:prevMode]; - [runRanges addObject:aRange]; - prevIdx = g; - prevMode = mode; - } - } - if (prevIdx < glyphCount) { - ASTextRunGlyphRange *aRange = [ASTextRunGlyphRange rangeWithRange:NSMakeRange(prevIdx, glyphCount - prevIdx) drawMode:prevMode]; - [runRanges addObject:aRange]; - } - - } - }; - for (ASTextLine *line in lines) { - lineBlock(line); - } - if (truncatedLine) lineBlock(truncatedLine); - } - - if (visibleRange.length > 0) { - layout.needDrawText = YES; - - void (^block)(NSDictionary *attrs, NSRange range, BOOL *stop) = ^(NSDictionary *attrs, NSRange range, BOOL *stop) { - if (attrs[ASTextHighlightAttributeName]) layout.containsHighlight = YES; - if (attrs[ASTextBlockBorderAttributeName]) layout.needDrawBlockBorder = YES; - if (attrs[ASTextBackgroundBorderAttributeName]) layout.needDrawBackgroundBorder = YES; - if (attrs[ASTextShadowAttributeName] || attrs[NSShadowAttributeName]) layout.needDrawShadow = YES; - if (attrs[ASTextUnderlineAttributeName]) layout.needDrawUnderline = YES; - if (attrs[ASTextAttachmentAttributeName]) layout.needDrawAttachment = YES; - if (attrs[ASTextInnerShadowAttributeName]) layout.needDrawInnerShadow = YES; - if (attrs[ASTextStrikethroughAttributeName]) layout.needDrawStrikethrough = YES; - if (attrs[ASTextBorderAttributeName]) layout.needDrawBorder = YES; - }; - - [layout.text enumerateAttributesInRange:visibleRange options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:block]; - if (truncatedLine) { - [truncationToken enumerateAttributesInRange:NSMakeRange(0, truncationToken.length) options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:block]; - } - } - - attachments = [NSMutableArray new]; - attachmentRanges = [NSMutableArray new]; - attachmentRects = [NSMutableArray new]; - attachmentContentsSet = [NSMutableSet new]; - for (NSUInteger i = 0, max = lines.count; i < max; i++) { - ASTextLine *line = lines[i]; - if (truncatedLine && line.index == truncatedLine.index) line = truncatedLine; - if (line.attachments.count > 0) { - [attachments addObjectsFromArray:line.attachments]; - [attachmentRanges addObjectsFromArray:line.attachmentRanges]; - [attachmentRects addObjectsFromArray:line.attachmentRects]; - for (ASTextAttachment *attachment in line.attachments) { - if (attachment.content) { - [attachmentContentsSet addObject:attachment.content]; - } - } - } - } - if (attachments.count == 0) { - attachments = attachmentRanges = attachmentRects = nil; - } - - layout.frame = ctFrame; - layout.lines = lines; - layout.truncatedLine = truncatedLine; - layout.attachments = attachments; - layout.attachmentRanges = attachmentRanges; - layout.attachmentRects = attachmentRects; - layout.attachmentContentsSet = attachmentContentsSet; - layout.rowCount = rowCount; - layout.visibleRange = visibleRange; - layout.textBoundingRect = textBoundingRect; - layout.textBoundingSize = textBoundingSize; - layout.lineRowsEdge = lineRowsEdge; - layout.lineRowsIndex = lineRowsIndex; - CFRelease(cgPath); - CFRelease(ctSetter); - CFRelease(ctFrame); - if (lineOrigins) free(lineOrigins); - return layout; -} - -+ (NSArray *)layoutWithContainers:(NSArray *)containers text:(NSAttributedString *)text { - return [self layoutWithContainers:containers text:text range:NSMakeRange(0, text.length)]; -} - -+ (NSArray *)layoutWithContainers:(NSArray *)containers text:(NSAttributedString *)text range:(NSRange)range { - if (!containers || !text) return nil; - if (range.location + range.length > text.length) return nil; - NSMutableArray *layouts = [[NSMutableArray alloc] init]; - for (NSUInteger i = 0, max = containers.count; i < max; i++) { - ASTextContainer *container = containers[i]; - ASTextLayout *layout = [self layoutWithContainer:container text:text range:range]; - if (!layout) return nil; - NSInteger length = (NSInteger)range.length - (NSInteger)layout.visibleRange.length; - if (length <= 0) { - range.length = 0; - range.location = text.length; - } else { - range.length = length; - range.location += layout.visibleRange.length; - } - } - return layouts; -} - -- (void)setFrame:(CTFrameRef)frame { - if (_frame != frame) { - if (frame) CFRetain(frame); - if (_frame) CFRelease(_frame); - _frame = frame; - } -} - -- (void)dealloc { - if (_frame) CFRelease(_frame); - if (_lineRowsIndex) free(_lineRowsIndex); - if (_lineRowsEdge) free(_lineRowsEdge); -} - -#pragma mark - Copying - -- (id)copyWithZone:(NSZone *)zone { - return self; // readonly object -} - - -#pragma mark - Query - -/** - Get the row index with 'edge' distance. - - @param edge The distance from edge to the point. - If vertical form, the edge is left edge, otherwise the edge is top edge. - - @return Returns NSNotFound if there's no row at the point. - */ -- (NSUInteger)_rowIndexForEdge:(CGFloat)edge { - if (_rowCount == 0) return NSNotFound; - BOOL isVertical = _container.verticalForm; - NSUInteger lo = 0, hi = _rowCount - 1, mid = 0; - NSUInteger rowIdx = NSNotFound; - while (lo <= hi) { - mid = (lo + hi) / 2; - ASRowEdge oneEdge = _lineRowsEdge[mid]; - if (isVertical ? - (oneEdge.foot <= edge && edge <= oneEdge.head) : - (oneEdge.head <= edge && edge <= oneEdge.foot)) { - rowIdx = mid; - break; - } - if ((isVertical ? (edge > oneEdge.head) : (edge < oneEdge.head))) { - if (mid == 0) break; - hi = mid - 1; - } else { - lo = mid + 1; - } - } - return rowIdx; -} - -/** - Get the closest row index with 'edge' distance. - - @param edge The distance from edge to the point. - If vertical form, the edge is left edge, otherwise the edge is top edge. - - @return Returns NSNotFound if there's no line. - */ -- (NSUInteger)_closestRowIndexForEdge:(CGFloat)edge { - if (_rowCount == 0) return NSNotFound; - NSUInteger rowIdx = [self _rowIndexForEdge:edge]; - if (rowIdx == NSNotFound) { - if (_container.verticalForm) { - if (edge > _lineRowsEdge[0].head) { - rowIdx = 0; - } else if (edge < _lineRowsEdge[_rowCount - 1].foot) { - rowIdx = _rowCount - 1; - } - } else { - if (edge < _lineRowsEdge[0].head) { - rowIdx = 0; - } else if (edge > _lineRowsEdge[_rowCount - 1].foot) { - rowIdx = _rowCount - 1; - } - } - } - return rowIdx; -} - -/** - Get a CTRun from a line position. - - @param line The text line. - @param position The position in the whole text. - - @return Returns NULL if not found (no CTRun at the position). - */ -- (CTRunRef)_runForLine:(ASTextLine *)line position:(ASTextPosition *)position { - if (!line || !position) return NULL; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger i = 0, max = CFArrayGetCount(runs); i < max; i++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, i); - CFRange range = CTRunGetStringRange(run); - if (position.affinity == ASTextAffinityBackward) { - if (range.location < position.offset && position.offset <= range.location + range.length) { - return run; - } - } else { - if (range.location <= position.offset && position.offset < range.location + range.length) { - return run; - } - } - } - return NULL; -} - -/** - Whether the position is inside a composed character sequence. - - @param line The text line. - @param position Text text position in whole text. - @param block The block to be executed before returns YES. - left: left X offset - right: right X offset - prev: left position - next: right position - */ -- (BOOL)_insideComposedCharacterSequences:(ASTextLine *)line position:(NSUInteger)position block:(void (^)(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next))block { - NSRange range = line.range; - if (range.length == 0) return NO; - __block BOOL inside = NO; - __block NSUInteger _prev, _next; - [_text.string enumerateSubstringsInRange:range options:NSStringEnumerationByComposedCharacterSequences usingBlock: ^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { - NSUInteger prev = substringRange.location; - NSUInteger next = substringRange.location + substringRange.length; - if (prev == position || next == position) { - *stop = YES; - } - if (prev < position && position < next) { - inside = YES; - _prev = prev; - _next = next; - *stop = YES; - } - }]; - if (inside && block) { - CGFloat left = [self offsetForTextPosition:_prev lineIndex:line.index]; - CGFloat right = [self offsetForTextPosition:_next lineIndex:line.index]; - block(left, right, _prev, _next); - } - return inside; -} - -/** - Whether the position is inside an emoji (such as National Flag Emoji). - - @param line The text line. - @param position Text text position in whole text. - @param block Yhe block to be executed before returns YES. - left: emoji's left X offset - right: emoji's right X offset - prev: emoji's left position - next: emoji's right position - */ -- (BOOL)_insideEmoji:(ASTextLine *)line position:(NSUInteger)position block:(void (^)(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next))block { - if (!line) return NO; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - NSUInteger glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - CFRange range = CTRunGetStringRange(run); - if (range.length <= 1) continue; - if (position <= range.location || position >= range.location + range.length) continue; - CFDictionaryRef attrs = CTRunGetAttributes(run); - CTFontRef font = (CTFontRef)CFDictionaryGetValue(attrs, kCTFontAttributeName); - if (!ASTextCTFontContainsColorBitmapGlyphs(font)) continue; - - // Here's Emoji runs (larger than 1 unichar), and position is inside the range. - CFIndex indices[glyphCount]; - CTRunGetStringIndices(run, CFRangeMake(0, glyphCount), indices); - for (NSUInteger g = 0; g < glyphCount; g++) { - CFIndex prev = indices[g]; - CFIndex next = g + 1 < glyphCount ? indices[g + 1] : range.location + range.length; - if (position == prev) break; // Emoji edge - if (prev < position && position < next) { // inside an emoji (such as National Flag Emoji) - CGPoint pos = CGPointZero; - CGSize adv = CGSizeZero; - CTRunGetPositions(run, CFRangeMake(g, 1), &pos); - CTRunGetAdvances(run, CFRangeMake(g, 1), &adv); - if (block) { - block(line.position.x + pos.x, - line.position.x + pos.x + adv.width, - prev, next); - } - return YES; - } - } - } - return NO; -} -/** - Whether the write direction is RTL at the specified point - - @param line The text line - @param point The point in layout. - - @return YES if RTL. - */ -- (BOOL)_isRightToLeftInLine:(ASTextLine *)line atPoint:(CGPoint)point { - if (!line) return NO; - // get write direction - BOOL RTL = NO; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, max = CFArrayGetCount(runs); r < max; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CGPoint glyphPosition; - CTRunGetPositions(run, CFRangeMake(0, 1), &glyphPosition); - if (_container.verticalForm) { - CGFloat runX = glyphPosition.x; - runX += line.position.y; - CGFloat runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL); - if (runX <= point.y && point.y <= runX + runWidth) { - if (CTRunGetStatus(run) & kCTRunStatusRightToLeft) RTL = YES; - break; - } - } else { - CGFloat runX = glyphPosition.x; - runX += line.position.x; - CGFloat runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL); - if (runX <= point.x && point.x <= runX + runWidth) { - if (CTRunGetStatus(run) & kCTRunStatusRightToLeft) RTL = YES; - break; - } - } - } - return RTL; -} - -/** - Correct the range's edge. - */ -- (ASTextRange *)_correctedRangeWithEdge:(ASTextRange *)range { - NSRange visibleRange = self.visibleRange; - ASTextPosition *start = range.start; - ASTextPosition *end = range.end; - - if (start.offset == visibleRange.location && start.affinity == ASTextAffinityBackward) { - start = [ASTextPosition positionWithOffset:start.offset affinity:ASTextAffinityForward]; - } - - if (end.offset == visibleRange.location + visibleRange.length && start.affinity == ASTextAffinityForward) { - end = [ASTextPosition positionWithOffset:end.offset affinity:ASTextAffinityBackward]; - } - - if (start != range.start || end != range.end) { - range = [ASTextRange rangeWithStart:start end:end]; - } - return range; -} - -- (NSUInteger)lineIndexForRow:(NSUInteger)row { - if (row >= _rowCount) return NSNotFound; - return _lineRowsIndex[row]; -} - -- (NSUInteger)lineCountForRow:(NSUInteger)row { - if (row >= _rowCount) return NSNotFound; - if (row == _rowCount - 1) { - return _lines.count - _lineRowsIndex[row]; - } else { - return _lineRowsIndex[row + 1] - _lineRowsIndex[row]; - } -} - -- (NSUInteger)rowIndexForLine:(NSUInteger)line { - if (line >= _lines.count) return NSNotFound; - return ((ASTextLine *)_lines[line]).row; -} - -- (NSUInteger)lineIndexForPoint:(CGPoint)point { - if (_lines.count == 0 || _rowCount == 0) return NSNotFound; - NSUInteger rowIdx = [self _rowIndexForEdge:_container.verticalForm ? point.x : point.y]; - if (rowIdx == NSNotFound) return NSNotFound; - - NSUInteger lineIdx0 = _lineRowsIndex[rowIdx]; - NSUInteger lineIdx1 = rowIdx == _rowCount - 1 ? _lines.count - 1 : _lineRowsIndex[rowIdx + 1] - 1; - for (NSUInteger i = lineIdx0; i <= lineIdx1; i++) { - CGRect bounds = ((ASTextLine *)_lines[i]).bounds; - if (CGRectContainsPoint(bounds, point)) return i; - } - - return NSNotFound; -} - -- (NSUInteger)closestLineIndexForPoint:(CGPoint)point { - BOOL isVertical = _container.verticalForm; - if (_lines.count == 0 || _rowCount == 0) return NSNotFound; - NSUInteger rowIdx = [self _closestRowIndexForEdge:isVertical ? point.x : point.y]; - if (rowIdx == NSNotFound) return NSNotFound; - - NSUInteger lineIdx0 = _lineRowsIndex[rowIdx]; - NSUInteger lineIdx1 = rowIdx == _rowCount - 1 ? _lines.count - 1 : _lineRowsIndex[rowIdx + 1] - 1; - if (lineIdx0 == lineIdx1) return lineIdx0; - - CGFloat minDistance = CGFLOAT_MAX; - NSUInteger minIndex = lineIdx0; - for (NSUInteger i = lineIdx0; i <= lineIdx1; i++) { - CGRect bounds = ((ASTextLine *)_lines[i]).bounds; - if (isVertical) { - if (bounds.origin.y <= point.y && point.y <= bounds.origin.y + bounds.size.height) return i; - CGFloat distance; - if (point.y < bounds.origin.y) { - distance = bounds.origin.y - point.y; - } else { - distance = point.y - (bounds.origin.y + bounds.size.height); - } - if (distance < minDistance) { - minDistance = distance; - minIndex = i; - } - } else { - if (bounds.origin.x <= point.x && point.x <= bounds.origin.x + bounds.size.width) return i; - CGFloat distance; - if (point.x < bounds.origin.x) { - distance = bounds.origin.x - point.x; - } else { - distance = point.x - (bounds.origin.x + bounds.size.width); - } - if (distance < minDistance) { - minDistance = distance; - minIndex = i; - } - } - } - return minIndex; -} - -- (CGFloat)offsetForTextPosition:(NSUInteger)position lineIndex:(NSUInteger)lineIndex { - if (lineIndex >= _lines.count) return CGFLOAT_MAX; - ASTextLine *line = _lines[lineIndex]; - CFRange range = CTLineGetStringRange(line.CTLine); - if (position < range.location || position > range.location + range.length) return CGFLOAT_MAX; - - CGFloat offset = CTLineGetOffsetForStringIndex(line.CTLine, position, NULL); - return _container.verticalForm ? (offset + line.position.y) : (offset + line.position.x); -} - -- (NSUInteger)textPositionForPoint:(CGPoint)point lineIndex:(NSUInteger)lineIndex { - if (lineIndex >= _lines.count) return NSNotFound; - ASTextLine *line = _lines[lineIndex]; - if (_container.verticalForm) { - point.x = point.y - line.position.y; - point.y = 0; - } else { - point.x -= line.position.x; - point.y = 0; - } - CFIndex idx = CTLineGetStringIndexForPosition(line.CTLine, point); - if (idx == kCFNotFound) return NSNotFound; - - /* - If the emoji contains one or more variant form (such as ☔️ "\u2614\uFE0F") - and the font size is smaller than 379/15, then each variant form ("\uFE0F") - will rendered as a single blank glyph behind the emoji glyph. Maybe it's a - bug in CoreText? Seems iOS8.3 fixes this problem. - - If the point hit the blank glyph, the CTLineGetStringIndexForPosition() - returns the position before the emoji glyph, but it should returns the - position after the emoji and variant form. - - Here's a workaround. - */ - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, max = CFArrayGetCount(runs); r < max; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CFRange range = CTRunGetStringRange(run); - if (range.location <= idx && idx < range.location + range.length) { - NSUInteger glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) break; - CFDictionaryRef attrs = CTRunGetAttributes(run); - CTFontRef font = (CTFontRef)CFDictionaryGetValue(attrs, kCTFontAttributeName); - if (!ASTextCTFontContainsColorBitmapGlyphs(font)) break; - - CFIndex indices[glyphCount]; - CGPoint positions[glyphCount]; - CTRunGetStringIndices(run, CFRangeMake(0, glyphCount), indices); - CTRunGetPositions(run, CFRangeMake(0, glyphCount), positions); - for (NSUInteger g = 0; g < glyphCount; g++) { - NSUInteger gIdx = indices[g]; - if (gIdx == idx && g + 1 < glyphCount) { - CGFloat right = positions[g + 1].x; - if (point.x < right) break; - NSUInteger next = indices[g + 1]; - do { - if (next == range.location + range.length) break; - unichar c = [_text.string characterAtIndex:next]; - if ((c == 0xFE0E || c == 0xFE0F)) { // unicode variant form for emoji style - next++; - } else break; - } - while (1); - if (next != indices[g + 1]) idx = next; - break; - } - } - break; - } - } - return idx; -} - -- (ASTextPosition *)closestPositionToPoint:(CGPoint)point { - BOOL isVertical = _container.verticalForm; - // When call CTLineGetStringIndexForPosition() on ligature such as 'fi', - // and the point `hit` the glyph's left edge, it may get the ligature inside offset. - // I don't know why, maybe it's a bug of CoreText. Try to avoid it. - if (isVertical) point.y += 0.00001234; - else point.x += 0.00001234; - - NSUInteger lineIndex = [self closestLineIndexForPoint:point]; - if (lineIndex == NSNotFound) return nil; - ASTextLine *line = _lines[lineIndex]; - __block NSUInteger position = [self textPositionForPoint:point lineIndex:lineIndex]; - if (position == NSNotFound) position = line.range.location; - if (position <= _visibleRange.location) { - return [ASTextPosition positionWithOffset:_visibleRange.location affinity:ASTextAffinityForward]; - } else if (position >= _visibleRange.location + _visibleRange.length) { - return [ASTextPosition positionWithOffset:_visibleRange.location + _visibleRange.length affinity:ASTextAffinityBackward]; - } - - ASTextAffinity finalAffinity = ASTextAffinityForward; - BOOL finalAffinityDetected = NO; - - // binding range - NSRange bindingRange; - ASTextBinding *binding = [_text attribute:ASTextBindingAttributeName atIndex:position longestEffectiveRange:&bindingRange inRange:NSMakeRange(0, _text.length)]; - if (binding && bindingRange.length > 0) { - NSUInteger headLineIdx = [self lineIndexForPosition:[ASTextPosition positionWithOffset:bindingRange.location]]; - NSUInteger tailLineIdx = [self lineIndexForPosition:[ASTextPosition positionWithOffset:bindingRange.location + bindingRange.length affinity:ASTextAffinityBackward]]; - if (headLineIdx == lineIndex && lineIndex == tailLineIdx) { // all in same line - CGFloat left = [self offsetForTextPosition:bindingRange.location lineIndex:lineIndex]; - CGFloat right = [self offsetForTextPosition:bindingRange.location + bindingRange.length lineIndex:lineIndex]; - if (left != CGFLOAT_MAX && right != CGFLOAT_MAX) { - if (_container.isVerticalForm) { - if (fabs(point.y - left) < fabs(point.y - right)) { - position = bindingRange.location; - finalAffinity = ASTextAffinityForward; - } else { - position = bindingRange.location + bindingRange.length; - finalAffinity = ASTextAffinityBackward; - } - } else { - if (fabs(point.x - left) < fabs(point.x - right)) { - position = bindingRange.location; - finalAffinity = ASTextAffinityForward; - } else { - position = bindingRange.location + bindingRange.length; - finalAffinity = ASTextAffinityBackward; - } - } - } else if (left != CGFLOAT_MAX) { - position = left; - finalAffinity = ASTextAffinityForward; - } else if (right != CGFLOAT_MAX) { - position = right; - finalAffinity = ASTextAffinityBackward; - } - finalAffinityDetected = YES; - } else if (headLineIdx == lineIndex) { - CGFloat left = [self offsetForTextPosition:bindingRange.location lineIndex:lineIndex]; - if (left != CGFLOAT_MAX) { - position = bindingRange.location; - finalAffinity = ASTextAffinityForward; - finalAffinityDetected = YES; - } - } else if (tailLineIdx == lineIndex) { - CGFloat right = [self offsetForTextPosition:bindingRange.location + bindingRange.length lineIndex:lineIndex]; - if (right != CGFLOAT_MAX) { - position = bindingRange.location + bindingRange.length; - finalAffinity = ASTextAffinityBackward; - finalAffinityDetected = YES; - } - } else { - BOOL onLeft = NO, onRight = NO; - if (headLineIdx != NSNotFound && tailLineIdx != NSNotFound) { - if (abs((int)headLineIdx - (int)lineIndex) < abs((int)tailLineIdx - (int)lineIndex)) onLeft = YES; - else onRight = YES; - } else if (headLineIdx != NSNotFound) { - onLeft = YES; - } else if (tailLineIdx != NSNotFound) { - onRight = YES; - } - - if (onLeft) { - CGFloat left = [self offsetForTextPosition:bindingRange.location lineIndex:headLineIdx]; - if (left != CGFLOAT_MAX) { - lineIndex = headLineIdx; - line = _lines[headLineIdx]; - position = bindingRange.location; - finalAffinity = ASTextAffinityForward; - finalAffinityDetected = YES; - } - } else if (onRight) { - CGFloat right = [self offsetForTextPosition:bindingRange.location + bindingRange.length lineIndex:tailLineIdx]; - if (right != CGFLOAT_MAX) { - lineIndex = tailLineIdx; - line = _lines[tailLineIdx]; - position = bindingRange.location + bindingRange.length; - finalAffinity = ASTextAffinityBackward; - finalAffinityDetected = YES; - } - } - } - } - - // empty line - if (line.range.length == 0) { - BOOL behind = (_lines.count > 1 && lineIndex == _lines.count - 1); //end line - return [ASTextPosition positionWithOffset:line.range.location affinity:behind ? ASTextAffinityBackward:ASTextAffinityForward]; - } - - // detect weather the line is a linebreak token - if (line.range.length <= 2) { - NSString *str = [_text.string substringWithRange:line.range]; - if (ASTextIsLinebreakString(str)) { // an empty line ("\r", "\n", "\r\n") - return [ASTextPosition positionWithOffset:line.range.location]; - } - } - - // above whole text frame - if (lineIndex == 0 && (isVertical ? (point.x > line.right) : (point.y < line.top))) { - position = 0; - finalAffinity = ASTextAffinityForward; - finalAffinityDetected = YES; - } - // below whole text frame - if (lineIndex == _lines.count - 1 && (isVertical ? (point.x < line.left) : (point.y > line.bottom))) { - position = line.range.location + line.range.length; - finalAffinity = ASTextAffinityBackward; - finalAffinityDetected = YES; - } - - // There must be at least one non-linebreak char, - // ignore the linebreak characters at line end if exists. - if (position >= line.range.location + line.range.length - 1) { - if (position > line.range.location) { - unichar c1 = [_text.string characterAtIndex:position - 1]; - if (ASTextIsLinebreakChar(c1)) { - position--; - if (position > line.range.location) { - unichar c0 = [_text.string characterAtIndex:position - 1]; - if (ASTextIsLinebreakChar(c0)) { - position--; - } - } - } - } - } - if (position == line.range.location) { - return [ASTextPosition positionWithOffset:position]; - } - if (position == line.range.location + line.range.length) { - return [ASTextPosition positionWithOffset:position affinity:ASTextAffinityBackward]; - } - - [self _insideComposedCharacterSequences:line position:position block: ^(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next) { - if (isVertical) { - position = fabs(left - point.y) < fabs(right - point.y) < (right ? prev : next); - } else { - position = fabs(left - point.x) < fabs(right - point.x) < (right ? prev : next); - } - }]; - - [self _insideEmoji:line position:position block: ^(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next) { - if (isVertical) { - position = fabs(left - point.y) < fabs(right - point.y) < (right ? prev : next); - } else { - position = fabs(left - point.x) < fabs(right - point.x) < (right ? prev : next); - } - }]; - - if (position < _visibleRange.location) position = _visibleRange.location; - else if (position > _visibleRange.location + _visibleRange.length) position = _visibleRange.location + _visibleRange.length; - - if (!finalAffinityDetected) { - CGFloat ofs = [self offsetForTextPosition:position lineIndex:lineIndex]; - if (ofs != CGFLOAT_MAX) { - BOOL RTL = [self _isRightToLeftInLine:line atPoint:point]; - if (position >= line.range.location + line.range.length) { - finalAffinity = RTL ? ASTextAffinityForward : ASTextAffinityBackward; - } else if (position <= line.range.location) { - finalAffinity = RTL ? ASTextAffinityBackward : ASTextAffinityForward; - } else { - finalAffinity = (ofs < (isVertical ? point.y : point.x) && !RTL) ? ASTextAffinityForward : ASTextAffinityBackward; - } - } - } - - return [ASTextPosition positionWithOffset:position affinity:finalAffinity]; -} - -- (ASTextPosition *)positionForPoint:(CGPoint)point - oldPosition:(ASTextPosition *)oldPosition - otherPosition:(ASTextPosition *)otherPosition { - if (!oldPosition || !otherPosition) { - return oldPosition; - } - ASTextPosition *newPos = [self closestPositionToPoint:point]; - if (!newPos) return oldPosition; - if ([newPos compare:otherPosition] == [oldPosition compare:otherPosition] && - newPos.offset != otherPosition.offset) { - return newPos; - } - NSUInteger lineIndex = [self lineIndexForPosition:otherPosition]; - if (lineIndex == NSNotFound) return oldPosition; - ASTextLine *line = _lines[lineIndex]; - ASRowEdge vertical = _lineRowsEdge[line.row]; - if (_container.verticalForm) { - point.x = (vertical.head + vertical.foot) * 0.5; - } else { - point.y = (vertical.head + vertical.foot) * 0.5; - } - newPos = [self closestPositionToPoint:point]; - if ([newPos compare:otherPosition] == [oldPosition compare:otherPosition] && - newPos.offset != otherPosition.offset) { - return newPos; - } - - if (_container.isVerticalForm) { - if ([oldPosition compare:otherPosition] == NSOrderedAscending) { // search backward - ASTextRange *range = [self textRangeByExtendingPosition:otherPosition inDirection:UITextLayoutDirectionUp offset:1]; - if (range) return range.start; - } else { // search forward - ASTextRange *range = [self textRangeByExtendingPosition:otherPosition inDirection:UITextLayoutDirectionDown offset:1]; - if (range) return range.end; - } - } else { - if ([oldPosition compare:otherPosition] == NSOrderedAscending) { // search backward - ASTextRange *range = [self textRangeByExtendingPosition:otherPosition inDirection:UITextLayoutDirectionLeft offset:1]; - if (range) return range.start; - } else { // search forward - ASTextRange *range = [self textRangeByExtendingPosition:otherPosition inDirection:UITextLayoutDirectionRight offset:1]; - if (range) return range.end; - } - } - - return oldPosition; -} - -- (ASTextRange *)textRangeAtPoint:(CGPoint)point { - NSUInteger lineIndex = [self lineIndexForPoint:point]; - if (lineIndex == NSNotFound) return nil; - NSUInteger textPosition = [self textPositionForPoint:point lineIndex:[self lineIndexForPoint:point]]; - if (textPosition == NSNotFound) return nil; - ASTextPosition *pos = [self closestPositionToPoint:point]; - if (!pos) return nil; - - // get write direction - BOOL RTL = [self _isRightToLeftInLine:_lines[lineIndex] atPoint:point]; - CGRect rect = [self caretRectForPosition:pos]; - if (CGRectIsNull(rect)) return nil; - - if (_container.verticalForm) { - ASTextRange *range = [self textRangeByExtendingPosition:pos inDirection:(rect.origin.y >= point.y && !RTL) ? UITextLayoutDirectionUp:UITextLayoutDirectionDown offset:1]; - return range; - } else { - ASTextRange *range = [self textRangeByExtendingPosition:pos inDirection:(rect.origin.x >= point.x && !RTL) ? UITextLayoutDirectionLeft:UITextLayoutDirectionRight offset:1]; - return range; - } -} - -- (ASTextRange *)closestTextRangeAtPoint:(CGPoint)point { - ASTextPosition *pos = [self closestPositionToPoint:point]; - if (!pos) return nil; - NSUInteger lineIndex = [self lineIndexForPosition:pos]; - if (lineIndex == NSNotFound) return nil; - ASTextLine *line = _lines[lineIndex]; - BOOL RTL = [self _isRightToLeftInLine:line atPoint:point]; - CGRect rect = [self caretRectForPosition:pos]; - if (CGRectIsNull(rect)) return nil; - - UITextLayoutDirection direction = UITextLayoutDirectionRight; - if (pos.offset >= line.range.location + line.range.length) { - if (direction != RTL) { - direction = _container.verticalForm ? UITextLayoutDirectionUp : UITextLayoutDirectionLeft; - } else { - direction = _container.verticalForm ? UITextLayoutDirectionDown : UITextLayoutDirectionRight; - } - } else if (pos.offset <= line.range.location) { - if (direction != RTL) { - direction = _container.verticalForm ? UITextLayoutDirectionDown : UITextLayoutDirectionRight; - } else { - direction = _container.verticalForm ? UITextLayoutDirectionUp : UITextLayoutDirectionLeft; - } - } else { - if (_container.verticalForm) { - direction = (rect.origin.y >= point.y && !RTL) ? UITextLayoutDirectionUp:UITextLayoutDirectionDown; - } else { - direction = (rect.origin.x >= point.x && !RTL) ? UITextLayoutDirectionLeft:UITextLayoutDirectionRight; - } - } - - ASTextRange *range = [self textRangeByExtendingPosition:pos inDirection:direction offset:1]; - return range; -} - -- (ASTextRange *)textRangeByExtendingPosition:(ASTextPosition *)position { - NSUInteger visibleStart = _visibleRange.location; - NSUInteger visibleEnd = _visibleRange.location + _visibleRange.length; - - if (!position) return nil; - if (position.offset < visibleStart || position.offset > visibleEnd) return nil; - - // head or tail, returns immediately - if (position.offset == visibleStart) { - return [ASTextRange rangeWithRange:NSMakeRange(position.offset, 0)]; - } else if (position.offset == visibleEnd) { - return [ASTextRange rangeWithRange:NSMakeRange(position.offset, 0) affinity:ASTextAffinityBackward]; - } - - // binding range - NSRange tRange; - ASTextBinding *binding = [_text attribute:ASTextBindingAttributeName atIndex:position.offset longestEffectiveRange:&tRange inRange:_visibleRange]; - if (binding && tRange.length > 0 && tRange.location < position.offset) { - return [ASTextRange rangeWithRange:tRange]; - } - - // inside emoji or composed character sequences - NSUInteger lineIndex = [self lineIndexForPosition:position]; - if (lineIndex != NSNotFound) { - __block NSUInteger _prev, _next; - BOOL emoji = NO, seq = NO; - - ASTextLine *line = _lines[lineIndex]; - emoji = [self _insideEmoji:line position:position.offset block: ^(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next) { - _prev = prev; - _next = next; - }]; - if (!emoji) { - seq = [self _insideComposedCharacterSequences:line position:position.offset block: ^(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next) { - _prev = prev; - _next = next; - }]; - } - if (emoji || seq) { - return [ASTextRange rangeWithRange:NSMakeRange(_prev, _next - _prev)]; - } - } - - // inside linebreak '\r\n' - if (position.offset > visibleStart && position.offset < visibleEnd) { - unichar c0 = [_text.string characterAtIndex:position.offset - 1]; - if ((c0 == '\r') && position.offset < visibleEnd) { - unichar c1 = [_text.string characterAtIndex:position.offset]; - if (c1 == '\n') { - return [ASTextRange rangeWithStart:[ASTextPosition positionWithOffset:position.offset - 1] end:[ASTextPosition positionWithOffset:position.offset + 1]]; - } - } - if (ASTextIsLinebreakChar(c0) && position.affinity == ASTextAffinityBackward) { - NSString *str = [_text.string substringToIndex:position.offset]; - NSUInteger len = ASTextLinebreakTailLength(str); - return [ASTextRange rangeWithStart:[ASTextPosition positionWithOffset:position.offset - len] end:[ASTextPosition positionWithOffset:position.offset]]; - } - } - - return [ASTextRange rangeWithRange:NSMakeRange(position.offset, 0) affinity:position.affinity]; -} - -- (ASTextRange *)textRangeByExtendingPosition:(ASTextPosition *)position - inDirection:(UITextLayoutDirection)direction - offset:(NSInteger)offset { - NSInteger visibleStart = _visibleRange.location; - NSInteger visibleEnd = _visibleRange.location + _visibleRange.length; - - if (!position) return nil; - if (position.offset < visibleStart || position.offset > visibleEnd) return nil; - if (offset == 0) return [self textRangeByExtendingPosition:position]; - - BOOL isVerticalForm = _container.verticalForm; - BOOL verticalMove, forwardMove; - - if (isVerticalForm) { - verticalMove = direction == UITextLayoutDirectionLeft || direction == UITextLayoutDirectionRight; - forwardMove = direction == UITextLayoutDirectionLeft || direction == UITextLayoutDirectionDown; - } else { - verticalMove = direction == UITextLayoutDirectionUp || direction == UITextLayoutDirectionDown; - forwardMove = direction == UITextLayoutDirectionDown || direction == UITextLayoutDirectionRight; - } - - if (offset < 0) { - forwardMove = !forwardMove; - offset = -offset; - } - - // head or tail, returns immediately - if (!forwardMove && position.offset == visibleStart) { - return [ASTextRange rangeWithRange:NSMakeRange(_visibleRange.location, 0)]; - } else if (forwardMove && position.offset == visibleEnd) { - return [ASTextRange rangeWithRange:NSMakeRange(position.offset, 0) affinity:ASTextAffinityBackward]; - } - - // extend from position - ASTextRange *fromRange = [self textRangeByExtendingPosition:position]; - if (!fromRange) return nil; - ASTextRange *allForward = [ASTextRange rangeWithStart:fromRange.start end:[ASTextPosition positionWithOffset:visibleEnd]]; - ASTextRange *allBackward = [ASTextRange rangeWithStart:[ASTextPosition positionWithOffset:visibleStart] end:fromRange.end]; - - if (verticalMove) { // up/down in text layout - NSInteger lineIndex = [self lineIndexForPosition:position]; - if (lineIndex == NSNotFound) return nil; - - ASTextLine *line = _lines[lineIndex]; - NSInteger moveToRowIndex = (NSInteger)line.row + (forwardMove ? offset : -offset); - if (moveToRowIndex < 0) return allBackward; - else if (moveToRowIndex >= (NSInteger)_rowCount) return allForward; - - CGFloat ofs = [self offsetForTextPosition:position.offset lineIndex:lineIndex]; - if (ofs == CGFLOAT_MAX) return nil; - - NSUInteger moveToLineFirstIndex = [self lineIndexForRow:moveToRowIndex]; - NSUInteger moveToLineCount = [self lineCountForRow:moveToRowIndex]; - if (moveToLineFirstIndex == NSNotFound || moveToLineCount == NSNotFound || moveToLineCount == 0) return nil; - CGFloat mostLeft = CGFLOAT_MAX, mostRight = -CGFLOAT_MAX; - ASTextLine *mostLeftLine = nil, *mostRightLine = nil; - NSUInteger insideIndex = NSNotFound; - for (NSUInteger i = 0; i < moveToLineCount; i++) { - NSUInteger lineIndex = moveToLineFirstIndex + i; - ASTextLine *line = _lines[lineIndex]; - if (isVerticalForm) { - if (line.top <= ofs && ofs <= line.bottom) { - insideIndex = line.index; - break; - } - if (line.top < mostLeft) { - mostLeft = line.top; - mostLeftLine = line; - } - if (line.bottom > mostRight) { - mostRight = line.bottom; - mostRightLine = line; - } - } else { - if (line.left <= ofs && ofs <= line.right) { - insideIndex = line.index; - break; - } - if (line.left < mostLeft) { - mostLeft = line.left; - mostLeftLine = line; - } - if (line.right > mostRight) { - mostRight = line.right; - mostRightLine = line; - } - } - } - BOOL afinityEdge = NO; - if (insideIndex == NSNotFound) { - if (ofs <= mostLeft) { - insideIndex = mostLeftLine.index; - } else { - insideIndex = mostRightLine.index; - } - afinityEdge = YES; - } - ASTextLine *insideLine = _lines[insideIndex]; - NSUInteger pos; - if (isVerticalForm) { - pos = [self textPositionForPoint:CGPointMake(insideLine.position.x, ofs) lineIndex:insideIndex]; - } else { - pos = [self textPositionForPoint:CGPointMake(ofs, insideLine.position.y) lineIndex:insideIndex]; - } - if (pos == NSNotFound) return nil; - ASTextPosition *extPos; - if (afinityEdge) { - if (pos == insideLine.range.location + insideLine.range.length) { - NSString *subStr = [_text.string substringWithRange:insideLine.range]; - NSUInteger lineBreakLen = ASTextLinebreakTailLength(subStr); - extPos = [ASTextPosition positionWithOffset:pos - lineBreakLen]; - } else { - extPos = [ASTextPosition positionWithOffset:pos]; - } - } else { - extPos = [ASTextPosition positionWithOffset:pos]; - } - ASTextRange *ext = [self textRangeByExtendingPosition:extPos]; - if (!ext) return nil; - if (forwardMove) { - return [ASTextRange rangeWithStart:fromRange.start end:ext.end]; - } else { - return [ASTextRange rangeWithStart:ext.start end:fromRange.end]; - } - - } else { // left/right in text layout - ASTextPosition *toPosition = [ASTextPosition positionWithOffset:position.offset + (forwardMove ? offset : -offset)]; - if (toPosition.offset <= visibleStart) return allBackward; - else if (toPosition.offset >= visibleEnd) return allForward; - - ASTextRange *toRange = [self textRangeByExtendingPosition:toPosition]; - if (!toRange) return nil; - - NSInteger start = MIN(fromRange.start.offset, toRange.start.offset); - NSInteger end = MAX(fromRange.end.offset, toRange.end.offset); - return [ASTextRange rangeWithRange:NSMakeRange(start, end - start)]; - } -} - -- (NSUInteger)lineIndexForPosition:(ASTextPosition *)position { - if (!position) return NSNotFound; - if (_lines.count == 0) return NSNotFound; - NSUInteger location = position.offset; - NSInteger lo = 0, hi = _lines.count - 1, mid = 0; - if (position.affinity == ASTextAffinityBackward) { - while (lo <= hi) { - mid = (lo + hi) / 2; - ASTextLine *line = _lines[mid]; - NSRange range = line.range; - if (range.location < location && location <= range.location + range.length) { - return mid; - } - if (location <= range.location) { - hi = mid - 1; - } else { - lo = mid + 1; - } - } - } else { - while (lo <= hi) { - mid = (lo + hi) / 2; - ASTextLine *line = _lines[mid]; - NSRange range = line.range; - if (range.location <= location && location < range.location + range.length) { - return mid; - } - if (location < range.location) { - hi = mid - 1; - } else { - lo = mid + 1; - } - } - } - return NSNotFound; -} - -- (CGPoint)linePositionForPosition:(ASTextPosition *)position { - NSUInteger lineIndex = [self lineIndexForPosition:position]; - if (lineIndex == NSNotFound) return CGPointZero; - ASTextLine *line = _lines[lineIndex]; - CGFloat offset = [self offsetForTextPosition:position.offset lineIndex:lineIndex]; - if (offset == CGFLOAT_MAX) return CGPointZero; - if (_container.verticalForm) { - return CGPointMake(line.position.x, offset); - } else { - return CGPointMake(offset, line.position.y); - } -} - -- (CGRect)caretRectForPosition:(ASTextPosition *)position { - NSUInteger lineIndex = [self lineIndexForPosition:position]; - if (lineIndex == NSNotFound) return CGRectNull; - ASTextLine *line = _lines[lineIndex]; - CGFloat offset = [self offsetForTextPosition:position.offset lineIndex:lineIndex]; - if (offset == CGFLOAT_MAX) return CGRectNull; - if (_container.verticalForm) { - return CGRectMake(line.bounds.origin.x, offset, line.bounds.size.width, 0); - } else { - return CGRectMake(offset, line.bounds.origin.y, 0, line.bounds.size.height); - } -} - -- (CGRect)firstRectForRange:(ASTextRange *)range { - range = [self _correctedRangeWithEdge:range]; - - NSUInteger startLineIndex = [self lineIndexForPosition:range.start]; - NSUInteger endLineIndex = [self lineIndexForPosition:range.end]; - if (startLineIndex == NSNotFound || endLineIndex == NSNotFound) return CGRectNull; - if (startLineIndex > endLineIndex) return CGRectNull; - ASTextLine *startLine = _lines[startLineIndex]; - ASTextLine *endLine = _lines[endLineIndex]; - NSMutableArray *lines = [NSMutableArray new]; - for (NSUInteger i = startLineIndex; i <= startLineIndex; i++) { - ASTextLine *line = _lines[i]; - if (line.row != startLine.row) break; - [lines addObject:line]; - } - if (_container.verticalForm) { - if (lines.count == 1) { - CGFloat top = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex]; - CGFloat bottom; - if (startLine == endLine) { - bottom = [self offsetForTextPosition:range.end.offset lineIndex:startLineIndex]; - } else { - bottom = startLine.bottom; - } - if (top == CGFLOAT_MAX || bottom == CGFLOAT_MAX) return CGRectNull; - if (top > bottom) ASTEXT_SWAP(top, bottom); - return CGRectMake(startLine.left, top, startLine.width, bottom - top); - } else { - CGFloat top = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex]; - CGFloat bottom = startLine.bottom; - if (top == CGFLOAT_MAX || bottom == CGFLOAT_MAX) return CGRectNull; - if (top > bottom) ASTEXT_SWAP(top, bottom); - CGRect rect = CGRectMake(startLine.left, top, startLine.width, bottom - top); - for (NSUInteger i = 1; i < lines.count; i++) { - ASTextLine *line = lines[i]; - rect = CGRectUnion(rect, line.bounds); - } - return rect; - } - } else { - if (lines.count == 1) { - CGFloat left = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex]; - CGFloat right; - if (startLine == endLine) { - right = [self offsetForTextPosition:range.end.offset lineIndex:startLineIndex]; - } else { - right = startLine.right; - } - if (left == CGFLOAT_MAX || right == CGFLOAT_MAX) return CGRectNull; - if (left > right) ASTEXT_SWAP(left, right); - return CGRectMake(left, startLine.top, right - left, startLine.height); - } else { - CGFloat left = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex]; - CGFloat right = startLine.right; - if (left == CGFLOAT_MAX || right == CGFLOAT_MAX) return CGRectNull; - if (left > right) ASTEXT_SWAP(left, right); - CGRect rect = CGRectMake(left, startLine.top, right - left, startLine.height); - for (NSUInteger i = 1; i < lines.count; i++) { - ASTextLine *line = lines[i]; - rect = CGRectUnion(rect, line.bounds); - } - return rect; - } - } -} - -- (CGRect)rectForRange:(ASTextRange *)range { - NSArray *rects = [self selectionRectsForRange:range]; - if (rects.count == 0) return CGRectNull; - CGRect rectUnion = ((ASTextSelectionRect *)rects.firstObject).rect; - for (NSUInteger i = 1; i < rects.count; i++) { - ASTextSelectionRect *rect = rects[i]; - rectUnion = CGRectUnion(rectUnion, rect.rect); - } - return rectUnion; -} - -- (NSArray *)selectionRectsForRange:(ASTextRange *)range { - range = [self _correctedRangeWithEdge:range]; - - BOOL isVertical = _container.verticalForm; - NSMutableArray *rects = [[NSMutableArray alloc] init]; - if (!range) return rects; - - NSUInteger startLineIndex = [self lineIndexForPosition:range.start]; - NSUInteger endLineIndex = [self lineIndexForPosition:range.end]; - if (startLineIndex == NSNotFound || endLineIndex == NSNotFound) return rects; - if (startLineIndex > endLineIndex) ASTEXT_SWAP(startLineIndex, endLineIndex); - ASTextLine *startLine = _lines[startLineIndex]; - ASTextLine *endLine = _lines[endLineIndex]; - CGFloat offsetStart = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex]; - CGFloat offsetEnd = [self offsetForTextPosition:range.end.offset lineIndex:endLineIndex]; - - ASTextSelectionRect *start = [ASTextSelectionRect new]; - if (isVertical) { - start.rect = CGRectMake(startLine.left, offsetStart, startLine.width, 0); - } else { - start.rect = CGRectMake(offsetStart, startLine.top, 0, startLine.height); - } - start.containsStart = YES; - start.isVertical = isVertical; - [rects addObject:start]; - - ASTextSelectionRect *end = [ASTextSelectionRect new]; - if (isVertical) { - end.rect = CGRectMake(endLine.left, offsetEnd, endLine.width, 0); - } else { - end.rect = CGRectMake(offsetEnd, endLine.top, 0, endLine.height); - } - end.containsEnd = YES; - end.isVertical = isVertical; - [rects addObject:end]; - - if (startLine.row == endLine.row) { // same row - if (offsetStart > offsetEnd) ASTEXT_SWAP(offsetStart, offsetEnd); - ASTextSelectionRect *rect = [ASTextSelectionRect new]; - if (isVertical) { - rect.rect = CGRectMake(startLine.bounds.origin.x, offsetStart, MAX(startLine.width, endLine.width), offsetEnd - offsetStart); - } else { - rect.rect = CGRectMake(offsetStart, startLine.bounds.origin.y, offsetEnd - offsetStart, MAX(startLine.height, endLine.height)); - } - rect.isVertical = isVertical; - [rects addObject:rect]; - - } else { // more than one row - - // start line select rect - ASTextSelectionRect *topRect = [ASTextSelectionRect new]; - topRect.isVertical = isVertical; - CGFloat topOffset = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex]; - CTRunRef topRun = [self _runForLine:startLine position:range.start]; - if (topRun && (CTRunGetStatus(topRun) & kCTRunStatusRightToLeft)) { - if (isVertical) { - topRect.rect = CGRectMake(startLine.left, _container.path ? startLine.top : _container.insets.top, startLine.width, topOffset - startLine.top); - } else { - topRect.rect = CGRectMake(_container.path ? startLine.left : _container.insets.left, startLine.top, topOffset - startLine.left, startLine.height); - } - topRect.writingDirection = UITextWritingDirectionRightToLeft; - } else { - if (isVertical) { - topRect.rect = CGRectMake(startLine.left, topOffset, startLine.width, (_container.path ? startLine.bottom : _container.size.height - _container.insets.bottom) - topOffset); - } else { - topRect.rect = CGRectMake(topOffset, startLine.top, (_container.path ? startLine.right : _container.size.width - _container.insets.right) - topOffset, startLine.height); - } - } - [rects addObject:topRect]; - - // end line select rect - ASTextSelectionRect *bottomRect = [ASTextSelectionRect new]; - bottomRect.isVertical = isVertical; - CGFloat bottomOffset = [self offsetForTextPosition:range.end.offset lineIndex:endLineIndex]; - CTRunRef bottomRun = [self _runForLine:endLine position:range.end]; - if (bottomRun && (CTRunGetStatus(bottomRun) & kCTRunStatusRightToLeft)) { - if (isVertical) { - bottomRect.rect = CGRectMake(endLine.left, bottomOffset, endLine.width, (_container.path ? endLine.bottom : _container.size.height - _container.insets.bottom) - bottomOffset); - } else { - bottomRect.rect = CGRectMake(bottomOffset, endLine.top, (_container.path ? endLine.right : _container.size.width - _container.insets.right) - bottomOffset, endLine.height); - } - bottomRect.writingDirection = UITextWritingDirectionRightToLeft; - } else { - if (isVertical) { - CGFloat top = _container.path ? endLine.top : _container.insets.top; - bottomRect.rect = CGRectMake(endLine.left, top, endLine.width, bottomOffset - top); - } else { - CGFloat left = _container.path ? endLine.left : _container.insets.left; - bottomRect.rect = CGRectMake(left, endLine.top, bottomOffset - left, endLine.height); - } - } - [rects addObject:bottomRect]; - - if (endLineIndex - startLineIndex >= 2) { - CGRect r = CGRectZero; - BOOL startLineDetected = NO; - for (NSUInteger l = startLineIndex + 1; l < endLineIndex; l++) { - ASTextLine *line = _lines[l]; - if (line.row == startLine.row || line.row == endLine.row) continue; - if (!startLineDetected) { - r = line.bounds; - startLineDetected = YES; - } else { - r = CGRectUnion(r, line.bounds); - } - } - if (startLineDetected) { - if (isVertical) { - if (!_container.path) { - r.origin.y = _container.insets.top; - r.size.height = _container.size.height - _container.insets.bottom - _container.insets.top; - } - r.size.width = CGRectGetMinX(topRect.rect) - CGRectGetMaxX(bottomRect.rect); - r.origin.x = CGRectGetMaxX(bottomRect.rect); - } else { - if (!_container.path) { - r.origin.x = _container.insets.left; - r.size.width = _container.size.width - _container.insets.right - _container.insets.left; - } - r.origin.y = CGRectGetMaxY(topRect.rect); - r.size.height = bottomRect.rect.origin.y - r.origin.y; - } - - ASTextSelectionRect *rect = [ASTextSelectionRect new]; - rect.rect = r; - rect.isVertical = isVertical; - [rects addObject:rect]; - } - } else { - if (isVertical) { - CGRect r0 = bottomRect.rect; - CGRect r1 = topRect.rect; - CGFloat mid = (CGRectGetMaxX(r0) + CGRectGetMinX(r1)) * 0.5; - r0.size.width = mid - r0.origin.x; - CGFloat r1ofs = r1.origin.x - mid; - r1.origin.x -= r1ofs; - r1.size.width += r1ofs; - topRect.rect = r1; - bottomRect.rect = r0; - } else { - CGRect r0 = topRect.rect; - CGRect r1 = bottomRect.rect; - CGFloat mid = (CGRectGetMaxY(r0) + CGRectGetMinY(r1)) * 0.5; - r0.size.height = mid - r0.origin.y; - CGFloat r1ofs = r1.origin.y - mid; - r1.origin.y -= r1ofs; - r1.size.height += r1ofs; - topRect.rect = r0; - bottomRect.rect = r1; - } - } - } - return rects; -} - -- (NSArray *)selectionRectsWithoutStartAndEndForRange:(ASTextRange *)range { - NSMutableArray *rects = [self selectionRectsForRange:range].mutableCopy; - for (NSInteger i = 0, max = rects.count; i < max; i++) { - ASTextSelectionRect *rect = rects[i]; - if (rect.containsStart || rect.containsEnd) { - [rects removeObjectAtIndex:i]; - i--; - max--; - } - } - return rects; -} - -- (NSArray *)selectionRectsWithOnlyStartAndEndForRange:(ASTextRange *)range { - NSMutableArray *rects = [self selectionRectsForRange:range].mutableCopy; - for (NSInteger i = 0, max = rects.count; i < max; i++) { - ASTextSelectionRect *rect = rects[i]; - if (!rect.containsStart && !rect.containsEnd) { - [rects removeObjectAtIndex:i]; - i--; - max--; - } - } - return rects; -} - - -#pragma mark - Draw - - -typedef NS_OPTIONS(NSUInteger, ASTextDecorationType) { - ASTextDecorationTypeUnderline = 1 << 0, - ASTextDecorationTypeStrikethrough = 1 << 1, -}; - -typedef NS_OPTIONS(NSUInteger, ASTextBorderType) { - ASTextBorderTypeBackgound = 1 << 0, - ASTextBorderTypeNormal = 1 << 1, -}; - -static CGRect ASTextMergeRectInSameLine(CGRect rect1, CGRect rect2, BOOL isVertical) { - if (isVertical) { - CGFloat top = MIN(rect1.origin.y, rect2.origin.y); - CGFloat bottom = MAX(rect1.origin.y + rect1.size.height, rect2.origin.y + rect2.size.height); - CGFloat width = MAX(rect1.size.width, rect2.size.width); - return CGRectMake(rect1.origin.x, top, width, bottom - top); - } else { - CGFloat left = MIN(rect1.origin.x, rect2.origin.x); - CGFloat right = MAX(rect1.origin.x + rect1.size.width, rect2.origin.x + rect2.size.width); - CGFloat height = MAX(rect1.size.height, rect2.size.height); - return CGRectMake(left, rect1.origin.y, right - left, height); - } -} - -static void ASTextGetRunsMaxMetric(CFArrayRef runs, CGFloat *xHeight, CGFloat *underlinePosition, CGFloat *lineThickness) { - CGFloat maxXHeight = 0; - CGFloat maxUnderlinePos = 0; - CGFloat maxLineThickness = 0; - for (NSUInteger i = 0, max = CFArrayGetCount(runs); i < max; i++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, i); - CFDictionaryRef attrs = CTRunGetAttributes(run); - if (attrs) { - CTFontRef font = (CTFontRef)CFDictionaryGetValue(attrs, kCTFontAttributeName); - if (font) { - CGFloat xHeight = CTFontGetXHeight(font); - if (xHeight > maxXHeight) maxXHeight = xHeight; - CGFloat underlinePos = CTFontGetUnderlinePosition(font); - if (underlinePos < maxUnderlinePos) maxUnderlinePos = underlinePos; - CGFloat lineThickness = CTFontGetUnderlineThickness(font); - if (lineThickness > maxLineThickness) maxLineThickness = lineThickness; - } - } - } - if (xHeight) *xHeight = maxXHeight; - if (underlinePosition) *underlinePosition = maxUnderlinePos; - if (lineThickness) *lineThickness = maxLineThickness; -} - -static void ASTextDrawRun(ASTextLine *line, CTRunRef run, CGContextRef context, CGSize size, BOOL isVertical, NSArray *runRanges, CGFloat verticalOffset) { - CGAffineTransform runTextMatrix = CTRunGetTextMatrix(run); - BOOL runTextMatrixIsID = CGAffineTransformIsIdentity(runTextMatrix); - - CFDictionaryRef runAttrs = CTRunGetAttributes(run); - NSValue *glyphTransformValue = (NSValue *)CFDictionaryGetValue(runAttrs, (__bridge const void *)(ASTextGlyphTransformAttributeName)); - if (!isVertical && !glyphTransformValue) { // draw run - if (!runTextMatrixIsID) { - CGContextSaveGState(context); - CGAffineTransform trans = CGContextGetTextMatrix(context); - CGContextSetTextMatrix(context, CGAffineTransformConcat(trans, runTextMatrix)); - } - CTRunDraw(run, context, CFRangeMake(0, 0)); - if (!runTextMatrixIsID) { - CGContextRestoreGState(context); - } - } else { // draw glyph - CTFontRef runFont = (CTFontRef)CFDictionaryGetValue(runAttrs, kCTFontAttributeName); - if (!runFont) return; - NSUInteger glyphCount = CTRunGetGlyphCount(run); - if (glyphCount <= 0) return; - - CGGlyph glyphs[glyphCount]; - CGPoint glyphPositions[glyphCount]; - CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs); - CTRunGetPositions(run, CFRangeMake(0, 0), glyphPositions); - - CGColorRef fillColor = (CGColorRef)CFDictionaryGetValue(runAttrs, kCTForegroundColorAttributeName); - fillColor = ASTextGetCGColor(fillColor); - NSNumber *strokeWidth = (NSNumber *)CFDictionaryGetValue(runAttrs, kCTStrokeWidthAttributeName); - - CGContextSaveGState(context); { - CGContextSetFillColorWithColor(context, fillColor); - if (!strokeWidth || strokeWidth.floatValue == 0) { - CGContextSetTextDrawingMode(context, kCGTextFill); - } else { - CGColorRef strokeColor = (CGColorRef)CFDictionaryGetValue(runAttrs, kCTStrokeColorAttributeName); - if (!strokeColor) strokeColor = fillColor; - CGContextSetStrokeColorWithColor(context, strokeColor); - CGContextSetLineWidth(context, CTFontGetSize(runFont) * fabs(strokeWidth.floatValue * 0.01)); - if (strokeWidth.floatValue > 0) { - CGContextSetTextDrawingMode(context, kCGTextStroke); - } else { - CGContextSetTextDrawingMode(context, kCGTextFillStroke); - } - } - - if (isVertical) { - CFIndex runStrIdx[glyphCount + 1]; - CTRunGetStringIndices(run, CFRangeMake(0, 0), runStrIdx); - CFRange runStrRange = CTRunGetStringRange(run); - runStrIdx[glyphCount] = runStrRange.location + runStrRange.length; - CGSize glyphAdvances[glyphCount]; - CTRunGetAdvances(run, CFRangeMake(0, 0), glyphAdvances); - CGFloat ascent = CTFontGetAscent(runFont); - CGFloat descent = CTFontGetDescent(runFont); - CGAffineTransform glyphTransform = glyphTransformValue.CGAffineTransformValue; - CGPoint zeroPoint = CGPointZero; - - for (ASTextRunGlyphRange *oneRange in runRanges) { - NSRange range = oneRange.glyphRangeInRun; - NSUInteger rangeMax = range.location + range.length; - ASTextRunGlyphDrawMode mode = oneRange.drawMode; - - for (NSUInteger g = range.location; g < rangeMax; g++) { - CGContextSaveGState(context); { - CGContextSetTextMatrix(context, CGAffineTransformIdentity); - if (glyphTransformValue) { - CGContextSetTextMatrix(context, glyphTransform); - } - if (mode) { // CJK glyph, need rotated - CGFloat ofs = (ascent - descent) * 0.5; - CGFloat w = glyphAdvances[g].width * 0.5; - CGFloat x = x = line.position.x + verticalOffset + glyphPositions[g].y + (ofs - w); - CGFloat y = -line.position.y + size.height - glyphPositions[g].x - (ofs + w); - if (mode == ASTextRunGlyphDrawModeVerticalRotateMove) { - x += w; - y += w; - } - CGContextSetTextPosition(context, x, y); - } else { - CGContextRotateCTM(context, -M_PI_2); - CGContextSetTextPosition(context, - line.position.y - size.height + glyphPositions[g].x, - line.position.x + verticalOffset + glyphPositions[g].y); - } - - if (ASTextCTFontContainsColorBitmapGlyphs(runFont)) { - CTFontDrawGlyphs(runFont, glyphs + g, &zeroPoint, 1, context); - } else { - CGFontRef cgFont = CTFontCopyGraphicsFont(runFont, NULL); - CGContextSetFont(context, cgFont); - CGContextSetFontSize(context, CTFontGetSize(runFont)); - CGContextShowGlyphsAtPositions(context, glyphs + g, &zeroPoint, 1); - CGFontRelease(cgFont); - } - } CGContextRestoreGState(context); - } - } - } else { // not vertical - if (glyphTransformValue) { - CFIndex runStrIdx[glyphCount + 1]; - CTRunGetStringIndices(run, CFRangeMake(0, 0), runStrIdx); - CFRange runStrRange = CTRunGetStringRange(run); - runStrIdx[glyphCount] = runStrRange.location + runStrRange.length; - CGSize glyphAdvances[glyphCount]; - CTRunGetAdvances(run, CFRangeMake(0, 0), glyphAdvances); - CGAffineTransform glyphTransform = glyphTransformValue.CGAffineTransformValue; - CGPoint zeroPoint = CGPointZero; - - for (NSUInteger g = 0; g < glyphCount; g++) { - CGContextSaveGState(context); { - CGContextSetTextMatrix(context, CGAffineTransformIdentity); - CGContextSetTextMatrix(context, glyphTransform); - CGContextSetTextPosition(context, - line.position.x + glyphPositions[g].x, - size.height - (line.position.y + glyphPositions[g].y)); - - if (ASTextCTFontContainsColorBitmapGlyphs(runFont)) { - CTFontDrawGlyphs(runFont, glyphs + g, &zeroPoint, 1, context); - } else { - CGFontRef cgFont = CTFontCopyGraphicsFont(runFont, NULL); - CGContextSetFont(context, cgFont); - CGContextSetFontSize(context, CTFontGetSize(runFont)); - CGContextShowGlyphsAtPositions(context, glyphs + g, &zeroPoint, 1); - CGFontRelease(cgFont); - } - } CGContextRestoreGState(context); - } - } else { - if (ASTextCTFontContainsColorBitmapGlyphs(runFont)) { - CTFontDrawGlyphs(runFont, glyphs, glyphPositions, glyphCount, context); - } else { - CGFontRef cgFont = CTFontCopyGraphicsFont(runFont, NULL); - CGContextSetFont(context, cgFont); - CGContextSetFontSize(context, CTFontGetSize(runFont)); - CGContextShowGlyphsAtPositions(context, glyphs, glyphPositions, glyphCount); - CGFontRelease(cgFont); - } - } - } - - } CGContextRestoreGState(context); - } -} - -static void ASTextSetLinePatternInContext(ASTextLineStyle style, CGFloat width, CGFloat phase, CGContextRef context){ - CGContextSetLineWidth(context, width); - CGContextSetLineCap(context, kCGLineCapButt); - CGContextSetLineJoin(context, kCGLineJoinMiter); - - CGFloat dash = 12, dot = 5, space = 3; - NSUInteger pattern = style & 0xF00; - if (pattern == ASTextLineStylePatternSolid) { - CGContextSetLineDash(context, phase, NULL, 0); - } else if (pattern == ASTextLineStylePatternDot) { - CGFloat lengths[2] = {width * dot, width * space}; - CGContextSetLineDash(context, phase, lengths, 2); - } else if (pattern == ASTextLineStylePatternDash) { - CGFloat lengths[2] = {width * dash, width * space}; - CGContextSetLineDash(context, phase, lengths, 2); - } else if (pattern == ASTextLineStylePatternDashDot) { - CGFloat lengths[4] = {width * dash, width * space, width * dot, width * space}; - CGContextSetLineDash(context, phase, lengths, 4); - } else if (pattern == ASTextLineStylePatternDashDotDot) { - CGFloat lengths[6] = {width * dash, width * space,width * dot, width * space, width * dot, width * space}; - CGContextSetLineDash(context, phase, lengths, 6); - } else if (pattern == ASTextLineStylePatternCircleDot) { - CGFloat lengths[2] = {width * 0, width * 3}; - CGContextSetLineDash(context, phase, lengths, 2); - CGContextSetLineCap(context, kCGLineCapRound); - CGContextSetLineJoin(context, kCGLineJoinRound); - } -} - - -static void ASTextDrawBorderRects(CGContextRef context, CGSize size, ASTextBorder *border, NSArray *rects, BOOL isVertical) { - if (rects.count == 0) return; - - ASTextShadow *shadow = border.shadow; - if (shadow.color) { - CGContextSaveGState(context); - CGContextSetShadowWithColor(context, shadow.offset, shadow.radius, shadow.color.CGColor); - CGContextBeginTransparencyLayer(context, NULL); - } - - NSMutableArray *paths = [NSMutableArray new]; - for (NSValue *value in rects) { - CGRect rect = value.CGRectValue; - if (isVertical) { - rect = UIEdgeInsetsInsetRect(rect, UIEdgeInsetRotateVertical(border.insets)); - } else { - rect = UIEdgeInsetsInsetRect(rect, border.insets); - } - rect = ASTextCGRectPixelRound(rect); - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:border.cornerRadius]; - [path closePath]; - [paths addObject:path]; - } - - if (border.fillColor) { - CGContextSaveGState(context); - CGContextSetFillColorWithColor(context, border.fillColor.CGColor); - for (UIBezierPath *path in paths) { - CGContextAddPath(context, path.CGPath); - } - CGContextFillPath(context); - CGContextRestoreGState(context); - } - - if (border.strokeColor && border.lineStyle > 0 && border.strokeWidth > 0) { - - //-------------------------- single line ------------------------------// - CGContextSaveGState(context); - for (UIBezierPath *path in paths) { - CGRect bounds = CGRectUnion(path.bounds, (CGRect){CGPointZero, size}); - bounds = CGRectInset(bounds, -2 * border.strokeWidth, -2 * border.strokeWidth); - CGContextAddRect(context, bounds); - CGContextAddPath(context, path.CGPath); - CGContextEOClip(context); - } - [border.strokeColor setStroke]; - ASTextSetLinePatternInContext(border.lineStyle, border.strokeWidth, 0, context); - CGFloat inset = -border.strokeWidth * 0.5; - if ((border.lineStyle & 0xFF) == ASTextLineStyleThick) { - inset *= 2; - CGContextSetLineWidth(context, border.strokeWidth * 2); - } - CGFloat radiusDelta = -inset; - if (border.cornerRadius <= 0) { - radiusDelta = 0; - } - CGContextSetLineJoin(context, border.lineJoin); - for (NSValue *value in rects) { - CGRect rect = value.CGRectValue; - if (isVertical) { - rect = UIEdgeInsetsInsetRect(rect, UIEdgeInsetRotateVertical(border.insets)); - } else { - rect = UIEdgeInsetsInsetRect(rect, border.insets); - } - rect = CGRectInset(rect, inset, inset); - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:border.cornerRadius + radiusDelta]; - [path closePath]; - CGContextAddPath(context, path.CGPath); - } - CGContextStrokePath(context); - CGContextRestoreGState(context); - - //------------------------- second line ------------------------------// - if ((border.lineStyle & 0xFF) == ASTextLineStyleDouble) { - CGContextSaveGState(context); - CGFloat inset = -border.strokeWidth * 2; - for (NSValue *value in rects) { - CGRect rect = value.CGRectValue; - rect = UIEdgeInsetsInsetRect(rect, border.insets); - rect = CGRectInset(rect, inset, inset); - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:border.cornerRadius + 2 * border.strokeWidth]; - [path closePath]; - - CGRect bounds = CGRectUnion(path.bounds, (CGRect){CGPointZero, size}); - bounds = CGRectInset(bounds, -2 * border.strokeWidth, -2 * border.strokeWidth); - CGContextAddRect(context, bounds); - CGContextAddPath(context, path.CGPath); - CGContextEOClip(context); - } - CGContextSetStrokeColorWithColor(context, border.strokeColor.CGColor); - ASTextSetLinePatternInContext(border.lineStyle, border.strokeWidth, 0, context); - CGContextSetLineJoin(context, border.lineJoin); - inset = -border.strokeWidth * 2.5; - radiusDelta = border.strokeWidth * 2; - if (border.cornerRadius <= 0) { - radiusDelta = 0; - } - for (NSValue *value in rects) { - CGRect rect = value.CGRectValue; - rect = UIEdgeInsetsInsetRect(rect, border.insets); - rect = CGRectInset(rect, inset, inset); - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:border.cornerRadius + radiusDelta]; - [path closePath]; - CGContextAddPath(context, path.CGPath); - } - CGContextStrokePath(context); - CGContextRestoreGState(context); - } - } - - if (shadow.color) { - CGContextEndTransparencyLayer(context); - CGContextRestoreGState(context); - } -} - -static void ASTextDrawLineStyle(CGContextRef context, CGFloat length, CGFloat lineWidth, ASTextLineStyle style, CGPoint position, CGColorRef color, BOOL isVertical) { - NSUInteger styleBase = style & 0xFF; - if (styleBase == 0) return; - - CGContextSaveGState(context); { - if (isVertical) { - CGFloat x, y1, y2, w; - y1 = ASRoundPixelValue(position.y); - y2 = ASRoundPixelValue(position.y + length); - w = (styleBase == ASTextLineStyleThick ? lineWidth * 2 : lineWidth); - - CGFloat linePixel = ASTextCGFloatToPixel(w); - if (fabs(linePixel - floor(linePixel)) < 0.1) { - int iPixel = linePixel; - if (iPixel == 0 || (iPixel % 2)) { // odd line pixel - x = ASTextCGFloatPixelHalf(position.x); - } else { - x = ASFloorPixelValue(position.x); - } - } else { - x = position.x; - } - - CGContextSetStrokeColorWithColor(context, color); - ASTextSetLinePatternInContext(style, lineWidth, position.y, context); - CGContextSetLineWidth(context, w); - if (styleBase == ASTextLineStyleSingle) { - CGContextMoveToPoint(context, x, y1); - CGContextAddLineToPoint(context, x, y2); - CGContextStrokePath(context); - } else if (styleBase == ASTextLineStyleThick) { - CGContextMoveToPoint(context, x, y1); - CGContextAddLineToPoint(context, x, y2); - CGContextStrokePath(context); - } else if (styleBase == ASTextLineStyleDouble) { - CGContextMoveToPoint(context, x - w, y1); - CGContextAddLineToPoint(context, x - w, y2); - CGContextStrokePath(context); - CGContextMoveToPoint(context, x + w, y1); - CGContextAddLineToPoint(context, x + w, y2); - CGContextStrokePath(context); - } - } else { - CGFloat x1, x2, y, w; - x1 = ASRoundPixelValue(position.x); - x2 = ASRoundPixelValue(position.x + length); - w = (styleBase == ASTextLineStyleThick ? lineWidth * 2 : lineWidth); - - CGFloat linePixel = ASTextCGFloatToPixel(w); - if (fabs(linePixel - floor(linePixel)) < 0.1) { - int iPixel = linePixel; - if (iPixel == 0 || (iPixel % 2)) { // odd line pixel - y = ASTextCGFloatPixelHalf(position.y); - } else { - y = ASFloorPixelValue(position.y); - } - } else { - y = position.y; - } - - CGContextSetStrokeColorWithColor(context, color); - ASTextSetLinePatternInContext(style, lineWidth, position.x, context); - CGContextSetLineWidth(context, w); - if (styleBase == ASTextLineStyleSingle) { - CGContextMoveToPoint(context, x1, y); - CGContextAddLineToPoint(context, x2, y); - CGContextStrokePath(context); - } else if (styleBase == ASTextLineStyleThick) { - CGContextMoveToPoint(context, x1, y); - CGContextAddLineToPoint(context, x2, y); - CGContextStrokePath(context); - } else if (styleBase == ASTextLineStyleDouble) { - CGContextMoveToPoint(context, x1, y - w); - CGContextAddLineToPoint(context, x2, y - w); - CGContextStrokePath(context); - CGContextMoveToPoint(context, x1, y + w); - CGContextAddLineToPoint(context, x2, y + w); - CGContextStrokePath(context); - } - } - } CGContextRestoreGState(context); -} - -static void ASTextDrawText(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, BOOL (^cancel)(void)) { - CGContextSaveGState(context); { - - CGContextTranslateCTM(context, point.x, point.y); - CGContextTranslateCTM(context, 0, size.height); - CGContextScaleCTM(context, 1, -1); - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - - NSArray *lines = layout.lines; - for (NSUInteger l = 0, lMax = lines.count; l < lMax; l++) { - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - NSArray *lineRunRanges = line.verticalRotateRange; - CGFloat posX = line.position.x + verticalOffset; - CGFloat posY = size.height - line.position.y; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CGContextSetTextMatrix(context, CGAffineTransformIdentity); - CGContextSetTextPosition(context, posX, posY); - ASTextDrawRun(line, run, context, size, isVertical, lineRunRanges[r], verticalOffset); - } - if (cancel && cancel()) break; - } - - // Use this to draw frame for test/debug. - // CGContextTranslateCTM(context, verticalOffset, size.height); - // CTFrameDraw(layout.frame, context); - - } CGContextRestoreGState(context); -} - -static void ASTextDrawBlockBorder(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, BOOL (^cancel)(void)) { - CGContextSaveGState(context); - CGContextTranslateCTM(context, point.x, point.y); - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - - NSArray *lines = layout.lines; - for (NSInteger l = 0, lMax = lines.count; l < lMax; l++) { - if (cancel && cancel()) break; - - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CFIndex glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - NSDictionary *attrs = (id)CTRunGetAttributes(run); - ASTextBorder *border = attrs[ASTextBlockBorderAttributeName]; - if (!border) continue; - - NSUInteger lineStartIndex = line.index; - while (lineStartIndex > 0) { - if (((ASTextLine *)lines[lineStartIndex - 1]).row == line.row) lineStartIndex--; - else break; - } - - CGRect unionRect = CGRectZero; - NSUInteger lineStartRow = ((ASTextLine *)lines[lineStartIndex]).row; - NSUInteger lineContinueIndex = lineStartIndex; - NSUInteger lineContinueRow = lineStartRow; - do { - ASTextLine *one = lines[lineContinueIndex]; - if (lineContinueIndex == lineStartIndex) { - unionRect = one.bounds; - } else { - unionRect = CGRectUnion(unionRect, one.bounds); - } - if (lineContinueIndex + 1 == lMax) break; - ASTextLine *next = lines[lineContinueIndex + 1]; - if (next.row != lineContinueRow) { - ASTextBorder *nextBorder = [layout.text as_attribute:ASTextBlockBorderAttributeName atIndex:next.range.location]; - if ([nextBorder isEqual:border]) { - lineContinueRow++; - } else { - break; - } - } - lineContinueIndex++; - } while (true); - - if (isVertical) { - UIEdgeInsets insets = layout.container.insets; - unionRect.origin.y = insets.top; - unionRect.size.height = layout.container.size.height -insets.top - insets.bottom; - } else { - UIEdgeInsets insets = layout.container.insets; - unionRect.origin.x = insets.left; - unionRect.size.width = layout.container.size.width -insets.left - insets.right; - } - unionRect.origin.x += verticalOffset; - ASTextDrawBorderRects(context, size, border, @[[NSValue valueWithCGRect:unionRect]], isVertical); - - l = lineContinueIndex; - break; - } - } - - - CGContextRestoreGState(context); -} - -static void ASTextDrawBorder(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, ASTextBorderType type, BOOL (^cancel)(void)) { - CGContextSaveGState(context); - CGContextTranslateCTM(context, point.x, point.y); - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - - NSArray *lines = layout.lines; - NSString *borderKey = (type == ASTextBorderTypeNormal ? ASTextBorderAttributeName : ASTextBackgroundBorderAttributeName); - - BOOL needJumpRun = NO; - NSUInteger jumpRunIndex = 0; - - for (NSInteger l = 0, lMax = lines.count; l < lMax; l++) { - if (cancel && cancel()) break; - - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - if (needJumpRun) { - needJumpRun = NO; - r = jumpRunIndex + 1; - if (r >= rMax) break; - } - - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CFIndex glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - - NSDictionary *attrs = (id)CTRunGetAttributes(run); - ASTextBorder *border = attrs[borderKey]; - if (!border) continue; - - CFRange runRange = CTRunGetStringRange(run); - if (runRange.location == kCFNotFound || runRange.length == 0) continue; - if (runRange.location + runRange.length > layout.text.length) continue; - - NSMutableArray *runRects = [NSMutableArray new]; - NSInteger endLineIndex = l; - NSInteger endRunIndex = r; - BOOL endFound = NO; - for (NSInteger ll = l; ll < lMax; ll++) { - if (endFound) break; - ASTextLine *iLine = lines[ll]; - CFArrayRef iRuns = CTLineGetGlyphRuns(iLine.CTLine); - - CGRect extLineRect = CGRectNull; - for (NSInteger rr = (ll == l) ? r : 0, rrMax = CFArrayGetCount(iRuns); rr < rrMax; rr++) { - CTRunRef iRun = (CTRunRef)CFArrayGetValueAtIndex(iRuns, rr); - NSDictionary *iAttrs = (id)CTRunGetAttributes(iRun); - ASTextBorder *iBorder = iAttrs[borderKey]; - if (![border isEqual:iBorder]) { - endFound = YES; - break; - } - endLineIndex = ll; - endRunIndex = rr; - - CGPoint iRunPosition = CGPointZero; - CTRunGetPositions(iRun, CFRangeMake(0, 1), &iRunPosition); - CGFloat ascent, descent; - CGFloat iRunWidth = CTRunGetTypographicBounds(iRun, CFRangeMake(0, 0), &ascent, &descent, NULL); - - if (isVertical) { - ASTEXT_SWAP(iRunPosition.x, iRunPosition.y); - iRunPosition.y += iLine.position.y; - CGRect iRect = CGRectMake(verticalOffset + line.position.x - descent, iRunPosition.y, ascent + descent, iRunWidth); - if (CGRectIsNull(extLineRect)) { - extLineRect = iRect; - } else { - extLineRect = CGRectUnion(extLineRect, iRect); - } - } else { - iRunPosition.x += iLine.position.x; - CGRect iRect = CGRectMake(iRunPosition.x, iLine.position.y - ascent, iRunWidth, ascent + descent); - if (CGRectIsNull(extLineRect)) { - extLineRect = iRect; - } else { - extLineRect = CGRectUnion(extLineRect, iRect); - } - } - } - - if (!CGRectIsNull(extLineRect)) { - [runRects addObject:[NSValue valueWithCGRect:extLineRect]]; - } - } - - NSMutableArray *drawRects = [NSMutableArray new]; - CGRect curRect= ((NSValue *)[runRects firstObject]).CGRectValue; - for (NSInteger re = 0, reMax = runRects.count; re < reMax; re++) { - CGRect rect = ((NSValue *)runRects[re]).CGRectValue; - if (isVertical) { - if (fabs(rect.origin.x - curRect.origin.x) < 1) { - curRect = ASTextMergeRectInSameLine(rect, curRect, isVertical); - } else { - [drawRects addObject:[NSValue valueWithCGRect:curRect]]; - curRect = rect; - } - } else { - if (fabs(rect.origin.y - curRect.origin.y) < 1) { - curRect = ASTextMergeRectInSameLine(rect, curRect, isVertical); - } else { - [drawRects addObject:[NSValue valueWithCGRect:curRect]]; - curRect = rect; - } - } - } - if (!CGRectEqualToRect(curRect, CGRectZero)) { - [drawRects addObject:[NSValue valueWithCGRect:curRect]]; - } - - ASTextDrawBorderRects(context, size, border, drawRects, isVertical); - - if (l == endLineIndex) { - r = endRunIndex; - } else { - l = endLineIndex - 1; - needJumpRun = YES; - jumpRunIndex = endRunIndex; - break; - } - - } - } - - CGContextRestoreGState(context); -} - -static void ASTextDrawDecoration(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, ASTextDecorationType type, BOOL (^cancel)(void)) { - NSArray *lines = layout.lines; - - CGContextSaveGState(context); - CGContextTranslateCTM(context, point.x, point.y); - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - CGContextTranslateCTM(context, verticalOffset, 0); - - for (NSUInteger l = 0, lMax = layout.lines.count; l < lMax; l++) { - if (cancel && cancel()) break; - - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CFIndex glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - - NSDictionary *attrs = (id)CTRunGetAttributes(run); - ASTextDecoration *underline = attrs[ASTextUnderlineAttributeName]; - ASTextDecoration *strikethrough = attrs[ASTextStrikethroughAttributeName]; - - BOOL needDrawUnderline = NO, needDrawStrikethrough = NO; - if ((type & ASTextDecorationTypeUnderline) && underline.style > 0) { - needDrawUnderline = YES; - } - if ((type & ASTextDecorationTypeStrikethrough) && strikethrough.style > 0) { - needDrawStrikethrough = YES; - } - if (!needDrawUnderline && !needDrawStrikethrough) continue; - - CFRange runRange = CTRunGetStringRange(run); - if (runRange.location == kCFNotFound || runRange.length == 0) continue; - if (runRange.location + runRange.length > layout.text.length) continue; - NSString *runStr = [layout.text attributedSubstringFromRange:NSMakeRange(runRange.location, runRange.length)].string; - if (ASTextIsLinebreakString(runStr)) continue; // may need more checks... - - CGFloat xHeight, underlinePosition, lineThickness; - ASTextGetRunsMaxMetric(runs, &xHeight, &underlinePosition, &lineThickness); - - CGPoint underlineStart, strikethroughStart; - CGFloat length; - - if (isVertical) { - underlineStart.x = line.position.x + underlinePosition; - strikethroughStart.x = line.position.x + xHeight / 2; - - CGPoint runPosition = CGPointZero; - CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition); - underlineStart.y = strikethroughStart.y = runPosition.x + line.position.y; - length = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL); - - } else { - underlineStart.y = line.position.y - underlinePosition; - strikethroughStart.y = line.position.y - xHeight / 2; - - CGPoint runPosition = CGPointZero; - CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition); - underlineStart.x = strikethroughStart.x = runPosition.x + line.position.x; - length = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL); - } - - if (needDrawUnderline) { - CGColorRef color = underline.color.CGColor; - if (!color) { - color = (__bridge CGColorRef)(attrs[(id)kCTForegroundColorAttributeName]); - color = ASTextGetCGColor(color); - } - CGFloat thickness = underline.width ? underline.width.floatValue : lineThickness; - ASTextShadow *shadow = underline.shadow; - while (shadow) { - if (!shadow.color) { - shadow = shadow.subShadow; - continue; - } - CGFloat offsetAlterX = size.width + 0xFFFF; - CGContextSaveGState(context); { - CGSize offset = shadow.offset; - offset.width -= offsetAlterX; - CGContextSaveGState(context); { - CGContextSetShadowWithColor(context, offset, shadow.radius, shadow.color.CGColor); - CGContextSetBlendMode(context, shadow.blendMode); - CGContextTranslateCTM(context, offsetAlterX, 0); - ASTextDrawLineStyle(context, length, thickness, underline.style, underlineStart, color, isVertical); - } CGContextRestoreGState(context); - } CGContextRestoreGState(context); - shadow = shadow.subShadow; - } - ASTextDrawLineStyle(context, length, thickness, underline.style, underlineStart, color, isVertical); - } - - if (needDrawStrikethrough) { - CGColorRef color = strikethrough.color.CGColor; - if (!color) { - color = (__bridge CGColorRef)(attrs[(id)kCTForegroundColorAttributeName]); - color = ASTextGetCGColor(color); - } - CGFloat thickness = strikethrough.width ? strikethrough.width.floatValue : lineThickness; - ASTextShadow *shadow = underline.shadow; - while (shadow) { - if (!shadow.color) { - shadow = shadow.subShadow; - continue; - } - CGFloat offsetAlterX = size.width + 0xFFFF; - CGContextSaveGState(context); { - CGSize offset = shadow.offset; - offset.width -= offsetAlterX; - CGContextSaveGState(context); { - CGContextSetShadowWithColor(context, offset, shadow.radius, shadow.color.CGColor); - CGContextSetBlendMode(context, shadow.blendMode); - CGContextTranslateCTM(context, offsetAlterX, 0); - ASTextDrawLineStyle(context, length, thickness, underline.style, underlineStart, color, isVertical); - } CGContextRestoreGState(context); - } CGContextRestoreGState(context); - shadow = shadow.subShadow; - } - ASTextDrawLineStyle(context, length, thickness, strikethrough.style, strikethroughStart, color, isVertical); - } - } - } - CGContextRestoreGState(context); -} - -static void ASTextDrawAttachment(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, UIView *targetView, CALayer *targetLayer, BOOL (^cancel)(void)) { - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - - for (NSUInteger i = 0, max = layout.attachments.count; i < max; i++) { - ASTextAttachment *a = layout.attachments[i]; - if (!a.content) continue; - - UIImage *image = nil; - UIView *view = nil; - CALayer *layer = nil; - if ([a.content isKindOfClass:[UIImage class]]) { - image = a.content; - } else if ([a.content isKindOfClass:[UIView class]]) { - view = a.content; - } else if ([a.content isKindOfClass:[CALayer class]]) { - layer = a.content; - } - if (!image && !view && !layer) continue; - if (image && !context) continue; - if (view && !targetView) continue; - if (layer && !targetLayer) continue; - if (cancel && cancel()) break; - - CGSize asize = image ? image.size : view ? view.frame.size : layer.frame.size; - CGRect rect = ((NSValue *)layout.attachmentRects[i]).CGRectValue; - if (isVertical) { - rect = UIEdgeInsetsInsetRect(rect, UIEdgeInsetRotateVertical(a.contentInsets)); - } else { - rect = UIEdgeInsetsInsetRect(rect, a.contentInsets); - } - rect = ASTextCGRectFitWithContentMode(rect, asize, a.contentMode); - rect = ASTextCGRectPixelRound(rect); - rect = CGRectStandardize(rect); - rect.origin.x += point.x + verticalOffset; - rect.origin.y += point.y; - if (image) { - CGImageRef ref = image.CGImage; - if (ref) { - CGContextSaveGState(context); - CGContextTranslateCTM(context, 0, CGRectGetMaxY(rect) + CGRectGetMinY(rect)); - CGContextScaleCTM(context, 1, -1); - CGContextDrawImage(context, rect, ref); - CGContextRestoreGState(context); - } - } else if (view) { - view.frame = rect; - [targetView addSubview:view]; - } else if (layer) { - layer.frame = rect; - [targetLayer addSublayer:layer]; - } - } -} - -static void ASTextDrawShadow(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, BOOL (^cancel)(void)) { - //move out of context. (0xFFFF is just a random large number) - CGFloat offsetAlterX = size.width + 0xFFFF; - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - - CGContextSaveGState(context); { - CGContextTranslateCTM(context, point.x, point.y); - CGContextTranslateCTM(context, 0, size.height); - CGContextScaleCTM(context, 1, -1); - NSArray *lines = layout.lines; - for (NSUInteger l = 0, lMax = layout.lines.count; l < lMax; l++) { - if (cancel && cancel()) break; - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - NSArray *lineRunRanges = line.verticalRotateRange; - CGFloat linePosX = line.position.x; - CGFloat linePosY = size.height - line.position.y; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CGContextSetTextMatrix(context, CGAffineTransformIdentity); - CGContextSetTextPosition(context, linePosX, linePosY); - NSDictionary *attrs = (id)CTRunGetAttributes(run); - ASTextShadow *shadow = attrs[ASTextShadowAttributeName]; - ASTextShadow *nsShadow = [ASTextShadow shadowWithNSShadow:attrs[NSShadowAttributeName]]; // NSShadow compatible - if (nsShadow) { - nsShadow.subShadow = shadow; - shadow = nsShadow; - } - while (shadow) { - if (!shadow.color) { - shadow = shadow.subShadow; - continue; - } - CGSize offset = shadow.offset; - offset.width -= offsetAlterX; - CGContextSaveGState(context); { - CGContextSetShadowWithColor(context, offset, shadow.radius, shadow.color.CGColor); - CGContextSetBlendMode(context, shadow.blendMode); - CGContextTranslateCTM(context, offsetAlterX, 0); - ASTextDrawRun(line, run, context, size, isVertical, lineRunRanges[r], verticalOffset); - } CGContextRestoreGState(context); - shadow = shadow.subShadow; - } - } - } - } CGContextRestoreGState(context); -} - -static void ASTextDrawInnerShadow(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, BOOL (^cancel)(void)) { - CGContextSaveGState(context); - CGContextTranslateCTM(context, point.x, point.y); - CGContextTranslateCTM(context, 0, size.height); - CGContextScaleCTM(context, 1, -1); - CGContextSetTextMatrix(context, CGAffineTransformIdentity); - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - - NSArray *lines = layout.lines; - for (NSUInteger l = 0, lMax = lines.count; l < lMax; l++) { - if (cancel && cancel()) break; - - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - NSArray *lineRunRanges = line.verticalRotateRange; - CGFloat linePosX = line.position.x; - CGFloat linePosY = size.height - line.position.y; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - if (CTRunGetGlyphCount(run) == 0) continue; - CGContextSetTextMatrix(context, CGAffineTransformIdentity); - CGContextSetTextPosition(context, linePosX, linePosY); - NSDictionary *attrs = (id)CTRunGetAttributes(run); - ASTextShadow *shadow = attrs[ASTextInnerShadowAttributeName]; - while (shadow) { - if (!shadow.color) { - shadow = shadow.subShadow; - continue; - } - CGPoint runPosition = CGPointZero; - CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition); - CGRect runImageBounds = CTRunGetImageBounds(run, context, CFRangeMake(0, 0)); - runImageBounds.origin.x += runPosition.x; - if (runImageBounds.size.width < 0.1 || runImageBounds.size.height < 0.1) continue; - - CFDictionaryRef runAttrs = CTRunGetAttributes(run); - NSValue *glyphTransformValue = (NSValue *)CFDictionaryGetValue(runAttrs, (__bridge const void *)(ASTextGlyphTransformAttributeName)); - if (glyphTransformValue) { - runImageBounds = CGRectMake(0, 0, size.width, size.height); - } - - // text inner shadow - CGContextSaveGState(context); { - CGContextSetBlendMode(context, shadow.blendMode); - CGContextSetShadowWithColor(context, CGSizeZero, 0, NULL); - CGContextSetAlpha(context, CGColorGetAlpha(shadow.color.CGColor)); - CGContextClipToRect(context, runImageBounds); - CGContextBeginTransparencyLayer(context, NULL); { - UIColor *opaqueShadowColor = [shadow.color colorWithAlphaComponent:1]; - CGContextSetShadowWithColor(context, shadow.offset, shadow.radius, opaqueShadowColor.CGColor); - CGContextSetFillColorWithColor(context, opaqueShadowColor.CGColor); - CGContextSetBlendMode(context, kCGBlendModeSourceOut); - CGContextBeginTransparencyLayer(context, NULL); { - CGContextFillRect(context, runImageBounds); - CGContextSetBlendMode(context, kCGBlendModeDestinationIn); - CGContextBeginTransparencyLayer(context, NULL); { - ASTextDrawRun(line, run, context, size, isVertical, lineRunRanges[r], verticalOffset); - } CGContextEndTransparencyLayer(context); - } CGContextEndTransparencyLayer(context); - } CGContextEndTransparencyLayer(context); - } CGContextRestoreGState(context); - shadow = shadow.subShadow; - } - } - } - - CGContextRestoreGState(context); -} - -static void ASTextDrawDebug(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, ASTextDebugOption *op) { - UIGraphicsPushContext(context); - CGContextSaveGState(context); - CGContextTranslateCTM(context, point.x, point.y); - CGContextSetLineWidth(context, 1.0 / ASScreenScale()); - CGContextSetLineDash(context, 0, NULL, 0); - CGContextSetLineJoin(context, kCGLineJoinMiter); - CGContextSetLineCap(context, kCGLineCapButt); - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - CGContextTranslateCTM(context, verticalOffset, 0); - - if (op.CTFrameBorderColor || op.CTFrameFillColor) { - UIBezierPath *path = layout.container.path; - if (!path) { - CGRect rect = (CGRect){CGPointZero, layout.container.size}; - rect = UIEdgeInsetsInsetRect(rect, layout.container.insets); - if (op.CTFrameBorderColor) rect = ASTextCGRectPixelHalf(rect); - else rect = ASTextCGRectPixelRound(rect); - path = [UIBezierPath bezierPathWithRect:rect]; - } - [path closePath]; - - for (UIBezierPath *ex in layout.container.exclusionPaths) { - [path appendPath:ex]; - } - if (op.CTFrameFillColor) { - [op.CTFrameFillColor setFill]; - if (layout.container.pathLineWidth > 0) { - CGContextSaveGState(context); { - CGContextBeginTransparencyLayer(context, NULL); { - CGContextAddPath(context, path.CGPath); - if (layout.container.pathFillEvenOdd) { - CGContextEOFillPath(context); - } else { - CGContextFillPath(context); - } - CGContextSetBlendMode(context, kCGBlendModeDestinationOut); - [[UIColor blackColor] setFill]; - CGPathRef cgPath = CGPathCreateCopyByStrokingPath(path.CGPath, NULL, layout.container.pathLineWidth, kCGLineCapButt, kCGLineJoinMiter, 0); - if (cgPath) { - CGContextAddPath(context, cgPath); - CGContextFillPath(context); - } - CGPathRelease(cgPath); - } CGContextEndTransparencyLayer(context); - } CGContextRestoreGState(context); - } else { - CGContextAddPath(context, path.CGPath); - if (layout.container.pathFillEvenOdd) { - CGContextEOFillPath(context); - } else { - CGContextFillPath(context); - } - } - } - if (op.CTFrameBorderColor) { - CGContextSaveGState(context); { - if (layout.container.pathLineWidth > 0) { - CGContextSetLineWidth(context, layout.container.pathLineWidth); - } - [op.CTFrameBorderColor setStroke]; - CGContextAddPath(context, path.CGPath); - CGContextStrokePath(context); - } CGContextRestoreGState(context); - } - } - - NSArray *lines = layout.lines; - for (NSUInteger l = 0, lMax = lines.count; l < lMax; l++) { - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - CGRect lineBounds = line.bounds; - if (op.CTLineFillColor) { - [op.CTLineFillColor setFill]; - CGContextAddRect(context, ASTextCGRectPixelRound(lineBounds)); - CGContextFillPath(context); - } - if (op.CTLineBorderColor) { - [op.CTLineBorderColor setStroke]; - CGContextAddRect(context, ASTextCGRectPixelHalf(lineBounds)); - CGContextStrokePath(context); - } - if (op.baselineColor) { - [op.baselineColor setStroke]; - if (isVertical) { - CGFloat x = ASTextCGFloatPixelHalf(line.position.x); - CGFloat y1 = ASTextCGFloatPixelHalf(line.top); - CGFloat y2 = ASTextCGFloatPixelHalf(line.bottom); - CGContextMoveToPoint(context, x, y1); - CGContextAddLineToPoint(context, x, y2); - CGContextStrokePath(context); - } else { - CGFloat x1 = ASTextCGFloatPixelHalf(lineBounds.origin.x); - CGFloat x2 = ASTextCGFloatPixelHalf(lineBounds.origin.x + lineBounds.size.width); - CGFloat y = ASTextCGFloatPixelHalf(line.position.y); - CGContextMoveToPoint(context, x1, y); - CGContextAddLineToPoint(context, x2, y); - CGContextStrokePath(context); - } - } - if (op.CTLineNumberColor) { - [op.CTLineNumberColor set]; - NSMutableAttributedString *num = [[NSMutableAttributedString alloc] initWithString:@(l).description]; - num.as_color = op.CTLineNumberColor; - num.as_font = [UIFont systemFontOfSize:6]; - [num drawAtPoint:CGPointMake(line.position.x, line.position.y - (isVertical ? 1 : 6))]; - } - if (op.CTRunFillColor || op.CTRunBorderColor || op.CTRunNumberColor || op.CGGlyphFillColor || op.CGGlyphBorderColor) { - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CFIndex glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - - CGPoint glyphPositions[glyphCount]; - CTRunGetPositions(run, CFRangeMake(0, glyphCount), glyphPositions); - - CGSize glyphAdvances[glyphCount]; - CTRunGetAdvances(run, CFRangeMake(0, glyphCount), glyphAdvances); - - CGPoint runPosition = glyphPositions[0]; - if (isVertical) { - ASTEXT_SWAP(runPosition.x, runPosition.y); - runPosition.x = line.position.x; - runPosition.y += line.position.y; - } else { - runPosition.x += line.position.x; - runPosition.y = line.position.y - runPosition.y; - } - - CGFloat ascent, descent, leading; - CGFloat width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, &leading); - CGRect runTypoBounds; - if (isVertical) { - runTypoBounds = CGRectMake(runPosition.x - descent, runPosition.y, ascent + descent, width); - } else { - runTypoBounds = CGRectMake(runPosition.x, line.position.y - ascent, width, ascent + descent); - } - - if (op.CTRunFillColor) { - [op.CTRunFillColor setFill]; - CGContextAddRect(context, ASTextCGRectPixelRound(runTypoBounds)); - CGContextFillPath(context); - } - if (op.CTRunBorderColor) { - [op.CTRunBorderColor setStroke]; - CGContextAddRect(context, ASTextCGRectPixelHalf(runTypoBounds)); - CGContextStrokePath(context); - } - if (op.CTRunNumberColor) { - [op.CTRunNumberColor set]; - NSMutableAttributedString *num = [[NSMutableAttributedString alloc] initWithString:@(r).description]; - num.as_color = op.CTRunNumberColor; - num.as_font = [UIFont systemFontOfSize:6]; - [num drawAtPoint:CGPointMake(runTypoBounds.origin.x, runTypoBounds.origin.y - 1)]; - } - if (op.CGGlyphBorderColor || op.CGGlyphFillColor) { - for (NSUInteger g = 0; g < glyphCount; g++) { - CGPoint pos = glyphPositions[g]; - CGSize adv = glyphAdvances[g]; - CGRect rect; - if (isVertical) { - ASTEXT_SWAP(pos.x, pos.y); - pos.x = runPosition.x; - pos.y += line.position.y; - rect = CGRectMake(pos.x - descent, pos.y, runTypoBounds.size.width, adv.width); - } else { - pos.x += line.position.x; - pos.y = runPosition.y; - rect = CGRectMake(pos.x, pos.y - ascent, adv.width, runTypoBounds.size.height); - } - if (op.CGGlyphFillColor) { - [op.CGGlyphFillColor setFill]; - CGContextAddRect(context, ASTextCGRectPixelRound(rect)); - CGContextFillPath(context); - } - if (op.CGGlyphBorderColor) { - [op.CGGlyphBorderColor setStroke]; - CGContextAddRect(context, ASTextCGRectPixelHalf(rect)); - CGContextStrokePath(context); - } - } - } - } - } - } - CGContextRestoreGState(context); - UIGraphicsPopContext(); -} - - -- (void)drawInContext:(CGContextRef)context - size:(CGSize)size - point:(CGPoint)point - view:(UIView *)view - layer:(CALayer *)layer - debug:(ASTextDebugOption *)debug - cancel:(BOOL (^)(void))cancel{ - @autoreleasepool { - if (self.needDrawBlockBorder && context) { - if (cancel && cancel()) return; - ASTextDrawBlockBorder(self, context, size, point, cancel); - } - if (self.needDrawBackgroundBorder && context) { - if (cancel && cancel()) return; - ASTextDrawBorder(self, context, size, point, ASTextBorderTypeBackgound, cancel); - } - if (self.needDrawShadow && context) { - if (cancel && cancel()) return; - ASTextDrawShadow(self, context, size, point, cancel); - } - if (self.needDrawUnderline && context) { - if (cancel && cancel()) return; - ASTextDrawDecoration(self, context, size, point, ASTextDecorationTypeUnderline, cancel); - } - if (self.needDrawText && context) { - if (cancel && cancel()) return; - ASTextDrawText(self, context, size, point, cancel); - } - if (self.needDrawAttachment && (context || view || layer)) { - if (cancel && cancel()) return; - ASTextDrawAttachment(self, context, size, point, view, layer, cancel); - } - if (self.needDrawInnerShadow && context) { - if (cancel && cancel()) return; - ASTextDrawInnerShadow(self, context, size, point, cancel); - } - if (self.needDrawStrikethrough && context) { - if (cancel && cancel()) return; - ASTextDrawDecoration(self, context, size, point, ASTextDecorationTypeStrikethrough, cancel); - } - if (self.needDrawBorder && context) { - if (cancel && cancel()) return; - ASTextDrawBorder(self, context, size, point, ASTextBorderTypeNormal, cancel); - } - if (debug.needDrawDebug && context) { - if (cancel && cancel()) return; - ASTextDrawDebug(self, context, size, point, debug); - } - } -} - -- (void)drawInContext:(CGContextRef)context - size:(CGSize)size - debug:(ASTextDebugOption *)debug { - [self drawInContext:context size:size point:CGPointZero view:nil layer:nil debug:debug cancel:nil]; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASTextLine.h b/submodules/AsyncDisplayKit/Source/ASTextLine.h deleted file mode 100644 index acb02e991b..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextLine.h +++ /dev/null @@ -1,77 +0,0 @@ -// -// ASTextLine.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 -#import -#import - -@class ASTextRunGlyphRange; - -NS_ASSUME_NONNULL_BEGIN - -/** - A text line object wrapped `CTLineRef`, see `ASTextLayout` for more. - */ -@interface ASTextLine : NSObject - -+ (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical NS_RETURNS_RETAINED; - -@property (nonatomic) NSUInteger index; ///< line index -@property (nonatomic) NSUInteger row; ///< line row -@property (nullable, nonatomic) NSArray *> *verticalRotateRange; ///< Run rotate range - -@property (nonatomic, readonly) CTLineRef CTLine; ///< CoreText line -@property (nonatomic, readonly) NSRange range; ///< string range -@property (nonatomic, readonly) BOOL vertical; ///< vertical form - -@property (nonatomic, readonly) CGRect bounds; ///< bounds (ascent + descent) -@property (nonatomic, readonly) CGSize size; ///< bounds.size -@property (nonatomic, readonly) CGFloat width; ///< bounds.size.width -@property (nonatomic, readonly) CGFloat height; ///< bounds.size.height -@property (nonatomic, readonly) CGFloat top; ///< bounds.origin.y -@property (nonatomic, readonly) CGFloat bottom; ///< bounds.origin.y + bounds.size.height -@property (nonatomic, readonly) CGFloat left; ///< bounds.origin.x -@property (nonatomic, readonly) CGFloat right; ///< bounds.origin.x + bounds.size.width - -@property (nonatomic) CGPoint position; ///< baseline position -@property (nonatomic, readonly) CGFloat ascent; ///< line ascent -@property (nonatomic, readonly) CGFloat descent; ///< line descent -@property (nonatomic, readonly) CGFloat leading; ///< line leading -@property (nonatomic, readonly) CGFloat lineWidth; ///< line width -@property (nonatomic, readonly) CGFloat trailingWhitespaceWidth; - -@property (nullable, nonatomic, readonly) NSArray *attachments; ///< ASTextAttachment -@property (nullable, nonatomic, readonly) NSArray *attachmentRanges; ///< NSRange(NSValue) -@property (nullable, nonatomic, readonly) NSArray *attachmentRects; ///< CGRect(NSValue) - -@end - - -typedef NS_ENUM(NSUInteger, ASTextRunGlyphDrawMode) { - /// No rotate. - ASTextRunGlyphDrawModeHorizontal = 0, - - /// Rotate vertical for single glyph. - ASTextRunGlyphDrawModeVerticalRotate = 1, - - /// Rotate vertical for single glyph, and move the glyph to a better position, - /// such as fullwidth punctuation. - ASTextRunGlyphDrawModeVerticalRotateMove = 2, -}; - -/** - A range in CTRun, used for vertical form. - */ -@interface ASTextRunGlyphRange : NSObject -@property (nonatomic) NSRange glyphRangeInRun; -@property (nonatomic) ASTextRunGlyphDrawMode drawMode; -+ (instancetype)rangeWithRange:(NSRange)range drawMode:(ASTextRunGlyphDrawMode)mode NS_RETURNS_RETAINED; -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASTextLine.mm b/submodules/AsyncDisplayKit/Source/ASTextLine.mm deleted file mode 100644 index 1a23e623a5..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextLine.mm +++ /dev/null @@ -1,164 +0,0 @@ -// -// ASTextLine.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 "ASTextLine.h" -#import - -@implementation ASTextLine { - CGFloat _firstGlyphPos; // first glyph position for baseline, typically 0. -} - -+ (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical NS_RETURNS_RETAINED { - if (!CTLine) return nil; - ASTextLine *line = [self new]; - line->_position = position; - line->_vertical = isVertical; - [line setCTLine:CTLine]; - return line; -} - -- (void)dealloc { - if (_CTLine) CFRelease(_CTLine); -} - -- (void)setCTLine:(_Nonnull CTLineRef)CTLine { - if (_CTLine != CTLine) { - if (CTLine) CFRetain(CTLine); - if (_CTLine) CFRelease(_CTLine); - _CTLine = CTLine; - if (_CTLine) { - _lineWidth = CTLineGetTypographicBounds(_CTLine, &_ascent, &_descent, &_leading); - CFRange range = CTLineGetStringRange(_CTLine); - _range = NSMakeRange(range.location, range.length); - if (CTLineGetGlyphCount(_CTLine) > 0) { - CFArrayRef runs = CTLineGetGlyphRuns(_CTLine); - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, 0); - CGPoint pos; - CTRunGetPositions(run, CFRangeMake(0, 1), &pos); - _firstGlyphPos = pos.x; - } else { - _firstGlyphPos = 0; - } - _trailingWhitespaceWidth = CTLineGetTrailingWhitespaceWidth(_CTLine); - } else { - _lineWidth = _ascent = _descent = _leading = _firstGlyphPos = _trailingWhitespaceWidth = 0; - _range = NSMakeRange(0, 0); - } - [self reloadBounds]; - } -} - -- (void)setPosition:(CGPoint)position { - _position = position; - [self reloadBounds]; -} - -- (void)reloadBounds { - if (_vertical) { - _bounds = CGRectMake(_position.x - _descent, _position.y, _ascent + _descent, _lineWidth); - _bounds.origin.y += _firstGlyphPos; - } else { - _bounds = CGRectMake(_position.x, _position.y - _ascent, _lineWidth, _ascent + _descent); - _bounds.origin.x += _firstGlyphPos; - } - - _attachments = nil; - _attachmentRanges = nil; - _attachmentRects = nil; - if (!_CTLine) return; - CFArrayRef runs = CTLineGetGlyphRuns(_CTLine); - NSUInteger runCount = CFArrayGetCount(runs); - if (runCount == 0) return; - - NSMutableArray *attachments = [NSMutableArray new]; - NSMutableArray *attachmentRanges = [NSMutableArray new]; - NSMutableArray *attachmentRects = [NSMutableArray new]; - for (NSUInteger r = 0; r < runCount; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CFIndex glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - NSDictionary *attrs = (id)CTRunGetAttributes(run); - ASTextAttachment *attachment = attrs[ASTextAttachmentAttributeName]; - if (attachment) { - CGPoint runPosition = CGPointZero; - CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition); - - CGFloat ascent, descent, leading, runWidth; - CGRect runTypoBounds; - runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, &leading); - - if (_vertical) { - ASTEXT_SWAP(runPosition.x, runPosition.y); - runPosition.y = _position.y + runPosition.y; - runTypoBounds = CGRectMake(_position.x + runPosition.x - descent, runPosition.y , ascent + descent, runWidth); - } else { - runPosition.x += _position.x; - runPosition.y = _position.y - runPosition.y; - runTypoBounds = CGRectMake(runPosition.x, runPosition.y - ascent, runWidth, ascent + descent); - } - - NSRange runRange = ASTextNSRangeFromCFRange(CTRunGetStringRange(run)); - [attachments addObject:attachment]; - [attachmentRanges addObject:[NSValue valueWithRange:runRange]]; - [attachmentRects addObject:[NSValue valueWithCGRect:runTypoBounds]]; - } - } - _attachments = attachments.count ? attachments : nil; - _attachmentRanges = attachmentRanges.count ? attachmentRanges : nil; - _attachmentRects = attachmentRects.count ? attachmentRects : nil; -} - -- (CGSize)size { - return _bounds.size; -} - -- (CGFloat)width { - return CGRectGetWidth(_bounds); -} - -- (CGFloat)height { - return CGRectGetHeight(_bounds); -} - -- (CGFloat)top { - return CGRectGetMinY(_bounds); -} - -- (CGFloat)bottom { - return CGRectGetMaxY(_bounds); -} - -- (CGFloat)left { - return CGRectGetMinX(_bounds); -} - -- (CGFloat)right { - return CGRectGetMaxX(_bounds); -} - -- (NSString *)description { - NSMutableString *desc = @"".mutableCopy; - NSRange range = self.range; - [desc appendFormat:@" row:%ld range:%tu,%tu", self, (long)self.row, range.location, range.length]; - [desc appendFormat:@" position:%@",NSStringFromCGPoint(self.position)]; - [desc appendFormat:@" bounds:%@",NSStringFromCGRect(self.bounds)]; - return desc; -} - -@end - - -@implementation ASTextRunGlyphRange -+ (instancetype)rangeWithRange:(NSRange)range drawMode:(ASTextRunGlyphDrawMode)mode NS_RETURNS_RETAINED { - ASTextRunGlyphRange *one = [self new]; - one.glyphRangeInRun = range; - one.drawMode = mode; - return one; -} -@end diff --git a/submodules/AsyncDisplayKit/Source/ASTextNode+Beta.h b/submodules/AsyncDisplayKit/Source/ASTextNode+Beta.h deleted file mode 100644 index ad897c5f0e..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextNode+Beta.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// ASTextNode+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 - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ASTextNode () - -/** - @abstract An array of descending scale factors that will be applied to this text node to try to make it fit within its constrained size - @discussion This array should be in descending order and NOT contain the scale factor 1.0. For example, it could return @[@(.9), @(.85), @(.8)]; - @default nil (no scaling) - */ -@property (nullable, nonatomic, copy) NSArray *pointSizeScaleFactors; - -/** - @abstract Text margins for text laid out in the text node. - @discussion defaults to UIEdgeInsetsZero. - This property can be useful for handling text which does not fit within the view by default. An example: like UILabel, - ASTextNode will clip the left and right of the string "judar" if it's rendered in an italicised font. - */ -@property (nonatomic) UIEdgeInsets textContainerInset; - -/** - * Returns YES if this node is using the experimental implementation. NO otherwise. Will not change. - */ -@property (readonly) BOOL usingExperiment; - -/** - * Returns a Boolean indicating if the text node will truncate for the given constrained size - */ -- (BOOL)shouldTruncateForConstrainedSize:(ASSizeRange)constrainedSize; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/ASTextNode.h b/submodules/AsyncDisplayKit/Source/ASTextNode.h deleted file mode 100644 index 783eafc53e..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextNode.h +++ /dev/null @@ -1,264 +0,0 @@ -// -// ASTextNode.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 - -#import -#import -#import - -#if (!AS_ENABLE_TEXTNODE) - -// Pull in ASTextNode2 to replace ASTextNode with ASTextNode2 -#import - -#else - -NS_ASSUME_NONNULL_BEGIN - -/** - @abstract Draws interactive rich text. - @discussion Backed by TextKit. - */ -@interface ASTextNode : ASControlNode - -/** - @abstract The styled text displayed by the node. - @discussion Defaults to nil, no text is shown. - For inline image attachments, add an attribute of key NSAttachmentAttributeName, with a value of an NSTextAttachment. - */ -@property (nullable, copy) NSAttributedString *attributedText; - -#pragma mark - Truncation - -/** - @abstract The attributedText to use when the text must be truncated. - @discussion Defaults to a localized ellipsis character. - */ -@property (nullable, copy) NSAttributedString *truncationAttributedText; - -/** - @summary The second attributed string appended for truncation. - @discussion This string will be highlighted on touches. - @default nil - */ -@property (nullable, copy) NSAttributedString *additionalTruncationMessage; - -/** - @abstract Determines how the text is truncated to fit within the receiver's maximum size. - @discussion Defaults to NSLineBreakByWordWrapping. - @note Setting a truncationMode in attributedString will override the truncation mode set here. - */ -@property NSLineBreakMode truncationMode; - -/** - @abstract If the text node is truncated. Text must have been sized first. - */ -@property (readonly, getter=isTruncated) BOOL truncated; - -/** - @abstract The maximum number of lines to render of the text before truncation. - @default 0 (No limit) - */ -@property NSUInteger maximumNumberOfLines; - -/** - @abstract The number of lines in the text. Text must have been sized first. - */ -@property (readonly) NSUInteger lineCount; - -/** - * An array of path objects representing the regions where text should not be displayed. - * - * @discussion The default value of this property is an empty array. You can - * assign an array of UIBezierPath objects to exclude text from one or more regions in - * the text node's bounds. You can use this property to have text wrap around images, - * shapes or other text like a fancy magazine. - */ -@property (nullable, copy) NSArray *exclusionPaths; - -#pragma mark - Placeholders - -/** - * @abstract ASTextNode has a special placeholder behavior when placeholderEnabled is YES. - * - * @discussion Defaults to NO. When YES, it draws rectangles for each line of text, - * following the true shape of the text's wrapping. This visually mirrors the overall - * shape and weight of paragraphs, making the appearance of the finished text less jarring. - */ -@property BOOL placeholderEnabled; - -/** - @abstract The placeholder color. - */ -@property (nullable, copy) UIColor *placeholderColor; - -/** - @abstract Inset each line of the placeholder. - */ -@property UIEdgeInsets placeholderInsets; - -#pragma mark - Shadow - -/** - @abstract When you set these ASDisplayNode properties, they are composited into the bitmap instead of being applied by CA. - - @property (nonatomic) CGColorRef shadowColor; - @property (nonatomic) CGFloat shadowOpacity; - @property (nonatomic) CGSize shadowOffset; - @property (nonatomic) CGFloat shadowRadius; - */ - -/** - @abstract The number of pixels used for shadow padding on each side of the receiver. - @discussion Each inset will be less than or equal to zero, so that applying - UIEdgeInsetsRect(boundingRectForText, shadowPadding) - will return a CGRect large enough to fit both the text and the appropriate shadow padding. - */ -@property (readonly) UIEdgeInsets shadowPadding; - -#pragma mark - Positioning - -/** - @abstract Returns an array of rects bounding the characters in a given text range. - @param textRange A range of text. Must be valid for the receiver's string. - @discussion Use this method to detect all the different rectangles a given range of text occupies. - The rects returned are not guaranteed to be contiguous (for example, if the given text range spans - a line break, the rects returned will be on opposite sides and different lines). The rects returned - are in the coordinate system of the receiver. - */ -- (NSArray *)rectsForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT; - -/** - @abstract Returns an array of rects used for highlighting the characters in a given text range. - @param textRange A range of text. Must be valid for the receiver's string. - @discussion Use this method to detect all the different rectangles the highlights of a given range of text occupies. - The rects returned are not guaranteed to be contiguous (for example, if the given text range spans - a line break, the rects returned will be on opposite sides and different lines). The rects returned - are in the coordinate system of the receiver. This method is useful for visual coordination with a - highlighted range of text. - */ -- (NSArray *)highlightRectsForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT; - -/** - @abstract Returns a bounding rect for the given text range. - @param textRange A range of text. Must be valid for the receiver's string. - @discussion The height of the frame returned is that of the receiver's line-height; adjustment for - cap-height and descenders is not performed. This method raises an exception if textRange is not - a valid substring range of the receiver's string. - */ -- (CGRect)frameForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT; - -/** - @abstract Returns the trailing rectangle of space in the receiver, after the final character. - @discussion Use this method to detect which portion of the receiver is not occupied by characters. - The rect returned is in the coordinate system of the receiver. - */ -- (CGRect)trailingRect AS_WARN_UNUSED_RESULT; - - -#pragma mark - Actions - -/** - @abstract The set of attribute names to consider links. Defaults to NSLinkAttributeName. - */ -@property (copy) NSArray *linkAttributeNames; - -/** - @abstract Indicates whether the receiver has an entity at a given point. - @param point The point, in the receiver's coordinate system. - @param attributeNameOut The name of the attribute at the point. Can be NULL. - @param rangeOut The ultimate range of the found text. Can be NULL. - @result YES if an entity exists at `point`; NO otherwise. - */ -- (nullable id)linkAttributeValueAtPoint:(CGPoint)point attributeName:(out NSString * _Nullable * _Nullable)attributeNameOut range:(out NSRange * _Nullable)rangeOut AS_WARN_UNUSED_RESULT; - -/** - @abstract The style to use when highlighting text. - */ -@property ASTextNodeHighlightStyle highlightStyle; - -/** - @abstract The range of text highlighted by the receiver. Changes to this property are not animated by default. - */ -@property NSRange highlightRange; - -/** - @abstract Set the range of text to highlight, with optional animation. - - @param highlightRange The range of text to highlight. - - @param animated Whether the text should be highlighted with an animation. - */ -- (void)setHighlightRange:(NSRange)highlightRange animated:(BOOL)animated; - -/** - @abstract Responds to actions from links in the text node. - @discussion The delegate must be set before the node is loaded, and implement - textNode:longPressedLinkAttribute:value:atPoint:textRange: in order for - the long press gesture recognizer to be installed. - */ -@property (nullable, weak) id delegate; - -/** - @abstract If YES and a long press is recognized, touches are cancelled. Default is NO - */ -@property (nonatomic) BOOL longPressCancelsTouches; - -/** - @abstract if YES will not intercept touches for non-link areas of the text. Default is NO. - */ -@property (nonatomic) BOOL passthroughNonlinkTouches; - -@end - -@interface ASTextNode (Unavailable) - -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE; - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE; - -@end - -/** - * @abstract Text node unsupported properties - */ -@interface ASTextNode (Unsupported) - -@property (nullable, nonatomic) id textContainerLinePositionModifier; - -@end - -/** - * @abstract Text node deprecated properties - */ -@interface ASTextNode (Deprecated) - -/** - The attributedString and attributedText properties are equivalent, but attributedText is now the standard API - name in order to match UILabel and ASEditableTextNode. - - @see attributedText - */ -@property (nullable, copy) NSAttributedString *attributedString ASDISPLAYNODE_DEPRECATED_MSG("Use .attributedText instead."); - - -/** - The truncationAttributedString and truncationAttributedText properties are equivalent, but truncationAttributedText is now the - standard API name in order to match UILabel and ASEditableTextNode. - - @see truncationAttributedText - */ -@property (nullable, copy) NSAttributedString *truncationAttributedString ASDISPLAYNODE_DEPRECATED_MSG("Use .truncationAttributedText instead."); - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTextNode.mm b/submodules/AsyncDisplayKit/Source/ASTextNode.mm deleted file mode 100644 index 52eb89979e..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextNode.mm +++ /dev/null @@ -1,1494 +0,0 @@ -// -// ASTextNode.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 - -#if AS_ENABLE_TEXTNODE - -#import - -#import - -#import -#import - -#import -#import -#import "Private/ASDisplayNode+FrameworkPrivate.h" -#import -#import -#import -#import -#import -#import - -#import -#import -#import - -#import "Private/ASInternalHelpers.h" -#import - -#import -#import -#import -#import - -/** - * If set, we will record all values set to attributedText into an array - * and once we get 2000, we'll write them all out into a plist file. - * - * This is useful for gathering realistic text data sets from apps for performance - * testing. - */ -#define AS_TEXTNODE_RECORD_ATTRIBUTED_STRINGS 0 - -static const NSTimeInterval ASTextNodeHighlightFadeOutDuration = 0.15; -static const NSTimeInterval ASTextNodeHighlightFadeInDuration = 0.1; -static const CGFloat ASTextNodeHighlightLightOpacity = 0.11; -static const CGFloat ASTextNodeHighlightDarkOpacity = 0.22; -static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncationAttribute"; - -#pragma mark - ASTextKitRenderer - -@interface ASTextNodeRendererKey : NSObject -@property (nonatomic) ASTextKitAttributes attributes; -@property (nonatomic) CGSize constrainedSize; -@end - -@implementation ASTextNodeRendererKey { - std::mutex _m; -} - -- (NSUInteger)hash -{ - std::lock_guard _l(_m); -#pragma clang diagnostic push -#pragma clang diagnostic warning "-Wpadded" - struct { - size_t attributesHash; - CGSize constrainedSize; -#pragma clang diagnostic pop - } data = { - _attributes.hash(), - _constrainedSize - }; - return ASHashBytes(&data, sizeof(data)); -} - -- (BOOL)isEqual:(ASTextNodeRendererKey *)object -{ - if (self == object) { - return YES; - } - - // NOTE: Skip the class check for this specialized, internal Key object. - - // Lock both objects, avoiding deadlock. - std::lock(_m, object->_m); - std::lock_guard lk1(_m, std::adopt_lock); - std::lock_guard lk2(object->_m, std::adopt_lock); - - return _attributes == object->_attributes && CGSizeEqualToSize(_constrainedSize, object->_constrainedSize); -} - -@end - -static NSCache *sharedRendererCache() -{ - static dispatch_once_t onceToken; - static NSCache *__rendererCache = nil; - dispatch_once(&onceToken, ^{ - __rendererCache = [[NSCache alloc] init]; - __rendererCache.countLimit = 500; // 500 renders cache - }); - return __rendererCache; -} - -/** - The concept here is that neither the node nor layout should ever have a strong reference to the renderer object. - This is to reduce memory load when loading thousands and thousands of text nodes into memory at once. Instead - we maintain a LRU renderer cache that is queried via a unique key based on text kit attributes and constrained size. - */ - -static ASTextKitRenderer *rendererForAttributes(ASTextKitAttributes attributes, CGSize constrainedSize) -{ - NSCache *cache = sharedRendererCache(); - - ASTextNodeRendererKey *key = [[ASTextNodeRendererKey alloc] init]; - key.attributes = attributes; - key.constrainedSize = constrainedSize; - - ASTextKitRenderer *renderer = [cache objectForKey:key]; - if (renderer == nil) { - renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:attributes constrainedSize:constrainedSize]; - [cache setObject:renderer forKey:key]; - } - - return renderer; -} - -#pragma mark - ASTextNodeDrawParameter - -@interface ASTextNodeDrawParameter : NSObject { -@package - ASTextKitAttributes _rendererAttributes; - UIColor *_backgroundColor; - UIEdgeInsets _textContainerInsets; - CGFloat _contentScale; - BOOL _opaque; - CGRect _bounds; -} -@end - -@implementation ASTextNodeDrawParameter - -- (instancetype)initWithRendererAttributes:(ASTextKitAttributes)rendererAttributes - backgroundColor:(/*nullable*/ UIColor *)backgroundColor - textContainerInsets:(UIEdgeInsets)textContainerInsets - contentScale:(CGFloat)contentScale - opaque:(BOOL)opaque - bounds:(CGRect)bounds -{ - self = [super init]; - if (self != nil) { - _rendererAttributes = rendererAttributes; - _backgroundColor = backgroundColor; - _textContainerInsets = textContainerInsets; - _contentScale = contentScale; - _opaque = opaque; - _bounds = bounds; - } - return self; -} - -- (ASTextKitRenderer *)rendererForBounds:(CGRect)bounds -{ - CGRect rect = UIEdgeInsetsInsetRect(bounds, _textContainerInsets); - return rendererForAttributes(_rendererAttributes, rect.size); -} - -@end - - -#pragma mark - ASTextNode - -@interface ASTextNode () - -@end - -@implementation ASTextNode { - CGSize _shadowOffset; - CGColorRef _shadowColor; - UIColor *_cachedShadowUIColor; - UIColor *_placeholderColor; - CGFloat _shadowOpacity; - CGFloat _shadowRadius; - - UIEdgeInsets _textContainerInset; - - NSArray *_exclusionPaths; - - NSAttributedString *_attributedText; - NSAttributedString *_truncationAttributedText; - NSAttributedString *_additionalTruncationMessage; - NSAttributedString *_composedTruncationText; - NSArray *_pointSizeScaleFactors; - NSLineBreakMode _truncationMode; - - NSUInteger _maximumNumberOfLines; - - NSString *_highlightedLinkAttributeName; - id _highlightedLinkAttributeValue; - ASTextNodeHighlightStyle _highlightStyle; - NSRange _highlightRange; - ASHighlightOverlayLayer *_activeHighlightLayer; - - UILongPressGestureRecognizer *_longPressGestureRecognizer; -} -@dynamic placeholderEnabled; - -static NSArray *DefaultLinkAttributeNames() { - static NSArray *names; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - names = @[ NSLinkAttributeName ]; - }); - return names; -} - -- (instancetype)init -{ - if (self = [super init]) { - // Load default values from superclass. - _shadowOffset = [super shadowOffset]; - _shadowColor = CGColorRetain([super shadowColor]); - _shadowOpacity = [super shadowOpacity]; - _shadowRadius = [super shadowRadius]; - - // Disable user interaction for text node by default. - self.userInteractionEnabled = NO; - self.needsDisplayOnBoundsChange = YES; - - _truncationMode = NSLineBreakByWordWrapping; - - // The common case is for a text node to be non-opaque and blended over some background. - self.opaque = NO; - self.backgroundColor = [UIColor clearColor]; - - self.linkAttributeNames = DefaultLinkAttributeNames(); - - // Accessibility - self.isAccessibilityElement = YES; - self.accessibilityTraits = self.defaultAccessibilityTraits; - - // Placeholders - // Disabled by default in ASDisplayNode, but add a few options for those who toggle - // on the special placeholder behavior of ASTextNode. - _placeholderColor = ASDisplayNodeDefaultPlaceholderColor(); - _placeholderInsets = UIEdgeInsetsMake(1.0, 0.0, 1.0, 0.0); - } - - return self; -} - -- (void)dealloc -{ - CGColorRelease(_shadowColor); - - // TODO: This may not be necessary post-iOS-9 when most UIKit assign APIs - // were changed to weak. - if (_longPressGestureRecognizer) { - _longPressGestureRecognizer.delegate = nil; - [_longPressGestureRecognizer removeTarget:nil action:NULL]; - [self.view removeGestureRecognizer:_longPressGestureRecognizer]; - } -} - -#pragma mark - Description - -- (NSString *)_plainStringForDescription -{ - NSString *plainString = [[self.attributedText string] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; - if (plainString.length > 50) { - plainString = [[plainString substringToIndex:50] stringByAppendingString:@"\u2026"]; - } - return plainString; -} - -- (NSMutableArray *)propertiesForDescription -{ - NSMutableArray *result = [super propertiesForDescription]; - NSString *plainString = [self _plainStringForDescription]; - if (plainString.length > 0) { - [result addObject:@{ (id)kCFNull : ASStringWithQuotesIfMultiword(plainString) }]; - } - return result; -} - -- (NSMutableArray *)propertiesForDebugDescription -{ - NSMutableArray *result = [super propertiesForDebugDescription]; - NSString *plainString = [self _plainStringForDescription]; - if (plainString.length > 0) { - [result insertObject:@{ @"text" : ASStringWithQuotesIfMultiword(plainString) } atIndex:0]; - } - return result; -} - -#pragma mark - ASDisplayNode - -- (void)didLoad -{ - [super didLoad]; - - // If we are view-backed and the delegate cares, support the long-press callback. - SEL longPressCallback = @selector(textNode:longPressedLinkAttribute:value:atPoint:textRange:); - if (!self.isLayerBacked && [_delegate respondsToSelector:longPressCallback]) { - _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_handleLongPress:)]; - _longPressGestureRecognizer.cancelsTouchesInView = self.longPressCancelsTouches; - _longPressGestureRecognizer.delegate = self; - [self.view addGestureRecognizer:_longPressGestureRecognizer]; - } -} - -- (BOOL)supportsLayerBacking -{ - if (!super.supportsLayerBacking) { - return NO; - } - - // If the text contains any links, return NO. - NSAttributedString *attributedText = self.attributedText; - NSRange range = NSMakeRange(0, attributedText.length); - for (NSString *linkAttributeName in _linkAttributeNames) { - __block BOOL hasLink = NO; - [attributedText enumerateAttribute:linkAttributeName inRange:range options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { - hasLink = (value != nil); - *stop = YES; - }]; - if (hasLink) { - return NO; - } - } - return YES; -} - -#pragma mark - Renderer Management - -- (ASTextKitRenderer *)_renderer -{ - ASLockScopeSelf(); - return [self _locked_renderer]; -} - -- (ASTextKitRenderer *)_rendererWithBounds:(CGRect)bounds -{ - ASLockScopeSelf(); - return [self _locked_rendererWithBounds:bounds]; -} - -- (ASTextKitRenderer *)_locked_renderer -{ - ASAssertLocked(__instanceLock__); - return [self _locked_rendererWithBounds:[self _locked_threadSafeBounds]]; -} - -- (ASTextKitRenderer *)_locked_rendererWithBounds:(CGRect)bounds -{ - ASAssertLocked(__instanceLock__); - bounds = UIEdgeInsetsInsetRect(bounds, _textContainerInset); - return rendererForAttributes([self _locked_rendererAttributes], bounds.size); -} - -- (ASTextKitAttributes)_locked_rendererAttributes -{ - ASAssertLocked(__instanceLock__); - return { - .attributedString = _attributedText, - .truncationAttributedString = [self _locked_composedTruncationText], - .lineBreakMode = _truncationMode, - .maximumNumberOfLines = _maximumNumberOfLines, - .exclusionPaths = _exclusionPaths, - // use the property getter so a subclass can provide these scale factors on demand if desired - .pointSizeScaleFactors = self.pointSizeScaleFactors, - .shadowOffset = _shadowOffset, - .shadowColor = _cachedShadowUIColor, - .shadowOpacity = _shadowOpacity, - .shadowRadius = _shadowRadius - }; -} - -- (NSString *)defaultAccessibilityLabel -{ - ASLockScopeSelf(); - return _attributedText.string; -} - -- (UIAccessibilityTraits)defaultAccessibilityTraits -{ - return UIAccessibilityTraitStaticText; -} - -#pragma mark - Layout and Sizing - -- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset -{ - if (ASLockedSelfCompareAssignCustom(_textContainerInset, textContainerInset, UIEdgeInsetsEqualToEdgeInsets)) { - [self setNeedsLayout]; - } -} - -- (UIEdgeInsets)textContainerInset -{ - return ASLockedSelf(_textContainerInset); -} - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - ASLockScopeSelf(); - - ASDisplayNodeAssert(constrainedSize.width >= 0, @"Constrained width for text (%f) is too narrow", constrainedSize.width); - ASDisplayNodeAssert(constrainedSize.height >= 0, @"Constrained height for text (%f) is too short", constrainedSize.height); - - // Cache the original constrained size for final size calculateion - CGSize originalConstrainedSize = constrainedSize; - - [self setNeedsDisplay]; - - ASTextKitRenderer *renderer = [self _locked_rendererWithBounds:{.size = constrainedSize}]; - CGSize size = renderer.size; - if (_attributedText.length > 0) { - self.style.ascender = [[self class] ascenderWithAttributedString:_attributedText]; - self.style.descender = [[_attributedText attribute:NSFontAttributeName atIndex:_attributedText.length - 1 effectiveRange:NULL] descender]; - if (renderer.currentScaleFactor > 0 && renderer.currentScaleFactor < 1.0) { - // while not perfect, this is a good estimate of what the ascender of the scaled font will be. - self.style.ascender *= renderer.currentScaleFactor; - self.style.descender *= renderer.currentScaleFactor; - } - } - - // Add the constrained size back textContainerInset - size.width += (_textContainerInset.left + _textContainerInset.right); - size.height += (_textContainerInset.top + _textContainerInset.bottom); - - return CGSizeMake(std::fmin(size.width, originalConstrainedSize.width), - std::fmin(size.height, originalConstrainedSize.height)); -} - -#pragma mark - Modifying User Text - -// Returns the ascender of the first character in attributedString by also including the line height if specified in paragraph style. -+ (CGFloat)ascenderWithAttributedString:(NSAttributedString *)attributedString -{ - UIFont *font = [attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL]; - NSParagraphStyle *paragraphStyle = [attributedString attribute:NSParagraphStyleAttributeName atIndex:0 effectiveRange:NULL]; - if (!paragraphStyle) { - return font.ascender; - } - CGFloat lineHeight = MAX(font.lineHeight, paragraphStyle.minimumLineHeight); - if (paragraphStyle.maximumLineHeight > 0) { - lineHeight = MIN(lineHeight, paragraphStyle.maximumLineHeight); - } - return lineHeight + font.descender; -} - -- (NSAttributedString *)attributedText -{ - ASLockScopeSelf(); - return _attributedText; -} - -- (void)setAttributedText:(NSAttributedString *)attributedText -{ - - if (attributedText == nil) { - attributedText = [[NSAttributedString alloc] initWithString:@"" attributes:nil]; - } - - { - ASLockScopeSelf(); - if (ASObjectIsEqual(attributedText, _attributedText)) { - return; - } - - NSAttributedString *cleanedAttributedString = ASCleanseAttributedStringOfCoreTextAttributes(attributedText); - - // Invalidating the truncation text must be done while we still hold the lock. Because after we release it, - // another thread may set a new truncation text that will then be cleared by this thread, other may draw - // this soon-to-be-invalidated text. - [self _locked_invalidateTruncationText]; - - NSUInteger length = cleanedAttributedString.length; - if (length > 0) { - // Updating ascender and descender in one transaction while holding the lock. - ASLayoutElementStyle *style = [self _locked_style]; - style.ascender = [[self class] ascenderWithAttributedString:cleanedAttributedString]; - style.descender = [[attributedText attribute:NSFontAttributeName atIndex:cleanedAttributedString.length - 1 effectiveRange:NULL] descender]; - } - - // Update attributed text with cleaned attributed string - _attributedText = cleanedAttributedString; - } - - // Tell the display node superclasses that the cached layout is incorrect now - [self setNeedsLayout]; - - // Force display to create renderer with new size and redisplay with new string - [self setNeedsDisplay]; - - // Accessiblity - const auto currentAttributedText = self.attributedText; // Grab attributed string again in case it changed in the meantime - self.accessibilityLabel = self.defaultAccessibilityLabel; - self.isAccessibilityElement = (currentAttributedText.length != 0); // We're an accessibility element by default if there is a string. - -#if AS_TEXTNODE_RECORD_ATTRIBUTED_STRINGS - [ASTextNode _registerAttributedText:_attributedText]; -#endif -} - -#pragma mark - Text Layout - -- (void)setExclusionPaths:(NSArray *)exclusionPaths -{ - if (ASLockedSelfCompareAssignCopy(_exclusionPaths, exclusionPaths)) { - [self setNeedsLayout]; - [self setNeedsDisplay]; - } -} - -- (NSArray *)exclusionPaths -{ - return ASLockedSelf(_exclusionPaths); -} - -#pragma mark - Drawing - -- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer -{ - ASLockScopeSelf(); - - return [[ASTextNodeDrawParameter alloc] initWithRendererAttributes:[self _locked_rendererAttributes] - backgroundColor:self.backgroundColor - textContainerInsets:_textContainerInset - contentScale:_contentsScaleForDisplay - opaque:self.isOpaque - bounds:[self threadSafeBounds]]; -} - -+ (UIImage *)displayWithParameters:(id)parameters isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelled -{ - ASTextNodeDrawParameter *drawParameter = (ASTextNodeDrawParameter *)parameters; - - if (drawParameter->_bounds.size.width <= 0 || drawParameter->_bounds.size.height <= 0) { - return nil; - } - - UIImage *result = nil; - UIColor *backgroundColor = drawParameter->_backgroundColor; - UIEdgeInsets textContainerInsets = drawParameter ? drawParameter->_textContainerInsets : UIEdgeInsetsZero; - ASTextKitRenderer *renderer = [drawParameter rendererForBounds:drawParameter->_bounds]; - BOOL renderedWithGraphicsRenderer = NO; - - if (AS_AVAILABLE_IOS_TVOS(10, 10)) { - if (ASActivateExperimentalFeature(ASExperimentalTextDrawing)) { - renderedWithGraphicsRenderer = YES; - UIGraphicsImageRenderer *graphicsRenderer = [[UIGraphicsImageRenderer alloc] initWithSize:CGSizeMake(drawParameter->_bounds.size.width, drawParameter->_bounds.size.height)]; - result = [graphicsRenderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) { - CGContextRef context = rendererContext.CGContext; - ASDisplayNodeAssert(context, @"This is no good without a context."); - - CGContextSaveGState(context); - CGContextTranslateCTM(context, textContainerInsets.left, textContainerInsets.top); - - // Fill background - if (backgroundColor != nil) { - [backgroundColor setFill]; - UIRectFillUsingBlendMode(CGContextGetClipBoundingBox(context), kCGBlendModeCopy); - } - - // Draw text - [renderer drawInContext:context bounds:drawParameter->_bounds]; - CGContextRestoreGState(context); - }]; - } - } - - if (!renderedWithGraphicsRenderer) { - UIGraphicsBeginImageContextWithOptions(CGSizeMake(drawParameter->_bounds.size.width, drawParameter->_bounds.size.height), drawParameter->_opaque, drawParameter->_contentScale); - - CGContextRef context = UIGraphicsGetCurrentContext(); - ASDisplayNodeAssert(context, @"This is no good without a context."); - - CGContextSaveGState(context); - CGContextTranslateCTM(context, textContainerInsets.left, textContainerInsets.top); - - // Fill background - if (backgroundColor != nil) { - [backgroundColor setFill]; - UIRectFillUsingBlendMode(CGContextGetClipBoundingBox(context), kCGBlendModeCopy); - } - - // Draw text - [renderer drawInContext:context bounds:drawParameter->_bounds]; - CGContextRestoreGState(context); - - result = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - } - - return result; -} - -#pragma mark - Attributes - -- (id)linkAttributeValueAtPoint:(CGPoint)point - attributeName:(out NSString **)attributeNameOut - range:(out NSRange *)rangeOut -{ - return [self _linkAttributeValueAtPoint:point - attributeName:attributeNameOut - range:rangeOut - inAdditionalTruncationMessage:NULL - forHighlighting:NO]; -} - -- (id)_linkAttributeValueAtPoint:(CGPoint)point - attributeName:(out NSString * __autoreleasing *)attributeNameOut - range:(out NSRange *)rangeOut - inAdditionalTruncationMessage:(out BOOL *)inAdditionalTruncationMessageOut - forHighlighting:(BOOL)highlighting -{ - ASDisplayNodeAssertMainThread(); - - ASLockScopeSelf(); - - ASTextKitRenderer *renderer = [self _locked_renderer]; - NSRange visibleRange = renderer.firstVisibleRange; - NSAttributedString *attributedString = _attributedText; - NSRange clampedRange = NSIntersectionRange(visibleRange, NSMakeRange(0, attributedString.length)); - - // Check in a 9-point region around the actual touch point so we make sure - // we get the best attribute for the touch. - __block CGFloat minimumGlyphDistance = CGFLOAT_MAX; - - // Final output vars - __block id linkAttributeValue = nil; - __block BOOL inTruncationMessage = NO; - - [renderer enumerateTextIndexesAtPosition:point usingBlock:^(NSUInteger characterIndex, CGRect glyphBoundingRect, BOOL *stop) { - CGPoint glyphLocation = CGPointMake(CGRectGetMidX(glyphBoundingRect), CGRectGetMidY(glyphBoundingRect)); - CGFloat currentDistance = std::sqrt(std::pow(point.x - glyphLocation.x, 2.f) + std::pow(point.y - glyphLocation.y, 2.f)); - if (currentDistance >= minimumGlyphDistance) { - // If the distance computed from the touch to the glyph location is - // not the minimum among the located link attributes, we can just skip - // to the next location. - return; - } - - // Check if it's outside the visible range, if so, then we mark this touch - // as inside the truncation message, because in at least one of the touch - // points it was. - if (!(NSLocationInRange(characterIndex, visibleRange))) { - inTruncationMessage = YES; - } - - if (inAdditionalTruncationMessageOut != NULL) { - *inAdditionalTruncationMessageOut = inTruncationMessage; - } - - // Short circuit here if it's just in the truncation message. Since the - // truncation message may be beyond the scope of the actual input string, - // we have to make sure that we don't start asking for attributes on it. - if (inTruncationMessage) { - return; - } - - for (NSString *attributeName in self->_linkAttributeNames) { - NSRange range; - id value = [attributedString attribute:attributeName atIndex:characterIndex longestEffectiveRange:&range inRange:clampedRange]; - NSString *name = attributeName; - - if (value == nil || name == nil) { - // Didn't find anything - continue; - } - - // If highlighting, check with delegate first. If not implemented, assume YES. - if (highlighting - && [self->_delegate respondsToSelector:@selector(textNode:shouldHighlightLinkAttribute:value:atPoint:)] - && ![self->_delegate textNode:self shouldHighlightLinkAttribute:name value:value atPoint:point]) { - value = nil; - name = nil; - } - - if (value != nil || name != nil) { - // We found a minimum glyph distance link attribute, so set the min - // distance, and the out params. - minimumGlyphDistance = currentDistance; - - if (rangeOut != NULL && value != nil) { - *rangeOut = range; - // Limit to only the visible range, because the attributed string will - // return values outside the visible range. - if (NSMaxRange(*rangeOut) > NSMaxRange(visibleRange)) { - (*rangeOut).length = MAX(NSMaxRange(visibleRange) - (*rangeOut).location, 0); - } - } - - if (attributeNameOut != NULL) { - *attributeNameOut = name; - } - - // Set the values for the next iteration - linkAttributeValue = value; - - break; - } - } - }]; - - return linkAttributeValue; -} - -#pragma mark - UIGestureRecognizerDelegate - -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer -{ - ASDisplayNodeAssertMainThread(); - - if (gestureRecognizer == _longPressGestureRecognizer) { - // Don't allow long press on truncation message - if ([self _pendingTruncationTap]) { - return NO; - } - - // Ask our delegate if a long-press on an attribute is relevant - if ([self _pendingLinkTap] && [_delegate respondsToSelector:@selector(textNode:shouldLongPressLinkAttribute:value:atPoint:)]) { - return [_delegate textNode:self - shouldLongPressLinkAttribute:_highlightedLinkAttributeName - value:_highlightedLinkAttributeValue - atPoint:[gestureRecognizer locationInView:self.view]]; - } - - // Otherwise we are good to go. - return YES; - } - - if (([self _pendingLinkTap] || [self _pendingTruncationTap]) - && [gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] - && CGRectContainsPoint(self.threadSafeBounds, [gestureRecognizer locationInView:self.view])) { - return NO; - } - - return [super gestureRecognizerShouldBegin:gestureRecognizer]; -} - -#pragma mark - Highlighting - -- (ASTextNodeHighlightStyle)highlightStyle -{ - ASLockScopeSelf(); - - return _highlightStyle; -} - -- (void)setHighlightStyle:(ASTextNodeHighlightStyle)highlightStyle -{ - ASLockScopeSelf(); - - _highlightStyle = highlightStyle; -} - -- (NSRange)highlightRange -{ - ASDisplayNodeAssertMainThread(); - - return _highlightRange; -} - -- (void)setHighlightRange:(NSRange)highlightRange -{ - [self setHighlightRange:highlightRange animated:NO]; -} - -- (void)setHighlightRange:(NSRange)highlightRange animated:(BOOL)animated -{ - [self _setHighlightRange:highlightRange forAttributeName:nil value:nil animated:animated]; -} - -- (void)_setHighlightRange:(NSRange)highlightRange forAttributeName:(NSString *)highlightedAttributeName value:(id)highlightedAttributeValue animated:(BOOL)animated -{ - ASDisplayNodeAssertMainThread(); - - _highlightedLinkAttributeName = highlightedAttributeName; - _highlightedLinkAttributeValue = highlightedAttributeValue; - - if (!NSEqualRanges(highlightRange, _highlightRange) && ((0 != highlightRange.length) || (0 != _highlightRange.length))) { - - _highlightRange = highlightRange; - - if (_activeHighlightLayer) { - if (animated) { - __weak CALayer *weakHighlightLayer = _activeHighlightLayer; - _activeHighlightLayer = nil; - - weakHighlightLayer.opacity = 0.0; - - CFTimeInterval beginTime = CACurrentMediaTime(); - CABasicAnimation *possibleFadeIn = (CABasicAnimation *)[weakHighlightLayer animationForKey:@"opacity"]; - if (possibleFadeIn) { - // Calculate when we should begin fading out based on the end of the fade in animation, - // Also check to make sure that the new begin time hasn't already passed - CGFloat newBeginTime = (possibleFadeIn.beginTime + possibleFadeIn.duration); - if (newBeginTime > beginTime) { - beginTime = newBeginTime; - } - } - - CABasicAnimation *fadeOut = [CABasicAnimation animationWithKeyPath:@"opacity"]; - fadeOut.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; - fadeOut.fromValue = possibleFadeIn.toValue ? : @(((CALayer *)weakHighlightLayer.presentationLayer).opacity); - fadeOut.toValue = @0.0; - fadeOut.fillMode = kCAFillModeBoth; - fadeOut.duration = ASTextNodeHighlightFadeOutDuration; - fadeOut.beginTime = beginTime; - - dispatch_block_t prev = [CATransaction completionBlock]; - [CATransaction setCompletionBlock:^{ - [weakHighlightLayer removeFromSuperlayer]; - }]; - - [weakHighlightLayer addAnimation:fadeOut forKey:fadeOut.keyPath]; - - [CATransaction setCompletionBlock:prev]; - - } else { - [_activeHighlightLayer removeFromSuperlayer]; - _activeHighlightLayer = nil; - } - } - if (0 != highlightRange.length) { - // Find layer in hierarchy that allows us to draw highlighting on. - CALayer *highlightTargetLayer = self.layer; - while (highlightTargetLayer != nil) { - if (highlightTargetLayer.as_allowsHighlightDrawing) { - break; - } - highlightTargetLayer = highlightTargetLayer.superlayer; - } - - if (highlightTargetLayer != nil) { - ASLockScopeSelf(); - ASTextKitRenderer *renderer = [self _locked_renderer]; - - NSArray *highlightRects = [renderer rectsForTextRange:highlightRange measureOption:ASTextKitRendererMeasureOptionBlock]; - NSMutableArray *converted = [NSMutableArray arrayWithCapacity:highlightRects.count]; - for (NSValue *rectValue in highlightRects) { - UIEdgeInsets shadowPadding = renderer.shadower.shadowPadding; - CGRect rendererRect = ASTextNodeAdjustRenderRectForShadowPadding(rectValue.CGRectValue, shadowPadding); - - // The rects returned from renderer don't have `textContainerInset`, - // as well as they are using the `constrainedSize` for layout, - // so we can simply increase the rect by insets to get the full blown layout. - rendererRect.size.width += _textContainerInset.left + _textContainerInset.right; - rendererRect.size.height += _textContainerInset.top + _textContainerInset.bottom; - - CGRect highlightedRect = [self.layer convertRect:rendererRect toLayer:highlightTargetLayer]; - - // We set our overlay layer's frame to the bounds of the highlight target layer. - // Offset highlight rects to avoid double-counting target layer's bounds.origin. - highlightedRect.origin.x -= highlightTargetLayer.bounds.origin.x; - highlightedRect.origin.y -= highlightTargetLayer.bounds.origin.y; - [converted addObject:[NSValue valueWithCGRect:highlightedRect]]; - } - - ASHighlightOverlayLayer *overlayLayer = [[ASHighlightOverlayLayer alloc] initWithRects:converted]; - overlayLayer.highlightColor = [[self class] _highlightColorForStyle:self.highlightStyle]; - overlayLayer.frame = highlightTargetLayer.bounds; - overlayLayer.masksToBounds = NO; - overlayLayer.opacity = [[self class] _highlightOpacityForStyle:self.highlightStyle]; - [highlightTargetLayer addSublayer:overlayLayer]; - - if (animated) { - CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:@"opacity"]; - fadeIn.fromValue = @0.0; - fadeIn.toValue = @(overlayLayer.opacity); - fadeIn.duration = ASTextNodeHighlightFadeInDuration; - fadeIn.beginTime = CACurrentMediaTime(); - - [overlayLayer addAnimation:fadeIn forKey:fadeIn.keyPath]; - } - - [overlayLayer setNeedsDisplay]; - - _activeHighlightLayer = overlayLayer; - } - } - } -} - -- (void)_clearHighlightIfNecessary -{ - ASDisplayNodeAssertMainThread(); - - if ([self _pendingLinkTap] || [self _pendingTruncationTap]) { - [self setHighlightRange:NSMakeRange(0, 0) animated:YES]; - } -} - -+ (CGColorRef)_highlightColorForStyle:(ASTextNodeHighlightStyle)style -{ - return [UIColor colorWithWhite:(style == ASTextNodeHighlightStyleLight ? 0.0 : 1.0) alpha:1.0].CGColor; -} - -+ (CGFloat)_highlightOpacityForStyle:(ASTextNodeHighlightStyle)style -{ - return (style == ASTextNodeHighlightStyleLight) ? ASTextNodeHighlightLightOpacity : ASTextNodeHighlightDarkOpacity; -} - -#pragma mark - Text rects - -static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UIEdgeInsets shadowPadding) { - rendererRect.origin.x -= shadowPadding.left; - rendererRect.origin.y -= shadowPadding.top; - return rendererRect; -} - -- (NSArray *)rectsForTextRange:(NSRange)textRange -{ - return [self _rectsForTextRange:textRange measureOption:ASTextKitRendererMeasureOptionCapHeight]; -} - -- (NSArray *)highlightRectsForTextRange:(NSRange)textRange -{ - return [self _rectsForTextRange:textRange measureOption:ASTextKitRendererMeasureOptionBlock]; -} - -- (NSArray *)_rectsForTextRange:(NSRange)textRange measureOption:(ASTextKitRendererMeasureOption)measureOption -{ - ASLockScopeSelf(); - - NSArray *rects = [[self _locked_renderer] rectsForTextRange:textRange measureOption:measureOption]; - const auto adjustedRects = [[NSMutableArray alloc] init]; - - for (NSValue *rectValue in rects) { - CGRect rect = [rectValue CGRectValue]; - rect = ASTextNodeAdjustRenderRectForShadowPadding(rect, self.shadowPadding); - - NSValue *adjustedRectValue = [NSValue valueWithCGRect:rect]; - [adjustedRects addObject:adjustedRectValue]; - } - - return adjustedRects; -} - -- (CGRect)trailingRect -{ - ASLockScopeSelf(); - - CGRect rect = [[self _locked_renderer] trailingRect]; - return ASTextNodeAdjustRenderRectForShadowPadding(rect, self.shadowPadding); -} - -- (CGRect)frameForTextRange:(NSRange)textRange -{ - ASLockScopeSelf(); - - CGRect frame = [[self _locked_renderer] frameForTextRange:textRange]; - return ASTextNodeAdjustRenderRectForShadowPadding(frame, self.shadowPadding); -} - -#pragma mark - Placeholders - -- (UIColor *)placeholderColor -{ - return ASLockedSelf(_placeholderColor); -} - -- (void)setPlaceholderColor:(UIColor *)placeholderColor -{ - if (ASLockedSelfCompareAssignCopy(_placeholderColor, placeholderColor)) { - self.placeholderEnabled = CGColorGetAlpha(placeholderColor.CGColor) > 0; - } -} - -- (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; - } - - ASLockScopeSelf(); - - ASGraphicsBeginImageContextWithOptions(size, NO, 1.0); - [self.placeholderColor setFill]; - - ASTextKitRenderer *renderer = [self _locked_renderer]; - NSRange visibleRange = renderer.firstVisibleRange; - - // cap height is both faster and creates less subpixel blending - NSArray *lineRects = [self _rectsForTextRange:visibleRange measureOption:ASTextKitRendererMeasureOptionLineHeight]; - - // fill each line with the placeholder color - for (NSValue *rectValue in lineRects) { - CGRect lineRect = [rectValue CGRectValue]; - CGRect fillBounds = CGRectIntegral(UIEdgeInsetsInsetRect(lineRect, self.placeholderInsets)); - - if (fillBounds.size.width > 0.0 && fillBounds.size.height > 0.0) { - UIRectFill(fillBounds); - } - } - - UIImage *image = ASGraphicsGetImageAndEndCurrentContext(); - return image; -} - -#pragma mark - Touch Handling - -- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - - if (!_passthroughNonlinkTouches) { - return [super pointInside:point withEvent:event]; - } - - NSRange range = NSMakeRange(0, 0); - NSString *linkAttributeName = nil; - BOOL inAdditionalTruncationMessage = NO; - - id linkAttributeValue = [self _linkAttributeValueAtPoint:point - attributeName:&linkAttributeName - range:&range - inAdditionalTruncationMessage:&inAdditionalTruncationMessage - forHighlighting:YES]; - - NSUInteger lastCharIndex = NSIntegerMax; - BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1); - - if (range.length > 0 && !linkCrossesVisibleRange && linkAttributeValue != nil && linkAttributeName != nil) { - return YES; - } else { - return NO; - } -} - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - - [super touchesBegan:touches withEvent:event]; - - CGPoint point = [[touches anyObject] locationInView:self.view]; - - NSRange range = NSMakeRange(0, 0); - NSString *linkAttributeName = nil; - BOOL inAdditionalTruncationMessage = NO; - - id linkAttributeValue = [self _linkAttributeValueAtPoint:point - attributeName:&linkAttributeName - range:&range - inAdditionalTruncationMessage:&inAdditionalTruncationMessage - forHighlighting:YES]; - - NSUInteger lastCharIndex = NSIntegerMax; - BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1); - - if (inAdditionalTruncationMessage) { - NSRange visibleRange = NSMakeRange(0, 0); - { - ASLockScopeSelf(); - visibleRange = [self _locked_renderer].firstVisibleRange; - } - NSRange truncationMessageRange = [self _additionalTruncationMessageRangeWithVisibleRange:visibleRange]; - [self _setHighlightRange:truncationMessageRange forAttributeName:ASTextNodeTruncationTokenAttributeName value:nil animated:YES]; - } else if (range.length > 0 && !linkCrossesVisibleRange && linkAttributeValue != nil && linkAttributeName != nil) { - [self _setHighlightRange:range forAttributeName:linkAttributeName value:linkAttributeValue animated:YES]; - } -} - - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - [super touchesCancelled:touches withEvent:event]; - - [self _clearHighlightIfNecessary]; -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - [super touchesEnded:touches withEvent:event]; - - if ([self _pendingLinkTap] && [_delegate respondsToSelector:@selector(textNode:tappedLinkAttribute:value:atPoint:textRange:)]) { - CGPoint point = [[touches anyObject] locationInView:self.view]; - [_delegate textNode:self tappedLinkAttribute:_highlightedLinkAttributeName value:_highlightedLinkAttributeValue atPoint:point textRange:_highlightRange]; - } - - if ([self _pendingTruncationTap]) { - if ([_delegate respondsToSelector:@selector(textNodeTappedTruncationToken:)]) { - [_delegate textNodeTappedTruncationToken:self]; - } - } - - [self _clearHighlightIfNecessary]; -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - [super touchesMoved:touches withEvent:event]; - - UITouch *touch = [touches anyObject]; - CGPoint locationInView = [touch locationInView:self.view]; - // on 3D Touch enabled phones, this gets fired with changes in force, and usually will get fired immediately after touchesBegan:withEvent: - if (CGPointEqualToPoint([touch previousLocationInView:self.view], locationInView)) - return; - - // If touch has moved out of the current highlight range, clear the highlight. - if (_highlightRange.length > 0) { - NSRange range = NSMakeRange(0, 0); - [self _linkAttributeValueAtPoint:locationInView - attributeName:NULL - range:&range - inAdditionalTruncationMessage:NULL - forHighlighting:YES]; - - if (!NSEqualRanges(_highlightRange, range)) { - [self _clearHighlightIfNecessary]; - } - } -} - -- (void)_handleLongPress:(UILongPressGestureRecognizer *)longPressRecognizer -{ - ASDisplayNodeAssertMainThread(); - - // Respond to long-press when it begins, not when it ends. - if (longPressRecognizer.state == UIGestureRecognizerStateBegan) { - if ([self _pendingLinkTap] && [_delegate respondsToSelector:@selector(textNode:longPressedLinkAttribute:value:atPoint:textRange:)]) { - CGPoint touchPoint = [_longPressGestureRecognizer locationInView:self.view]; - [_delegate textNode:self longPressedLinkAttribute:_highlightedLinkAttributeName value:_highlightedLinkAttributeValue atPoint:touchPoint textRange:_highlightRange]; - } - } -} - -- (BOOL)_pendingLinkTap -{ - ASLockScopeSelf(); - - return (_highlightedLinkAttributeValue != nil && ![self _pendingTruncationTap]) && _delegate != nil; -} - -- (BOOL)_pendingTruncationTap -{ - ASLockScopeSelf(); - - return [_highlightedLinkAttributeName isEqualToString:ASTextNodeTruncationTokenAttributeName]; -} - -#pragma mark - Shadow Properties - -- (CGColorRef)shadowColor -{ - return ASLockedSelf(_shadowColor); -} - -- (void)setShadowColor:(CGColorRef)shadowColor -{ - [self lock]; - - if (_shadowColor != shadowColor && CGColorEqualToColor(shadowColor, _shadowColor) == NO) { - CGColorRelease(_shadowColor); - _shadowColor = CGColorRetain(shadowColor); - _cachedShadowUIColor = [UIColor colorWithCGColor:shadowColor]; - [self unlock]; - - [self setNeedsDisplay]; - return; - } - - [self unlock]; -} - -- (CGSize)shadowOffset -{ - return ASLockedSelf(_shadowOffset); -} - -- (void)setShadowOffset:(CGSize)shadowOffset -{ - if (ASLockedSelfCompareAssignCustom(_shadowOffset, shadowOffset, CGSizeEqualToSize)) { - [self setNeedsDisplay]; - } -} - -- (CGFloat)shadowOpacity -{ - return ASLockedSelf(_shadowOpacity); -} - -- (void)setShadowOpacity:(CGFloat)shadowOpacity -{ - if (ASLockedSelfCompareAssign(_shadowOpacity, shadowOpacity)) { - [self setNeedsDisplay]; - } -} - -- (CGFloat)shadowRadius -{ - return ASLockedSelf(_shadowRadius); -} - -- (void)setShadowRadius:(CGFloat)shadowRadius -{ - if (ASLockedSelfCompareAssign(_shadowRadius, shadowRadius)) { - [self setNeedsDisplay]; - } -} - -- (UIEdgeInsets)shadowPadding -{ - ASLockScopeSelf(); - return [self _locked_renderer].shadower.shadowPadding; -} - -#pragma mark - Truncation Message - -static NSAttributedString *DefaultTruncationAttributedString() -{ - static NSAttributedString *defaultTruncationAttributedString; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - defaultTruncationAttributedString = [[NSAttributedString alloc] initWithString:NSLocalizedString(@"\u2026", @"Default truncation string")]; - }); - return defaultTruncationAttributedString; -} - -- (NSAttributedString *)truncationAttributedText -{ - return ASLockedSelf(_truncationAttributedText); -} - -- (void)setTruncationAttributedText:(NSAttributedString *)truncationAttributedText -{ - if (ASLockedSelfCompareAssignCopy(_truncationAttributedText, truncationAttributedText)) { - [self _invalidateTruncationText]; - [self setNeedsDisplay]; - } -} - -- (void)setAdditionalTruncationMessage:(NSAttributedString *)additionalTruncationMessage -{ - if (ASLockedSelfCompareAssignCopy(_additionalTruncationMessage, additionalTruncationMessage)) { - [self _invalidateTruncationText]; - [self setNeedsDisplay]; - } -} - -- (NSAttributedString *)additionalTruncationMessage -{ - return ASLockedSelf(_additionalTruncationMessage); -} - -- (void)setTruncationMode:(NSLineBreakMode)truncationMode -{ - if (ASLockedSelfCompareAssign(_truncationMode, truncationMode)) { - [self setNeedsDisplay]; - } -} - -- (NSLineBreakMode)truncationMode -{ - return ASLockedSelf(_truncationMode); -} - -- (BOOL)isTruncated -{ - return ASLockedSelf([[self _locked_renderer] isTruncated]); -} - -- (BOOL)shouldTruncateForConstrainedSize:(ASSizeRange)constrainedSize -{ - return ASLockedSelf([[self _locked_rendererWithBounds:{.size = constrainedSize.max}] isTruncated]); -} - -- (void)setPointSizeScaleFactors:(NSArray *)pointSizeScaleFactors -{ - if (ASLockedSelfCompareAssignCopy(_pointSizeScaleFactors, pointSizeScaleFactors)) { - [self setNeedsDisplay]; - } -} - -- (NSArray *)pointSizeScaleFactors -{ - return ASLockedSelf(_pointSizeScaleFactors); -} - -- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines -{ - if (ASLockedSelfCompareAssign(_maximumNumberOfLines, maximumNumberOfLines)) { - [self setNeedsDisplay]; - } -} - -- (NSUInteger)maximumNumberOfLines -{ - return ASLockedSelf(_maximumNumberOfLines); -} - -- (NSUInteger)lineCount -{ - return ASLockedSelf([[self _locked_renderer] lineCount]); -} - -#pragma mark - Truncation Message - -- (void)_invalidateTruncationText -{ - ASLockScopeSelf(); - [self _locked_invalidateTruncationText]; -} - -- (void)_locked_invalidateTruncationText -{ - _composedTruncationText = nil; -} - -/** - * @return the additional truncation message range within the as-rendered text. - * Must be called from main thread - */ -- (NSRange)_additionalTruncationMessageRangeWithVisibleRange:(NSRange)visibleRange -{ - ASLockScopeSelf(); - - // Check if we even have an additional truncation message. - if (!_additionalTruncationMessage) { - return NSMakeRange(NSNotFound, 0); - } - - // Character location of the unicode ellipsis (the first index after the visible range) - NSInteger truncationTokenIndex = NSMaxRange(visibleRange); - - NSUInteger additionalTruncationMessageLength = _additionalTruncationMessage.length; - // We get the location of the truncation token, then add the length of the - // truncation attributed string +1 for the space between. - return NSMakeRange(truncationTokenIndex + _truncationAttributedText.length + 1, additionalTruncationMessageLength); -} - -/** - * @return the truncation message for the string. If there are both an - * additional truncation message and a truncation attributed string, they will - * be properly composed. - */ -- (NSAttributedString *)_locked_composedTruncationText -{ - ASAssertLocked(__instanceLock__); - if (_composedTruncationText == nil) { - if (_truncationAttributedText != nil && _additionalTruncationMessage != nil) { - NSMutableAttributedString *newComposedTruncationString = [[NSMutableAttributedString alloc] initWithAttributedString:_truncationAttributedText]; - [newComposedTruncationString.mutableString appendString:@" "]; - [newComposedTruncationString appendAttributedString:_additionalTruncationMessage]; - _composedTruncationText = newComposedTruncationString; - } else if (_truncationAttributedText != nil) { - _composedTruncationText = _truncationAttributedText; - } else if (_additionalTruncationMessage != nil) { - _composedTruncationText = _additionalTruncationMessage; - } else { - _composedTruncationText = DefaultTruncationAttributedString(); - } - _composedTruncationText = [self _locked_prepareTruncationStringForDrawing:_composedTruncationText]; - } - return _composedTruncationText; -} - -/** - * - cleanses it of core text attributes so TextKit doesn't crash - * - Adds whole-string attributes so the truncation message matches the styling - * of the body text - */ -- (NSAttributedString *)_locked_prepareTruncationStringForDrawing:(NSAttributedString *)truncationString -{ - ASAssertLocked(__instanceLock__); - truncationString = ASCleanseAttributedStringOfCoreTextAttributes(truncationString); - NSMutableAttributedString *truncationMutableString = [truncationString mutableCopy]; - // Grab the attributes from the full string - if (_attributedText.length > 0) { - NSAttributedString *originalString = _attributedText; - NSInteger originalStringLength = _attributedText.length; - // Add any of the original string's attributes to the truncation string, - // but don't overwrite any of the truncation string's attributes - NSDictionary *originalStringAttributes = [originalString attributesAtIndex:originalStringLength-1 effectiveRange:NULL]; - [truncationString enumerateAttributesInRange:NSMakeRange(0, truncationString.length) options:0 usingBlock: - ^(NSDictionary *attributes, NSRange range, BOOL *stop) { - NSMutableDictionary *futureTruncationAttributes = [originalStringAttributes mutableCopy]; - [futureTruncationAttributes addEntriesFromDictionary:attributes]; - [truncationMutableString setAttributes:futureTruncationAttributes range:range]; - }]; - } - return truncationMutableString; -} - -#if AS_TEXTNODE_RECORD_ATTRIBUTED_STRINGS -+ (void)_registerAttributedText:(NSAttributedString *)str -{ - static NSMutableArray *array; - static NSLock *lock; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - lock = [NSLock new]; - array = [NSMutableArray new]; - }); - [lock lock]; - [array addObject:str]; - if (array.count % 20 == 0) { - NSLog(@"Got %d strings", (int)array.count); - } - if (array.count == 2000) { - NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"AttributedStrings.plist"]; - NSAssert([NSKeyedArchiver archiveRootObject:array toFile:path], nil); - NSLog(@"Saved to %@", path); - } - [lock unlock]; -} -#endif - -// All direct descendants of ASTextNode get their superclass replaced by ASTextNode2. -+ (void)initialize -{ - // Texture requires that node subclasses call [super initialize] - [super initialize]; - - if (class_getSuperclass(self) == [ASTextNode class] - && ASActivateExperimentalFeature(ASExperimentalTextNode)) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - class_setSuperclass(self, [ASTextNode2 class]); -#pragma clang diagnostic pop - } -} - -// For direct allocations of ASTextNode itself, we override allocWithZone: -+ (id)allocWithZone:(struct _NSZone *)zone -{ - if (ASActivateExperimentalFeature(ASExperimentalTextNode)) { - return (ASTextNode *)[ASTextNode2 allocWithZone:zone]; - } else { - return [super allocWithZone:zone]; - } -} - -@end - -@implementation ASTextNode (Unsupported) - -- (void)setTextContainerLinePositionModifier:(id)textContainerLinePositionModifier -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); -} - -- (id)textContainerLinePositionModifier -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return nil; -} - -@end - -@implementation ASTextNode (Deprecated) - -- (void)setAttributedString:(NSAttributedString *)attributedString -{ - self.attributedText = attributedString; -} - -- (NSAttributedString *)attributedString -{ - return self.attributedText; -} - -- (void)setTruncationAttributedString:(NSAttributedString *)truncationAttributedString -{ - self.truncationAttributedText = truncationAttributedString; -} - -- (NSAttributedString *)truncationAttributedString -{ - return self.truncationAttributedText; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTextNode2.h b/submodules/AsyncDisplayKit/Source/ASTextNode2.h deleted file mode 100644 index 254ce40a7e..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextNode2.h +++ /dev/null @@ -1,242 +0,0 @@ -// -// ASTextNode2.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -@protocol ASTextLinePositionModifier; - -NS_ASSUME_NONNULL_BEGIN - -/** - @abstract Draws interactive rich text. - @discussion Backed by the code in TextExperiment folder, on top of CoreText. - */ -#if AS_ENABLE_TEXTNODE -@interface ASTextNode2 : ASControlNode -#else -@interface ASTextNode : ASControlNode -#endif - -/** - @abstract The styled text displayed by the node. - @discussion Defaults to nil, no text is shown. - For inline image attachments, add an attribute of key NSAttachmentAttributeName, with a value of an NSTextAttachment. - */ -@property (nullable, copy) NSAttributedString *attributedText; - -#pragma mark - Truncation - -/** - @abstract The attributedText to use when the text must be truncated. - @discussion Defaults to a localized ellipsis character. - */ -@property (nullable, copy) NSAttributedString *truncationAttributedText; - -/** - @summary The second attributed string appended for truncation. - @discussion This string will be highlighted on touches. - @default nil - */ -@property (nullable, copy) NSAttributedString *additionalTruncationMessage; - -/** - @abstract Determines how the text is truncated to fit within the receiver's maximum size. - @discussion Defaults to NSLineBreakByWordWrapping. - @note Setting a truncationMode in attributedString will override the truncation mode set here. - */ -@property NSLineBreakMode truncationMode; - -/** - @abstract If the text node is truncated. Text must have been sized first. - */ -@property (readonly, getter=isTruncated) BOOL truncated; - -/** - @abstract The maximum number of lines to render of the text before truncation. - @default 0 (No limit) - */ -@property NSUInteger maximumNumberOfLines; - -/** - @abstract The number of lines in the text. Text must have been sized first. - */ -@property (readonly) NSUInteger lineCount; - -/** - * An array of path objects representing the regions where text should not be displayed. - * - * @discussion The default value of this property is an empty array. You can - * assign an array of UIBezierPath objects to exclude text from one or more regions in - * the text node's bounds. You can use this property to have text wrap around images, - * shapes or other text like a fancy magazine. - */ -@property (nullable, copy) NSArray *exclusionPaths; - -#pragma mark - Placeholders - -/** - * @abstract ASTextNode has a special placeholder behavior when placeholderEnabled is YES. - * - * @discussion Defaults to NO. When YES, it draws rectangles for each line of text, - * following the true shape of the text's wrapping. This visually mirrors the overall - * shape and weight of paragraphs, making the appearance of the finished text less jarring. - */ -@property BOOL placeholderEnabled; - -/** - @abstract The placeholder color. - */ -@property (nullable, copy) UIColor *placeholderColor; - -/** - @abstract Inset each line of the placeholder. - */ -@property UIEdgeInsets placeholderInsets; - -#pragma mark - Shadow - -/** - @abstract When you set these ASDisplayNode properties, they are composited into the bitmap instead of being applied by CA. - - @property (nonatomic) CGColorRef shadowColor; - @property (nonatomic) CGFloat shadowOpacity; - @property (nonatomic) CGSize shadowOffset; - @property (nonatomic) CGFloat shadowRadius; - */ - -/** - @abstract The number of pixels used for shadow padding on each side of the receiver. - @discussion Each inset will be less than or equal to zero, so that applying - UIEdgeInsetsRect(boundingRectForText, shadowPadding) - will return a CGRect large enough to fit both the text and the appropriate shadow padding. - */ -@property (nonatomic, readonly) UIEdgeInsets shadowPadding; - -#pragma mark - Positioning - -/** - @abstract Returns an array of rects bounding the characters in a given text range. - @param textRange A range of text. Must be valid for the receiver's string. - @discussion Use this method to detect all the different rectangles a given range of text occupies. - The rects returned are not guaranteed to be contiguous (for example, if the given text range spans - a line break, the rects returned will be on opposite sides and different lines). The rects returned - are in the coordinate system of the receiver. - */ -- (NSArray *)rectsForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT; - -/** - @abstract Returns an array of rects used for highlighting the characters in a given text range. - @param textRange A range of text. Must be valid for the receiver's string. - @discussion Use this method to detect all the different rectangles the highlights of a given range of text occupies. - The rects returned are not guaranteed to be contiguous (for example, if the given text range spans - a line break, the rects returned will be on opposite sides and different lines). The rects returned - are in the coordinate system of the receiver. This method is useful for visual coordination with a - highlighted range of text. - */ -- (NSArray *)highlightRectsForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT; - -/** - @abstract Returns a bounding rect for the given text range. - @param textRange A range of text. Must be valid for the receiver's string. - @discussion The height of the frame returned is that of the receiver's line-height; adjustment for - cap-height and descenders is not performed. This method raises an exception if textRange is not - a valid substring range of the receiver's string. - */ -- (CGRect)frameForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT; - -/** - @abstract Returns the trailing rectangle of space in the receiver, after the final character. - @discussion Use this method to detect which portion of the receiver is not occupied by characters. - The rect returned is in the coordinate system of the receiver. - */ -- (CGRect)trailingRect AS_WARN_UNUSED_RESULT; - - -#pragma mark - Actions - -/** - @abstract The set of attribute names to consider links. Defaults to NSLinkAttributeName. - */ -@property (nonatomic, copy) NSArray *linkAttributeNames; - -/** - @abstract Indicates whether the receiver has an entity at a given point. - @param point The point, in the receiver's coordinate system. - @param attributeNameOut The name of the attribute at the point. Can be NULL. - @param rangeOut The ultimate range of the found text. Can be NULL. - @result YES if an entity exists at `point`; NO otherwise. - */ -- (nullable id)linkAttributeValueAtPoint:(CGPoint)point attributeName:(out NSString * _Nullable * _Nullable)attributeNameOut range:(out NSRange * _Nullable)rangeOut AS_WARN_UNUSED_RESULT; - -/** - @abstract The style to use when highlighting text. - */ -@property (nonatomic) ASTextNodeHighlightStyle highlightStyle; - -/** - @abstract The range of text highlighted by the receiver. Changes to this property are not animated by default. - */ -@property (nonatomic) NSRange highlightRange; - -/** - @abstract Set the range of text to highlight, with optional animation. - - @param highlightRange The range of text to highlight. - - @param animated Whether the text should be highlighted with an animation. - */ -- (void)setHighlightRange:(NSRange)highlightRange animated:(BOOL)animated; - -/** - @abstract Responds to actions from links in the text node. - @discussion The delegate must be set before the node is loaded, and implement - textNode:longPressedLinkAttribute:value:atPoint:textRange: in order for - the long press gesture recognizer to be installed. - */ -@property (weak) id delegate; - -/** - @abstract If YES and a long press is recognized, touches are cancelled. Default is NO - */ -@property (nonatomic) BOOL longPressCancelsTouches; - -/** - @abstract if YES will not intercept touches for non-link areas of the text. Default is NO. - */ -@property (nonatomic) BOOL passthroughNonlinkTouches; - -+ (void)enableDebugging; - -#pragma mark - Layout and Sizing - -@property (nullable, nonatomic) id textContainerLinePositionModifier; - -@end - -#if AS_ENABLE_TEXTNODE -@interface ASTextNode2 (Unavailable) -#else -@interface ASTextNode (Unavailable) -#endif - -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE; - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE; - -@end - -#if (!AS_ENABLE_TEXTNODE) -// For the time beeing remap ASTextNode2 to ASTextNode -#define ASTextNode2 ASTextNode -#endif - -NS_ASSUME_NONNULL_END - - diff --git a/submodules/AsyncDisplayKit/Source/ASTextNode2.mm b/submodules/AsyncDisplayKit/Source/ASTextNode2.mm deleted file mode 100644 index adb914ff5a..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextNode2.mm +++ /dev/null @@ -1,1333 +0,0 @@ -// -// ASTextNode2.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import // Definition of ASTextNodeDelegate - -#import -#import - -#import -#import "Private/ASDisplayNode+FrameworkPrivate.h" -#import -#import -#import "Private/ASDisplayNodeInternal.h" -#import - -#import -#import -#import - -#import "Private/ASInternalHelpers.h" - -#import -#import -#import -#import - -@interface ASTextCacheValue : NSObject { - @package - AS::Mutex _m; - std::deque> _layouts; -} -@end -@implementation ASTextCacheValue -@end - -/** - * If set, we will record all values set to attributedText into an array - * and once we get 2000, we'll write them all out into a plist file. - * - * This is useful for gathering realistic text data sets from apps for performance - * testing. - */ -#define AS_TEXTNODE2_RECORD_ATTRIBUTED_STRINGS 0 - -/** - * If it can't find a compatible layout, this method creates one. - * - * NOTE: Be careful to copy `text` if needed. - */ -static NS_RETURNS_RETAINED ASTextLayout *ASTextNodeCompatibleLayoutWithContainerAndText(ASTextContainer *container, NSAttributedString *text) { - static dispatch_once_t onceToken; - static AS::Mutex *layoutCacheLock; - static NSCache *textLayoutCache; - dispatch_once(&onceToken, ^{ - layoutCacheLock = new AS::Mutex(); - textLayoutCache = [[NSCache alloc] init]; - }); - - layoutCacheLock->lock(); - - ASTextCacheValue *cacheValue = [textLayoutCache objectForKey:text]; - if (cacheValue == nil) { - cacheValue = [[ASTextCacheValue alloc] init]; - [textLayoutCache setObject:cacheValue forKey:[text copy]]; - } - - // Lock the cache item for the rest of the method. Only after acquiring can we release the NSCache. - AS::MutexLocker lock(cacheValue->_m); - layoutCacheLock->unlock(); - - CGRect containerBounds = (CGRect){ .size = container.size }; - { - for (const auto &t : cacheValue->_layouts) { - CGSize constrainedSize = std::get<0>(t); - ASTextLayout *layout = std::get<1>(t); - - CGSize layoutSize = layout.textBoundingSize; - // 1. CoreText can return frames that are narrower than the constrained width, for obvious reasons. - // 2. CoreText can return frames that are slightly wider than the constrained width, for some reason. - // We have to trust that somehow it's OK to try and draw within our size constraint, despite the return value. - // 3. Thus, those two values (constrained width & returned width) form a range, where - // intermediate values in that range will be snapped. Thus, we can use a given layout as long as our - // width is in that range, between the min and max of those two values. - CGRect minRect = CGRectMake(0, 0, MIN(layoutSize.width, constrainedSize.width), MIN(layoutSize.height, constrainedSize.height)); - if (!CGRectContainsRect(containerBounds, minRect)) { - continue; - } - CGRect maxRect = CGRectMake(0, 0, MAX(layoutSize.width, constrainedSize.width), MAX(layoutSize.height, constrainedSize.height)); - if (!CGRectContainsRect(maxRect, containerBounds)) { - continue; - } - if (!CGSizeEqualToSize(container.size, constrainedSize)) { - continue; - } - - // Now check container params. - ASTextContainer *otherContainer = layout.container; - if (!UIEdgeInsetsEqualToEdgeInsets(container.insets, otherContainer.insets)) { - continue; - } - if (!ASObjectIsEqual(container.exclusionPaths, otherContainer.exclusionPaths)) { - continue; - } - if (container.maximumNumberOfRows != otherContainer.maximumNumberOfRows) { - continue; - } - if (container.truncationType != otherContainer.truncationType) { - continue; - } - if (!ASObjectIsEqual(container.truncationToken, otherContainer.truncationToken)) { - continue; - } - // TODO: When we get a cache hit, move this entry to the front (LRU). - return layout; - } - } - - // Cache Miss. Compute the text layout. - ASTextLayout *layout = [ASTextLayout layoutWithContainer:container text:text]; - - // Store the result in the cache. - { - // This is a critical section. However we also must hold the lock until this point, in case - // another thread requests this cache item while a layout is being calculated, so they don't race. - cacheValue->_layouts.push_front(std::make_tuple(container.size, layout)); - if (cacheValue->_layouts.size() > 3) { - cacheValue->_layouts.pop_back(); - } - } - - return layout; -} - -static const CGFloat ASTextNodeHighlightLightOpacity = 0.11; -static const CGFloat ASTextNodeHighlightDarkOpacity = 0.22; -static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncationAttribute"; - -#if AS_ENABLE_TEXTNODE -@interface ASTextNode2 () -#else -@interface ASTextNode () -#endif - -@end - -#if AS_ENABLE_TEXTNODE -@implementation ASTextNode2 { -#else -@implementation ASTextNode { -#endif - ASTextContainer *_textContainer; - - CGSize _shadowOffset; - CGColorRef _shadowColor; - CGFloat _shadowOpacity; - CGFloat _shadowRadius; - - NSAttributedString *_attributedText; - NSAttributedString *_truncationAttributedText; - NSAttributedString *_additionalTruncationMessage; - NSAttributedString *_composedTruncationText; - NSArray *_pointSizeScaleFactors; - NSLineBreakMode _truncationMode; - - NSString *_highlightedLinkAttributeName; - id _highlightedLinkAttributeValue; - ASTextNodeHighlightStyle _highlightStyle; - NSRange _highlightRange; - ASHighlightOverlayLayer *_activeHighlightLayer; - UIColor *_placeholderColor; - - UILongPressGestureRecognizer *_longPressGestureRecognizer; -} -@dynamic placeholderEnabled; - -static NSArray *DefaultLinkAttributeNames() { - static NSArray *names; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - names = @[ NSLinkAttributeName ]; - }); - return names; -} - -- (instancetype)init -{ - if (self = [super init]) { - _textContainer = [[ASTextContainer alloc] init]; - // Load default values from superclass. - _shadowOffset = [super shadowOffset]; - _shadowColor = CGColorRetain([super shadowColor]); - _shadowOpacity = [super shadowOpacity]; - _shadowRadius = [super shadowRadius]; - - // Disable user interaction for text node by default. - self.userInteractionEnabled = NO; - self.needsDisplayOnBoundsChange = YES; - - _textContainer.truncationType = ASTextTruncationTypeEnd; - - // The common case is for a text node to be non-opaque and blended over some background. - self.opaque = NO; - self.backgroundColor = [UIColor clearColor]; - - self.linkAttributeNames = DefaultLinkAttributeNames(); - - // Accessibility - self.isAccessibilityElement = YES; - self.accessibilityTraits = self.defaultAccessibilityTraits; - - // Placeholders - // Disabled by default in ASDisplayNode, but add a few options for those who toggle - // on the special placeholder behavior of ASTextNode. - _placeholderColor = ASDisplayNodeDefaultPlaceholderColor(); - _placeholderInsets = UIEdgeInsetsMake(1.0, 0.0, 1.0, 0.0); - } - - return self; -} - -- (void)dealloc -{ - CGColorRelease(_shadowColor); - - if (_longPressGestureRecognizer) { - _longPressGestureRecognizer.delegate = nil; - [_longPressGestureRecognizer removeTarget:nil action:NULL]; - [self.view removeGestureRecognizer:_longPressGestureRecognizer]; - } -} - -#pragma mark - Description - -- (NSString *)_plainStringForDescription -{ - NSString *plainString = [[self.attributedText string] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; - if (plainString.length > 50) { - plainString = [[plainString substringToIndex:50] stringByAppendingString:@"…"]; - } - return plainString; -} - -- (NSMutableArray *)propertiesForDescription -{ - NSMutableArray *result = [super propertiesForDescription]; - NSString *plainString = [self _plainStringForDescription]; - if (plainString.length > 0) { - [result insertObject:@{ @"text" : ASStringWithQuotesIfMultiword(plainString) } atIndex:0]; - } - return result; -} - -- (NSMutableArray *)propertiesForDebugDescription -{ - NSMutableArray *result = [super propertiesForDebugDescription]; - NSString *plainString = [self _plainStringForDescription]; - if (plainString.length > 0) { - [result insertObject:@{ @"text" : ASStringWithQuotesIfMultiword(plainString) } atIndex:0]; - } - return result; -} - -#pragma mark - ASDisplayNode - -- (void)didLoad -{ - [super didLoad]; - - // If we are view-backed and the delegate cares, support the long-press callback. - // Locking is not needed, as all instance variables used are main-thread-only. - SEL longPressCallback = @selector(textNode:longPressedLinkAttribute:value:atPoint:textRange:); - if (!self.isLayerBacked && [self.delegate respondsToSelector:longPressCallback]) { - _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_handleLongPress:)]; - _longPressGestureRecognizer.cancelsTouchesInView = self.longPressCancelsTouches; - _longPressGestureRecognizer.delegate = self; - [self.view addGestureRecognizer:_longPressGestureRecognizer]; - } -} - -- (BOOL)supportsLayerBacking -{ - if (!super.supportsLayerBacking) { - return NO; - } - - ASLockScopeSelf(); - // If the text contains any links, return NO. - NSAttributedString *attributedText = _attributedText; - NSRange range = NSMakeRange(0, attributedText.length); - for (NSString *linkAttributeName in _linkAttributeNames) { - __block BOOL hasLink = NO; - [attributedText enumerateAttribute:linkAttributeName inRange:range options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { - hasLink = (value != nil); - *stop = YES; - }]; - if (hasLink) { - return NO; - } - } - return YES; -} - -- (NSString *)defaultAccessibilityLabel -{ - ASLockScopeSelf(); - return _attributedText.string; -} - -- (UIAccessibilityTraits)defaultAccessibilityTraits -{ - return UIAccessibilityTraitStaticText; -} - -#pragma mark - Layout and Sizing - -- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset -{ - ASLockScopeSelf(); - if (ASCompareAssignCustom(_textContainer.insets, textContainerInset, UIEdgeInsetsEqualToEdgeInsets)) { - [self setNeedsLayout]; - } -} - -- (UIEdgeInsets)textContainerInset -{ - // textContainer is invariant and has an atomic accessor. - return _textContainer.insets; -} - -- (void)setTextContainerLinePositionModifier:(id)modifier -{ - ASLockedSelfCompareAssignObjects(_textContainer.linePositionModifier, modifier); -} - -- (id)textContainerLinePositionModifier -{ - ASLockScopeSelf(); - return _textContainer.linePositionModifier; -} - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - ASDisplayNodeAssert(constrainedSize.width >= 0, @"Constrained width for text (%f) is too narrow", constrainedSize.width); - ASDisplayNodeAssert(constrainedSize.height >= 0, @"Constrained height for text (%f) is too short", constrainedSize.height); - - ASLockScopeSelf(); - - _textContainer.size = constrainedSize; - [self _ensureTruncationText]; - - // If the constrained size has a max/inf value on the text's forward direction, the text node is calculating its intrinsic size. - // Need to consider both width and height when determining if it is calculating instrinsic size. Even the constrained width is provided, the height can be inf - // it may provide a text that is longer than the width and require a wordWrapping line break mode and looking for the height to be calculated. - BOOL isCalculatingIntrinsicSize = (_textContainer.size.width >= ASTextContainerMaxSize.width) || (_textContainer.size.height >= ASTextContainerMaxSize.height); - - NSMutableAttributedString *mutableText = [_attributedText mutableCopy]; - [self prepareAttributedString:mutableText isForIntrinsicSize:isCalculatingIntrinsicSize]; - ASTextLayout *layout = ASTextNodeCompatibleLayoutWithContainerAndText(_textContainer, mutableText); - if (layout.truncatedLine != nil && layout.truncatedLine.size.width > layout.textBoundingSize.width) { - return (CGSize) {MIN(constrainedSize.width, layout.truncatedLine.size.width), layout.textBoundingSize.height}; - } - - return layout.textBoundingSize; -} - -#pragma mark - Modifying User Text - -// Returns the ascender of the first character in attributedString by also including the line height if specified in paragraph style. -+ (CGFloat)ascenderWithAttributedString:(NSAttributedString *)attributedString -{ - UIFont *font = [attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL]; - NSParagraphStyle *paragraphStyle = [attributedString attribute:NSParagraphStyleAttributeName atIndex:0 effectiveRange:NULL]; - if (!paragraphStyle) { - return font.ascender; - } - CGFloat lineHeight = MAX(font.lineHeight, paragraphStyle.minimumLineHeight); - if (paragraphStyle.maximumLineHeight > 0) { - lineHeight = MIN(lineHeight, paragraphStyle.maximumLineHeight); - } - return lineHeight + font.descender; -} - -- (NSAttributedString *)attributedText -{ - ASLockScopeSelf(); - return _attributedText; -} - -- (void)setAttributedText:(NSAttributedString *)attributedText -{ - if (attributedText == nil) { - attributedText = [[NSAttributedString alloc] initWithString:@"" attributes:nil]; - } - - // Many accessors in this method will acquire the lock (including ASDisplayNode methods). - // Holding it for the duration of the method is more efficient in this case. - ASLockScopeSelf(); - - if (!ASCompareAssignCopy(_attributedText, attributedText)) { - return; - } - - // Since truncation text matches style of attributedText, invalidate it now. - [self _locked_invalidateTruncationText]; - - NSUInteger length = attributedText.length; - if (length > 0) { - ASLayoutElementStyle *style = [self _locked_style]; - style.ascender = [[self class] ascenderWithAttributedString:attributedText]; - style.descender = [[attributedText attribute:NSFontAttributeName atIndex:attributedText.length - 1 effectiveRange:NULL] descender]; - } - - // Tell the display node superclasses that the cached layout is incorrect now - [self setNeedsLayout]; - - // Force display to create renderer with new size and redisplay with new string - [self setNeedsDisplay]; - - // Accessiblity - self.accessibilityLabel = self.defaultAccessibilityLabel; - self.isAccessibilityElement = (length != 0); // We're an accessibility element by default if there is a string. - -#if AS_TEXTNODE2_RECORD_ATTRIBUTED_STRINGS - [ASTextNode _registerAttributedText:_attributedText]; -#endif -} - -#pragma mark - Text Layout - -- (void)setExclusionPaths:(NSArray *)exclusionPaths -{ - ASLockScopeSelf(); - _textContainer.exclusionPaths = exclusionPaths; - - [self setNeedsLayout]; - [self setNeedsDisplay]; -} - -- (NSArray *)exclusionPaths -{ - ASLockScopeSelf(); - return _textContainer.exclusionPaths; -} - -- (void)prepareAttributedString:(NSMutableAttributedString *)attributedString isForIntrinsicSize:(BOOL)isForIntrinsicSize -{ - ASLockScopeSelf(); - NSLineBreakMode innerMode; - switch (_truncationMode) { - case NSLineBreakByWordWrapping: - case NSLineBreakByCharWrapping: - case NSLineBreakByClipping: - innerMode = _truncationMode; - break; - default: - innerMode = NSLineBreakByWordWrapping; - } - - // Apply/Fix paragraph style if needed - [attributedString enumerateAttribute:NSParagraphStyleAttributeName inRange:NSMakeRange(0, attributedString.length) options:kNilOptions usingBlock:^(NSParagraphStyle *style, NSRange range, BOOL * _Nonnull stop) { - - BOOL applyTruncationMode = YES; - NSMutableParagraphStyle *paragraphStyle = nil; - // Only "left" and "justified" alignments are supported while calculating intrinsic size. - // Other alignments like "right", "center" and "natural" cause the size to be bigger than needed and thus should be ignored/overridden. - const BOOL forceLeftAlignment = (style != nil - && isForIntrinsicSize - && style.alignment != NSTextAlignmentLeft - && style.alignment != NSTextAlignmentJustified); - if (style != nil) { - if (innerMode == style.lineBreakMode) { - applyTruncationMode = NO; - } - paragraphStyle = [style mutableCopy]; - } else { - if (innerMode == NSLineBreakByWordWrapping) { - applyTruncationMode = NO; - } - paragraphStyle = [NSMutableParagraphStyle new]; - } - if (!applyTruncationMode && !forceLeftAlignment) { - return; - } - paragraphStyle.lineBreakMode = innerMode; - - if (applyTruncationMode) { - paragraphStyle.lineBreakMode = _truncationMode; - } - if (forceLeftAlignment) { - paragraphStyle.alignment = NSTextAlignmentLeft; - } - [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range]; - }]; - - // Apply shadow if needed - if (_shadowOpacity > 0 && (_shadowRadius != 0 || !CGSizeEqualToSize(_shadowOffset, CGSizeZero)) && CGColorGetAlpha(_shadowColor) > 0) { - NSShadow *shadow = [[NSShadow alloc] init]; - if (_shadowOpacity != 1) { - CGColorRef shadowColorRef = CGColorCreateCopyWithAlpha(_shadowColor, _shadowOpacity * CGColorGetAlpha(_shadowColor)); - shadow.shadowColor = [UIColor colorWithCGColor:shadowColorRef]; - CGColorRelease(shadowColorRef); - } else { - shadow.shadowColor = [UIColor colorWithCGColor:_shadowColor]; - } - shadow.shadowOffset = _shadowOffset; - shadow.shadowBlurRadius = _shadowRadius; - [attributedString addAttribute:NSShadowAttributeName value:shadow range:NSMakeRange(0, attributedString.length)]; - } -} - -#pragma mark - Drawing - -- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer -{ - ASLockScopeSelf(); - [self _ensureTruncationText]; - - // Unlike layout, here we must copy the container since drawing is asynchronous. - ASTextContainer *copiedContainer = [_textContainer copy]; - copiedContainer.size = self.bounds.size; - [copiedContainer makeImmutable]; - NSMutableAttributedString *mutableText = [_attributedText mutableCopy] ?: [[NSMutableAttributedString alloc] init]; - - [self prepareAttributedString:mutableText isForIntrinsicSize:NO]; - - return @{ - @"container": copiedContainer, - @"text": mutableText, - @"bgColor": self.backgroundColor ?: [NSNull null] - }; -} - -+ (void)drawRect:(CGRect)bounds withParameters:(NSDictionary *)layoutDict isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing -{ - ASTextContainer *container = layoutDict[@"container"]; - NSAttributedString *text = layoutDict[@"text"]; - UIColor *bgColor = layoutDict[@"bgColor"]; - ASTextLayout *layout = ASTextNodeCompatibleLayoutWithContainerAndText(container, text); - - if (isCancelledBlock()) { - return; - } - - // Fill background color. - if (bgColor == (id)[NSNull null]) { - bgColor = nil; - } - - // They may have already drawn into this context in the pre-context block - // so unfortunately we have to use the normal blend mode, not copy. - if (bgColor && CGColorGetAlpha(bgColor.CGColor) > 0) { - [bgColor setFill]; - UIRectFillUsingBlendMode(bounds, kCGBlendModeNormal); - } - - CGContextRef context = UIGraphicsGetCurrentContext(); - ASDisplayNodeAssert(context, @"This is no good without a context."); - - [layout drawInContext:context size:bounds.size point:bounds.origin view:nil layer:nil debug:[ASTextDebugOption sharedDebugOption] cancel:isCancelledBlock]; -} - -#pragma mark - Attributes - -- (id)linkAttributeValueAtPoint:(CGPoint)point - attributeName:(out NSString **)attributeNameOut - range:(out NSRange *)rangeOut -{ - return [self _linkAttributeValueAtPoint:point - attributeName:attributeNameOut - range:rangeOut - inAdditionalTruncationMessage:NULL - forHighlighting:NO]; -} - -- (id)_linkAttributeValueAtPoint:(CGPoint)point - attributeName:(out NSString **)attributeNameOut - range:(out NSRange *)rangeOut - inAdditionalTruncationMessage:(out BOOL *)inAdditionalTruncationMessageOut - forHighlighting:(BOOL)highlighting -{ - ASLockScopeSelf(); - - // TODO: The copy and application of size shouldn't be required, but it is currently. - // See discussion in https://github.com/TextureGroup/Texture/pull/396 - ASTextContainer *containerCopy = [_textContainer copy]; - containerCopy.size = self.calculatedSize; - ASTextLayout *layout = ASTextNodeCompatibleLayoutWithContainerAndText(containerCopy, _attributedText); - - if ([self _locked_pointInsideAdditionalTruncationMessage:point withLayout:layout]) { - if (inAdditionalTruncationMessageOut != NULL) { - *inAdditionalTruncationMessageOut = YES; - } - return nil; - } - - NSRange visibleRange = layout.visibleRange; - NSRange clampedRange = NSIntersectionRange(visibleRange, NSMakeRange(0, _attributedText.length)); - - // Search the 9 points of a 44x44 square around the touch until we find a link. - // Start from center, then do sides, then do top/bottom, then do corners. - static constexpr CGSize kRectOffsets[9] = { - { 0, 0 }, - { -22, 0 }, { 22, 0 }, - { 0, -22 }, { 0, 22 }, - { -22, -22 }, { -22, 22 }, - { 22, -22 }, { 22, 22 } - }; - - for (const CGSize &offset : kRectOffsets) { - const CGPoint testPoint = CGPointMake(point.x + offset.width, - point.y + offset.height); - ASTextPosition *pos = [layout closestPositionToPoint:testPoint]; - if (!pos || !NSLocationInRange(pos.offset, clampedRange)) { - continue; - } - for (NSString *attributeName in _linkAttributeNames) { - NSRange effectiveRange = NSMakeRange(0, 0); - id value = [_attributedText attribute:attributeName atIndex:pos.offset - longestEffectiveRange:&effectiveRange inRange:clampedRange]; - if (value == nil) { - // Didn't find any links specified with this attribute. - continue; - } - - // If highlighting, check with delegate first. If not implemented, assume YES. - if (highlighting - && [_delegate respondsToSelector:@selector(textNode:shouldHighlightLinkAttribute:value:atPoint:)] - && ![_delegate textNode:(ASTextNode *)self shouldHighlightLinkAttribute:attributeName - value:value atPoint:point]) { - continue; - } - - *rangeOut = NSIntersectionRange(visibleRange, effectiveRange); - - if (attributeNameOut != NULL) { - *attributeNameOut = attributeName; - } - - return value; - } - } - - return nil; -} - -- (BOOL)_locked_pointInsideAdditionalTruncationMessage:(CGPoint)point withLayout:(ASTextLayout *)layout -{ - // Check if the range is within the additional truncation range - BOOL inAdditionalTruncationMessage = NO; - - CTLineRef truncatedCTLine = layout.truncatedLine.CTLine; - if (truncatedCTLine != NULL && _additionalTruncationMessage != nil) { - CFIndex stringIndexForPosition = CTLineGetStringIndexForPosition(truncatedCTLine, point); - if (stringIndexForPosition != kCFNotFound) { - CFIndex truncatedCTLineGlyphCount = CTLineGetGlyphCount(truncatedCTLine); - - CTLineRef truncationTokenLine = CTLineCreateWithAttributedString((CFAttributedStringRef)_truncationAttributedText); - CFIndex truncationTokenLineGlyphCount = truncationTokenLine ? CTLineGetGlyphCount(truncationTokenLine) : 0; - CFRelease(truncationTokenLine); - - CTLineRef additionalTruncationTokenLine = CTLineCreateWithAttributedString((CFAttributedStringRef)_additionalTruncationMessage); - CFIndex additionalTruncationTokenLineGlyphCount = additionalTruncationTokenLine ? CTLineGetGlyphCount(additionalTruncationTokenLine) : 0; - CFRelease(additionalTruncationTokenLine); - - switch (_textContainer.truncationType) { - case ASTextTruncationTypeStart: { - CFIndex composedTruncationTextLineGlyphCount = truncationTokenLineGlyphCount + additionalTruncationTokenLineGlyphCount; - if (stringIndexForPosition > truncationTokenLineGlyphCount && - stringIndexForPosition < composedTruncationTextLineGlyphCount) { - inAdditionalTruncationMessage = YES; - } - break; - } - case ASTextTruncationTypeMiddle: { - CFIndex composedTruncationTextLineGlyphCount = truncationTokenLineGlyphCount + additionalTruncationTokenLineGlyphCount; - CFIndex firstTruncatedTokenIndex = (truncatedCTLineGlyphCount - composedTruncationTextLineGlyphCount) / 2.0; - if ((firstTruncatedTokenIndex + truncationTokenLineGlyphCount) < stringIndexForPosition && - stringIndexForPosition < (firstTruncatedTokenIndex + composedTruncationTextLineGlyphCount)) { - inAdditionalTruncationMessage = YES; - } - break; - } - case ASTextTruncationTypeEnd: { - if (stringIndexForPosition > (truncatedCTLineGlyphCount - additionalTruncationTokenLineGlyphCount)) { - inAdditionalTruncationMessage = YES; - } - break; - } - default: - // For now, assume that a tap inside this text, but outside the text range is a tap on the - // truncation token. - if (![layout textRangeAtPoint:point]) { - inAdditionalTruncationMessage = YES; - } - break; - } - } - } - - return inAdditionalTruncationMessage; -} - -#pragma mark - UIGestureRecognizerDelegate - -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer -{ - ASDisplayNodeAssertMainThread(); - ASLockScopeSelf(); // Protect usage of _highlight* ivars. - - if (gestureRecognizer == _longPressGestureRecognizer) { - // Don't allow long press on truncation message - if ([self _pendingTruncationTap]) { - return NO; - } - - // Ask our delegate if a long-press on an attribute is relevant - id delegate = self.delegate; - if ([delegate respondsToSelector:@selector(textNode:shouldLongPressLinkAttribute:value:atPoint:)]) { - return [delegate textNode:(ASTextNode *)self - shouldLongPressLinkAttribute:_highlightedLinkAttributeName - value:_highlightedLinkAttributeValue - atPoint:[gestureRecognizer locationInView:self.view]]; - } - - // Otherwise we are good to go. - return YES; - } - - if (([self _pendingLinkTap] || [self _pendingTruncationTap]) - && [gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] - && CGRectContainsPoint(self.threadSafeBounds, [gestureRecognizer locationInView:self.view])) { - return NO; - } - - return [super gestureRecognizerShouldBegin:gestureRecognizer]; -} - -#pragma mark - Highlighting - -- (ASTextNodeHighlightStyle)highlightStyle -{ - ASLockScopeSelf(); - - return _highlightStyle; -} - -- (void)setHighlightStyle:(ASTextNodeHighlightStyle)highlightStyle -{ - ASLockScopeSelf(); - - _highlightStyle = highlightStyle; -} - -- (NSRange)highlightRange -{ - ASLockScopeSelf(); - - return _highlightRange; -} - -- (void)setHighlightRange:(NSRange)highlightRange -{ - [self setHighlightRange:highlightRange animated:NO]; -} - -- (void)setHighlightRange:(NSRange)highlightRange animated:(BOOL)animated -{ - [self _setHighlightRange:highlightRange forAttributeName:nil value:nil animated:animated]; -} - -- (void)_setHighlightRange:(NSRange)highlightRange forAttributeName:(NSString *)highlightedAttributeName value:(id)highlightedAttributeValue animated:(BOOL)animated -{ - ASLockScopeSelf(); // Protect usage of _highlight* ivars. - - // Set these so that link tapping works. - _highlightedLinkAttributeName = highlightedAttributeName; - _highlightedLinkAttributeValue = highlightedAttributeValue; - _highlightRange = highlightRange; - - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - // Much of the code from original ASTextNode is probably usable here. - - return; -} - -- (void)_clearHighlightIfNecessary -{ - ASDisplayNodeAssertMainThread(); - - if ([self _pendingLinkTap] || [self _pendingTruncationTap]) { - [self setHighlightRange:NSMakeRange(0, 0) animated:YES]; - } -} - -+ (CGColorRef)_highlightColorForStyle:(ASTextNodeHighlightStyle)style -{ - return [UIColor colorWithWhite:(style == ASTextNodeHighlightStyleLight ? 0.0 : 1.0) alpha:1.0].CGColor; -} - -+ (CGFloat)_highlightOpacityForStyle:(ASTextNodeHighlightStyle)style -{ - return (style == ASTextNodeHighlightStyleLight) ? ASTextNodeHighlightLightOpacity : ASTextNodeHighlightDarkOpacity; -} - -#pragma mark - Text rects - -- (NSArray *)rectsForTextRange:(NSRange)textRange -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return @[]; -} - -- (NSArray *)highlightRectsForTextRange:(NSRange)textRange -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return @[]; -} - -- (CGRect)trailingRect -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return CGRectZero; -} - -- (CGRect)frameForTextRange:(NSRange)textRange -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return CGRectZero; -} - -#pragma mark - Placeholders - -- (UIColor *)placeholderColor -{ - return ASLockedSelf(_placeholderColor); -} - -- (void)setPlaceholderColor:(UIColor *)placeholderColor -{ - ASLockScopeSelf(); - if (ASCompareAssignCopy(_placeholderColor, placeholderColor)) { - self.placeholderEnabled = CGColorGetAlpha(placeholderColor.CGColor) > 0; - } -} - -- (UIImage *)placeholderImage -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return nil; -} - -#pragma mark - Touch Handling - -- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - - if (!_passthroughNonlinkTouches) { - return [super pointInside:point withEvent:event]; - } - - NSRange range = NSMakeRange(0, 0); - NSString *linkAttributeName = nil; - BOOL inAdditionalTruncationMessage = NO; - - id linkAttributeValue = [self _linkAttributeValueAtPoint:point - attributeName:&linkAttributeName - range:&range - inAdditionalTruncationMessage:&inAdditionalTruncationMessage - forHighlighting:YES]; - - NSUInteger lastCharIndex = NSIntegerMax; - BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1); - - if (range.length > 0 && !linkCrossesVisibleRange && linkAttributeValue != nil && linkAttributeName != nil) { - return YES; - } else { - return NO; - } -} - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - - [super touchesBegan:touches withEvent:event]; - - CGPoint point = [[touches anyObject] locationInView:self.view]; - - NSRange range = NSMakeRange(0, 0); - NSString *linkAttributeName = nil; - BOOL inAdditionalTruncationMessage = NO; - - id linkAttributeValue = [self _linkAttributeValueAtPoint:point - attributeName:&linkAttributeName - range:&range - inAdditionalTruncationMessage:&inAdditionalTruncationMessage - forHighlighting:YES]; - - NSUInteger lastCharIndex = NSIntegerMax; - BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1); - - if (inAdditionalTruncationMessage) { - NSRange visibleRange = NSMakeRange(0, 0); - { - ASLockScopeSelf(); - // TODO: The copy and application of size shouldn't be required, but it is currently. - // See discussion in https://github.com/TextureGroup/Texture/pull/396 - ASTextContainer *containerCopy = [_textContainer copy]; - containerCopy.size = self.calculatedSize; - ASTextLayout *layout = ASTextNodeCompatibleLayoutWithContainerAndText(containerCopy, _attributedText); - visibleRange = layout.visibleRange; - } - NSRange truncationMessageRange = [self _additionalTruncationMessageRangeWithVisibleRange:visibleRange]; - [self _setHighlightRange:truncationMessageRange forAttributeName:ASTextNodeTruncationTokenAttributeName value:nil animated:YES]; - } else if (range.length > 0 && !linkCrossesVisibleRange && linkAttributeValue != nil && linkAttributeName != nil) { - [self _setHighlightRange:range forAttributeName:linkAttributeName value:linkAttributeValue animated:YES]; - } - - return; -} - - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - [super touchesCancelled:touches withEvent:event]; - - [self _clearHighlightIfNecessary]; -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - [super touchesEnded:touches withEvent:event]; - - ASLockScopeSelf(); // Protect usage of _highlight* ivars. - id delegate = self.delegate; - if ([self _pendingLinkTap] && [delegate respondsToSelector:@selector(textNode:tappedLinkAttribute:value:atPoint:textRange:)]) { - CGPoint point = [[touches anyObject] locationInView:self.view]; - [delegate textNode:(ASTextNode *)self tappedLinkAttribute:_highlightedLinkAttributeName value:_highlightedLinkAttributeValue atPoint:point textRange:_highlightRange]; - } - - if ([self _pendingTruncationTap]) { - if ([delegate respondsToSelector:@selector(textNodeTappedTruncationToken:)]) { - [delegate textNodeTappedTruncationToken:(ASTextNode *)self]; - } - } - - [self _clearHighlightIfNecessary]; -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - [super touchesMoved:touches withEvent:event]; - - ASLockScopeSelf(); // Protect usage of _highlight* ivars. - UITouch *touch = [touches anyObject]; - CGPoint locationInView = [touch locationInView:self.view]; - // on 3D Touch enabled phones, this gets fired with changes in force, and usually will get fired immediately after touchesBegan:withEvent: - if (CGPointEqualToPoint([touch previousLocationInView:self.view], locationInView)) - return; - - // If touch has moved out of the current highlight range, clear the highlight. - if (_highlightRange.length > 0) { - NSRange range = NSMakeRange(0, 0); - [self _linkAttributeValueAtPoint:locationInView - attributeName:NULL - range:&range - inAdditionalTruncationMessage:NULL - forHighlighting:YES]; - - if (!NSEqualRanges(_highlightRange, range)) { - [self _clearHighlightIfNecessary]; - } - } -} - -- (void)_handleLongPress:(UILongPressGestureRecognizer *)longPressRecognizer -{ - ASDisplayNodeAssertMainThread(); - - // Respond to long-press when it begins, not when it ends. - if (longPressRecognizer.state == UIGestureRecognizerStateBegan) { - id delegate = self.delegate; - if ([delegate respondsToSelector:@selector(textNode:longPressedLinkAttribute:value:atPoint:textRange:)]) { - ASLockScopeSelf(); // Protect usage of _highlight* ivars. - CGPoint touchPoint = [_longPressGestureRecognizer locationInView:self.view]; - [delegate textNode:(ASTextNode *)self longPressedLinkAttribute:_highlightedLinkAttributeName value:_highlightedLinkAttributeValue atPoint:touchPoint textRange:_highlightRange]; - } - } -} - -- (BOOL)_pendingLinkTap -{ - ASLockScopeSelf(); - - return (_highlightedLinkAttributeValue != nil && ![self _pendingTruncationTap]) && self.delegate != nil; -} - -- (BOOL)_pendingTruncationTap -{ - return [ASLockedSelf(_highlightedLinkAttributeName) isEqualToString:ASTextNodeTruncationTokenAttributeName]; -} - -#pragma mark - Shadow Properties - -/** - * Note about shadowed text: - * - * Shadowed text is pretty rare, and we are a framework that targets serious developers. - * We should probably ignore these properties and tell developers to set the shadow into their attributed text instead. - */ -- (CGColorRef)shadowColor -{ - return ASLockedSelf(_shadowColor); -} - -- (void)setShadowColor:(CGColorRef)shadowColor -{ - ASLockScopeSelf(); - if (_shadowColor != shadowColor && CGColorEqualToColor(shadowColor, _shadowColor) == NO) { - CGColorRelease(_shadowColor); - _shadowColor = CGColorRetain(shadowColor); - [self setNeedsDisplay]; - } -} - -- (CGSize)shadowOffset -{ - return ASLockedSelf(_shadowOffset); -} - -- (void)setShadowOffset:(CGSize)shadowOffset -{ - ASLockScopeSelf(); - if (ASCompareAssignCustom(_shadowOffset, shadowOffset, CGSizeEqualToSize)) { - [self setNeedsDisplay]; - } -} - -- (CGFloat)shadowOpacity -{ - return ASLockedSelf(_shadowOpacity); -} - -- (void)setShadowOpacity:(CGFloat)shadowOpacity -{ - ASLockScopeSelf(); - if (ASCompareAssign(_shadowOpacity, shadowOpacity)) { - [self setNeedsDisplay]; - } -} - -- (CGFloat)shadowRadius -{ - return ASLockedSelf(_shadowRadius); -} - -- (void)setShadowRadius:(CGFloat)shadowRadius -{ - ASLockScopeSelf(); - if (ASCompareAssign(_shadowRadius, shadowRadius)) { - [self setNeedsDisplay]; - } -} - -- (UIEdgeInsets)shadowPadding -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return UIEdgeInsetsZero; -} - -- (void)setPointSizeScaleFactors:(NSArray *)scaleFactors -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - ASLockScopeSelf(); - if (ASCompareAssignCopy(_pointSizeScaleFactors, scaleFactors)) { - [self setNeedsLayout]; - } -} - -- (NSArray *)pointSizeScaleFactors -{ - return ASLockedSelf(_pointSizeScaleFactors); -} - -#pragma mark - Truncation Message - -static NSAttributedString *DefaultTruncationAttributedString() -{ - static NSAttributedString *defaultTruncationAttributedString; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - defaultTruncationAttributedString = [[NSAttributedString alloc] initWithString:NSLocalizedString(@"\u2026", @"Default truncation string")]; - }); - return defaultTruncationAttributedString; -} - -- (void)_ensureTruncationText -{ - ASLockScopeSelf(); - if (_textContainer.truncationToken == nil) { - _textContainer.truncationToken = [self _locked_composedTruncationText]; - } -} - -- (NSAttributedString *)truncationAttributedText -{ - return ASLockedSelf(_truncationAttributedText); -} - -- (void)setTruncationAttributedText:(NSAttributedString *)truncationAttributedText -{ - ASLockScopeSelf(); - if (ASCompareAssignCopy(_truncationAttributedText, truncationAttributedText)) { - [self _invalidateTruncationText]; - } -} - -- (NSAttributedString *)additionalTruncationMessage -{ - return ASLockedSelf(_additionalTruncationMessage); -} - -- (void)setAdditionalTruncationMessage:(NSAttributedString *)additionalTruncationMessage -{ - ASLockScopeSelf(); - if (ASCompareAssignCopy(_additionalTruncationMessage, additionalTruncationMessage)) { - [self _invalidateTruncationText]; - } -} - -- (NSLineBreakMode)truncationMode -{ - return ASLockedSelf(_truncationMode); -} - -- (void)setTruncationMode:(NSLineBreakMode)truncationMode -{ - ASLockScopeSelf(); - if (ASCompareAssign(_truncationMode, truncationMode)) { - ASTextTruncationType truncationType; - switch (truncationMode) { - case NSLineBreakByTruncatingHead: - truncationType = ASTextTruncationTypeStart; - break; - case NSLineBreakByTruncatingTail: - truncationType = ASTextTruncationTypeEnd; - break; - case NSLineBreakByTruncatingMiddle: - truncationType = ASTextTruncationTypeMiddle; - break; - default: - truncationType = ASTextTruncationTypeNone; - } - - _textContainer.truncationType = truncationType; - - [self setNeedsDisplay]; - } -} - -- (BOOL)isTruncated -{ - return ASLockedSelf([self locked_textLayoutForSize:[self _locked_threadSafeBounds].size].truncatedLine != nil); -} - -- (BOOL)shouldTruncateForConstrainedSize:(ASSizeRange)constrainedSize -{ - return ASLockedSelf([self locked_textLayoutForSize:constrainedSize.max].truncatedLine != nil); -} - -- (ASTextLayout *)locked_textLayoutForSize:(CGSize)size -{ - ASTextContainer *container = [_textContainer copy]; - container.size = size; - return ASTextNodeCompatibleLayoutWithContainerAndText(container, _attributedText); -} - -- (NSUInteger)maximumNumberOfLines -{ - // _textContainer is invariant and this is just atomic access. - return _textContainer.maximumNumberOfRows; -} - -- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines -{ - ASLockScopeSelf(); - if (ASCompareAssign(_textContainer.maximumNumberOfRows, maximumNumberOfLines)) { - [self setNeedsDisplay]; - } -} - -- (NSUInteger)lineCount -{ - ASLockScopeSelf(); - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return 0; -} - -#pragma mark - Truncation Message - -- (void)_invalidateTruncationText -{ - ASLockScopeSelf(); - [self _locked_invalidateTruncationText]; - [self setNeedsDisplay]; -} - -- (void)_locked_invalidateTruncationText -{ - _textContainer.truncationToken = nil; -} - -/** - * @return the additional truncation message range within the as-rendered text. - * Must be called from main thread - */ -- (NSRange)_additionalTruncationMessageRangeWithVisibleRange:(NSRange)visibleRange -{ - ASLockScopeSelf(); - - // Check if we even have an additional truncation message. - if (!_additionalTruncationMessage) { - return NSMakeRange(NSNotFound, 0); - } - - // Character location of the unicode ellipsis (the first index after the visible range) - NSInteger truncationTokenIndex = NSMaxRange(visibleRange); - - NSUInteger additionalTruncationMessageLength = _additionalTruncationMessage.length; - // We get the location of the truncation token, then add the length of the - // truncation attributed string +1 for the space between. - return NSMakeRange(truncationTokenIndex + _truncationAttributedText.length + 1, additionalTruncationMessageLength); -} - -/** - * @return the truncation message for the string. If there are both an - * additional truncation message and a truncation attributed string, they will - * be properly composed. - */ -- (NSAttributedString *)_locked_composedTruncationText -{ - ASAssertLocked(__instanceLock__); - if (_composedTruncationText == nil) { - if (_truncationAttributedText != nil && _additionalTruncationMessage != nil) { - NSMutableAttributedString *newComposedTruncationString = [[NSMutableAttributedString alloc] initWithAttributedString:_truncationAttributedText]; - [newComposedTruncationString.mutableString appendString:@" "]; - [newComposedTruncationString appendAttributedString:_additionalTruncationMessage]; - _composedTruncationText = newComposedTruncationString; - } else if (_truncationAttributedText != nil) { - _composedTruncationText = _truncationAttributedText; - } else if (_additionalTruncationMessage != nil) { - _composedTruncationText = _additionalTruncationMessage; - } else { - _composedTruncationText = DefaultTruncationAttributedString(); - } - _composedTruncationText = [self _locked_prepareTruncationStringForDrawing:_composedTruncationText]; - } - return _composedTruncationText; -} - -/** - * - cleanses it of core text attributes so TextKit doesn't crash - * - Adds whole-string attributes so the truncation message matches the styling - * of the body text - */ -- (NSAttributedString *)_locked_prepareTruncationStringForDrawing:(NSAttributedString *)truncationString -{ - ASAssertLocked(__instanceLock__); - NSMutableAttributedString *truncationMutableString = [truncationString mutableCopy]; - // Grab the attributes from the full string - if (_attributedText.length > 0) { - NSAttributedString *originalString = _attributedText; - NSInteger originalStringLength = _attributedText.length; - // Add any of the original string's attributes to the truncation string, - // but don't overwrite any of the truncation string's attributes - NSDictionary *originalStringAttributes = [originalString attributesAtIndex:originalStringLength-1 effectiveRange:NULL]; - [truncationString enumerateAttributesInRange:NSMakeRange(0, truncationString.length) options:0 usingBlock: - ^(NSDictionary *attributes, NSRange range, BOOL *stop) { - NSMutableDictionary *futureTruncationAttributes = [originalStringAttributes mutableCopy]; - [futureTruncationAttributes addEntriesFromDictionary:attributes]; - [truncationMutableString setAttributes:futureTruncationAttributes range:range]; - }]; - } - return truncationMutableString; -} - -#if AS_TEXTNODE2_RECORD_ATTRIBUTED_STRINGS -+ (void)_registerAttributedText:(NSAttributedString *)str -{ - static NSMutableArray *array; - static NSLock *lock; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - lock = [NSLock new]; - array = [NSMutableArray new]; - }); - [lock lock]; - [array addObject:str]; - if (array.count % 20 == 0) { - NSLog(@"Got %d strings", (int)array.count); - } - if (array.count == 2000) { - NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"AttributedStrings.plist"]; - NSAssert([NSKeyedArchiver archiveRootObject:array toFile:path], nil); - NSLog(@"Saved to %@", path); - } - [lock unlock]; -} -#endif - -+ (void)enableDebugging -{ - ASTextDebugOption *debugOption = [[ASTextDebugOption alloc] init]; - debugOption.CTLineFillColor = [UIColor colorWithRed:0 green:0.3 blue:1 alpha:0.1]; - [ASTextDebugOption setSharedDebugOption:debugOption]; -} - -- (BOOL)usingExperiment -{ - return YES; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/ASTextNodeCommon.h b/submodules/AsyncDisplayKit/Source/ASTextNodeCommon.h deleted file mode 100644 index ab4e134bfd..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTextNodeCommon.h +++ /dev/null @@ -1,91 +0,0 @@ -// -// ASTextNodeCommon.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -@class ASTextNode; - -#define AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE() { \ - static dispatch_once_t onceToken; \ - dispatch_once(&onceToken, ^{ \ - NSLog(@"[Texture] Warning: Feature %@ is unimplemented in %@.", NSStringFromSelector(_cmd), NSStringFromClass(self.class)); \ - });\ -} - -/** - * Highlight styles. - */ -typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { - /** - * Highlight style for text on a light background. - */ - ASTextNodeHighlightStyleLight, - - /** - * Highlight style for text on a dark background. - */ - ASTextNodeHighlightStyleDark -}; - -/** - * @abstract Text node delegate. - */ -@protocol ASTextNodeDelegate -@optional - -/** - @abstract Indicates to the delegate that a link was tapped within a text node. - @param textNode The ASTextNode containing the link that was tapped. - @param attribute The attribute that was tapped. Will not be nil. - @param value The value of the tapped attribute. - @param point The point within textNode, in textNode's coordinate system, that was tapped. - @param textRange The range of highlighted text. - */ -- (void)textNode:(ASTextNode *)textNode tappedLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point textRange:(NSRange)textRange; - -/** - @abstract Indicates to the delegate that a link was tapped within a text node. - @param textNode The ASTextNode containing the link that was tapped. - @param attribute The attribute that was tapped. Will not be nil. - @param value The value of the tapped attribute. - @param point The point within textNode, in textNode's coordinate system, that was tapped. - @param textRange The range of highlighted text. - @discussion In addition to implementing this method, the delegate must be set on the text - node before it is loaded (the recognizer is created in -didLoad) - */ -- (void)textNode:(ASTextNode *)textNode longPressedLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point textRange:(NSRange)textRange; - -//! @abstract Called when the text node's truncation string has been tapped. -- (void)textNodeTappedTruncationToken:(ASTextNode *)textNode; - -/** - @abstract Indicates to the text node if an attribute should be considered a link. - @param textNode The text node containing the entity attribute. - @param attribute The attribute that was tapped. Will not be nil. - @param value The value of the tapped attribute. - @param point The point within textNode, in textNode's coordinate system, that was touched to trigger a highlight. - @discussion If not implemented, the default value is YES. - @return YES if the entity attribute should be a link, NO otherwise. - */ -- (BOOL)textNode:(ASTextNode *)textNode shouldHighlightLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point; - -/** - @abstract Indicates to the text node if an attribute is a valid long-press target - @param textNode The text node containing the entity attribute. - @param attribute The attribute that was tapped. Will not be nil. - @param value The value of the tapped attribute. - @param point The point within textNode, in textNode's coordinate system, that was long-pressed. - @discussion If not implemented, the default value is NO. - @return YES if the entity attribute should be treated as a long-press target, NO otherwise. - */ -- (BOOL)textNode:(ASTextNode *)textNode shouldLongPressLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point; - -@end - diff --git a/submodules/AsyncDisplayKit/Source/ASTip.h b/submodules/AsyncDisplayKit/Source/ASTip.h deleted file mode 100644 index 5ac6ac18bc..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTip.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// ASTip.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 -#import - -#if AS_ENABLE_TIPS - -NS_ASSUME_NONNULL_BEGIN - -@class ASDisplayNode; - -typedef NS_ENUM (NSInteger, ASTipKind) { - ASTipKindEnableLayerBacking -}; - -AS_SUBCLASSING_RESTRICTED -@interface ASTip : NSObject - -- (instancetype)initWithNode:(ASDisplayNode *)node - kind:(ASTipKind)kind - format:(NSString *)format, ... NS_FORMAT_FUNCTION(3, 4); - -/** - * The kind of tip this is. - */ -@property (nonatomic, readonly) ASTipKind kind; - -/** - * The node that this tip applies to. - */ -@property (nonatomic, readonly) ASDisplayNode *node; - -/** - * The text to show the user. - */ -@property (nonatomic, readonly) NSString *text; - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/ASTip.mm b/submodules/AsyncDisplayKit/Source/ASTip.mm deleted file mode 100644 index bfabd5e1e7..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTip.mm +++ /dev/null @@ -1,35 +0,0 @@ -// -// ASTip.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 -// - -#if AS_ENABLE_TIPS - -#import - -#import - -@implementation ASTip - -- (instancetype)initWithNode:(ASDisplayNode *)node - kind:(ASTipKind)kind - format:(NSString *)format, ... -{ - if (self = [super init]) { - _node = node; - _kind = kind; - va_list args; - va_start(args, format); - _text = [[NSString alloc] initWithFormat:format arguments:args]; - va_end(args); - } - return self; -} - -@end - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/ASTipNode.h b/submodules/AsyncDisplayKit/Source/ASTipNode.h deleted file mode 100644 index d01637d869..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTipNode.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// ASTipNode.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 -#import - -#if AS_ENABLE_TIPS - -@class ASTip; - -NS_ASSUME_NONNULL_BEGIN - -/** - * ASTipNode will send these up the responder chain. - */ -@protocol ASTipNodeActions -- (void)didTapTipNode:(id)sender; -@end - -AS_SUBCLASSING_RESTRICTED -@interface ASTipNode : ASControlNode - -- (instancetype)initWithTip:(ASTip *)tip NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; - -@property (nonatomic, readonly) ASTip *tip; - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/ASTipNode.mm b/submodules/AsyncDisplayKit/Source/ASTipNode.mm deleted file mode 100644 index dcca908b8a..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTipNode.mm +++ /dev/null @@ -1,28 +0,0 @@ -// -// ASTipNode.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 "ASTipNode.h" - -#if AS_ENABLE_TIPS - -@implementation ASTipNode - -- (instancetype)initWithTip:(ASTip *)tip -{ - if (self = [super init]) { - self.backgroundColor = [UIColor colorWithRed:0 green:0.7 blue:0.2 alpha:0.3]; - _tip = tip; - [self addTarget:nil action:@selector(didTapTipNode:) forControlEvents:ASControlNodeEventTouchUpInside]; - } - return self; -} - -@end - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/ASTipProvider.h b/submodules/AsyncDisplayKit/Source/ASTipProvider.h deleted file mode 100644 index e2aba6c5d9..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTipProvider.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// ASTipProvider.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 -#import - -#if AS_ENABLE_TIPS - -@class ASDisplayNode, ASTip; - -NS_ASSUME_NONNULL_BEGIN - -/** - * An abstract superclass for all tip providers. - */ -@interface ASTipProvider : NSObject - -/** - * The provider looks at the node's current situation and - * generates a tip, if any, to add to the node. - * - * Subclasses must override this. - */ -- (nullable ASTip *)tipForNode:(ASDisplayNode *)node; - -@end - -@interface ASTipProvider (Lookup) - -@property (class, nonatomic, copy, readonly) NSArray<__kindof ASTipProvider *> *all; - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/ASTipProvider.mm b/submodules/AsyncDisplayKit/Source/ASTipProvider.mm deleted file mode 100644 index f0dfcbf4cd..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTipProvider.mm +++ /dev/null @@ -1,43 +0,0 @@ -// -// ASTipProvider.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 "ASTipProvider.h" - -#if AS_ENABLE_TIPS - -#import - -// Concrete classes -#import - -@implementation ASTipProvider - -- (ASTip *)tipForNode:(ASDisplayNode *)node -{ - ASDisplayNodeFailAssert(@"Subclasses must override %@", NSStringFromSelector(_cmd)); - return nil; -} - -@end - -@implementation ASTipProvider (Lookup) - -+ (NSArray *)all -{ - static NSArray *providers; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - providers = @[ [ASLayerBackingTipProvider new] ]; - }); - return providers; -} - -@end - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/ASTipsController.h b/submodules/AsyncDisplayKit/Source/ASTipsController.h deleted file mode 100644 index fceeb92f60..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTipsController.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// ASTipsController.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 -#import - -#if AS_ENABLE_TIPS - -@class ASDisplayNode; - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASTipsController : NSObject - -/** - * The shared tip controller instance. - */ -@property (class, readonly) ASTipsController *shared; - -#pragma mark - Node Event Hooks - -/** - * Informs the controller that the sender did enter the visible range. - * - * The controller will run a pass with its tip providers, adding tips as needed. - */ -- (void)nodeDidAppear:(ASDisplayNode *)node; - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/ASTipsController.mm b/submodules/AsyncDisplayKit/Source/ASTipsController.mm deleted file mode 100644 index 344b0b1f8e..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTipsController.mm +++ /dev/null @@ -1,185 +0,0 @@ -// -// ASTipsController.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 "ASTipsController.h" - -#if AS_ENABLE_TIPS - -#import -#import -#import -#import -#import -#import - -@interface ASTipsController () - -/// Nil on init, updates to most recent visible window. -@property (nonatomic) UIWindow *appVisibleWindow; - -/// Nil until an application window has become visible. -@property (nonatomic) ASTipsWindow *tipWindow; - -/// Main-thread-only. -@property (nonatomic, readonly) NSMapTable *nodeToTipStates; - -@property (nonatomic) NSMutableArray *nodesThatAppearedDuringRunLoop; - -@end - -@implementation ASTipsController - -#pragma mark - Singleton - -+ (void)load -{ - [NSNotificationCenter.defaultCenter addObserver:self.shared - selector:@selector(windowDidBecomeVisibleWithNotification:) - name:UIWindowDidBecomeVisibleNotification - object:nil]; -} - -+ (ASTipsController *)shared -{ - static ASTipsController *ctrl; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - ctrl = [[ASTipsController alloc] init]; - }); - return ctrl; -} - -#pragma mark - Lifecycle - -- (instancetype)init -{ - ASDisplayNodeAssertMainThread(); - if (self = [super init]) { - _nodeToTipStates = [NSMapTable mapTableWithKeyOptions:(NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPointerPersonality) valueOptions:NSPointerFunctionsStrongMemory]; - _nodesThatAppearedDuringRunLoop = [NSMutableArray array]; - } - return self; -} - -#pragma mark - Event Handling - -- (void)nodeDidAppear:(ASDisplayNode *)node -{ - ASDisplayNodeAssertMainThread(); - // If they disabled tips on this class, bail. - if (![[node class] enableTips]) { - return; - } - - // If this node appeared in some other window (like our tips window) ignore it. - if (ASFindWindowOfLayer(node.layer) != self.appVisibleWindow) { - return; - } - - [_nodesThatAppearedDuringRunLoop addObject:node]; -} - -// If this is a main window, start watching it and clear out our tip window. -- (void)windowDidBecomeVisibleWithNotification:(NSNotification *)notification -{ - ASDisplayNodeAssertMainThread(); - UIWindow *window = notification.object; - - // If this is the same window we're already watching, bail. - if (window == self.appVisibleWindow) { - return; - } - - // Ignore windows that are not at the normal level or have empty bounds - if (window.windowLevel != UIWindowLevelNormal || CGRectIsEmpty(window.bounds)) { - return; - } - - self.appVisibleWindow = window; - - // Create the tip window if needed. - [self createTipWindowIfNeededWithFrame:window.bounds]; - - // Clear out our tip window and reset our states. - self.tipWindow.mainWindow = window; - [_nodeToTipStates removeAllObjects]; -} - -- (void)runLoopDidTick -{ - NSArray *nodes = [_nodesThatAppearedDuringRunLoop copy]; - [_nodesThatAppearedDuringRunLoop removeAllObjects]; - - // Go through the old array, removing any that have tips but aren't still visible. - for (ASDisplayNode *node in [_nodeToTipStates copy]) { - if (!node.visible) { - [_nodeToTipStates removeObjectForKey:node]; - } - } - - for (ASDisplayNode *node in nodes) { - // Get the tip state for the node. - ASDisplayNodeTipState *tipState = [_nodeToTipStates objectForKey:node]; - - // If the node already has a tip, bail. This could change. - if (tipState.tipNode != nil) { - return; - } - - for (ASTipProvider *provider in ASTipProvider.all) { - ASTip *tip = [provider tipForNode:node]; - if (!tip) { continue; } - - if (!tipState) { - tipState = [self createTipStateForNode:node]; - } - tipState.tipNode = [[ASTipNode alloc] initWithTip:tip]; - } - } - self.tipWindow.nodeToTipStates = _nodeToTipStates; - [self.tipWindow setNeedsLayout]; -} - -#pragma mark - Internal - -- (void)createTipWindowIfNeededWithFrame:(CGRect)tipWindowFrame -{ - // Lots of property accesses, but simple safe code, only run once. - if (self.tipWindow == nil) { - self.tipWindow = [[ASTipsWindow alloc] initWithFrame:tipWindowFrame]; - self.tipWindow.hidden = NO; - [self setupRunLoopObserver]; - } -} - -/** - * In order to keep the UI updated, the tips controller registers a run loop observer. - * Before the transaction commit happens, the tips controller calls -setNeedsLayout - * on the view controller's view. It will then layout the main window, and then update the frames - * for tip nodes accordingly. - */ -- (void)setupRunLoopObserver -{ - CFRunLoopObserverRef o = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { - [self runLoopDidTick]; - }); - CFRunLoopAddObserver(CFRunLoopGetMain(), o, kCFRunLoopCommonModes); -} - -- (ASDisplayNodeTipState *)createTipStateForNode:(ASDisplayNode *)node -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeTipState *tipState = [[ASDisplayNodeTipState alloc] initWithNode:node]; - [_nodeToTipStates setObject:tipState forKey:node]; - return tipState; -} - -@end - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/ASTipsWindow.h b/submodules/AsyncDisplayKit/Source/ASTipsWindow.h deleted file mode 100644 index fab52510fc..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTipsWindow.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// ASTipsWindow.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 -#import - -#if AS_ENABLE_TIPS - -@class ASDisplayNode, ASDisplayNodeTipState; - -NS_ASSUME_NONNULL_BEGIN - -/** - * A window that shows tips. This was originally meant to be a view controller - * but UIKit will not manage view controllers in non-key windows correctly AT ALL - * as of the time of this writing. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASTipsWindow : UIWindow - -/// The main application window that the tips are tracking. -@property (nonatomic, weak) UIWindow *mainWindow; - -@property (nonatomic, copy, nullable) NSMapTable *nodeToTipStates; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASTipsWindow.mm b/submodules/AsyncDisplayKit/Source/ASTipsWindow.mm deleted file mode 100644 index a5d8a56453..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASTipsWindow.mm +++ /dev/null @@ -1,98 +0,0 @@ -// -// ASTipsWindow.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 "ASTipsWindow.h" -#if AS_ENABLE_TIPS - -#import -#import -#import -#import - -@interface ASTipsWindow () -@property (nonatomic, readonly) ASDisplayNode *node; -@end - -@implementation ASTipsWindow - -- (instancetype)initWithFrame:(CGRect)frame -{ - if (self = [super initWithFrame:frame]) { - /** - * UIKit throws an exception if you don't add a root view controller to a window, - * but if the window isn't key, then it doesn't manage the root view controller correctly! - * - * So we set a dummy root view controller and hide it. - */ - self.rootViewController = [UIViewController new]; - self.rootViewController.view.hidden = YES; - - _node = [[ASDisplayNode alloc] init]; - [self addSubnode:_node]; - - self.windowLevel = UIWindowLevelNormal + 1; - self.opaque = NO; - } - return self; -} - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event -{ - UIView *result = [super hitTest:point withEvent:event]; - // Ignore touches unless they hit one of my node's subnodes - if (result == _node.view) { - return nil; - } - return result; -} - -- (void)setMainWindow:(UIWindow *)mainWindow -{ - _mainWindow = mainWindow; - for (ASDisplayNode *node in _node.subnodes) { - [node removeFromSupernode]; - } -} - -- (void)didTapTipNode:(ASTipNode *)tipNode -{ - ASDisplayNode.tipDisplayBlock(tipNode.tip.node, tipNode.tip.text); -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - _node.frame = self.bounds; - - // Ensure the main window is laid out first. - [self.mainWindow layoutIfNeeded]; - - NSMutableSet *tipNodesToRemove = [NSMutableSet setWithArray:_node.subnodes]; - for (ASDisplayNodeTipState *tipState in [_nodeToTipStates objectEnumerator]) { - ASDisplayNode *node = tipState.node; - ASTipNode *tipNode = tipState.tipNode; - [tipNodesToRemove removeObject:tipNode]; - CGRect rect = node.bounds; - rect = [node.view convertRect:rect toView:nil]; - rect = [self convertRect:rect fromView:nil]; - tipNode.frame = rect; - if (tipNode.supernode != _node) { - [_node addSubnode:tipNode]; - } - } - - // Clean up any tip nodes whose target nodes have disappeared. - for (ASTipNode *tipNode in tipNodesToRemove) { - [tipNode removeFromSupernode]; - } -} - -@end - -#endif // AS_ENABLE_TIPS diff --git a/submodules/AsyncDisplayKit/Source/ASVideoNode.h b/submodules/AsyncDisplayKit/Source/ASVideoNode.h deleted file mode 100644 index 5c38a6f641..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASVideoNode.h +++ /dev/null @@ -1,173 +0,0 @@ -// -// ASVideoNode.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 -#import - -#if AS_USE_VIDEO - -@class AVAsset, AVPlayer, AVPlayerLayer, AVPlayerItem, AVVideoComposition, AVAudioMix; -@protocol ASVideoNodeDelegate; - -typedef NS_ENUM(NSInteger, ASVideoNodePlayerState) { - ASVideoNodePlayerStateUnknown, - ASVideoNodePlayerStateInitialLoading, - ASVideoNodePlayerStateReadyToPlay, - ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying, - ASVideoNodePlayerStatePlaying, - ASVideoNodePlayerStateLoading, - ASVideoNodePlayerStatePaused, - ASVideoNodePlayerStateFinished -}; - -NS_ASSUME_NONNULL_BEGIN - -// IMPORTANT NOTES: -// 1. Applications using ASVideoNode must link AVFoundation! (this provides the AV* classes below) -// 2. This is a relatively new component of AsyncDisplayKit. It has many useful features, but -// there is room for further expansion and optimization. Please report any issues or requests -// in an issue on GitHub: https://github.com/facebook/AsyncDisplayKit/issues - -@interface ASVideoNode : ASNetworkImageNode - -- (void)play; -- (void)pause; -- (BOOL)isPlaying; -- (void)resetToPlaceholder; - -// TODO: copy -@property (nullable) AVAsset *asset; - -/** - ** @abstract The URL with which the asset was initialized. - ** @discussion Setting the URL will override the current asset with a newly created AVURLAsset created from the given URL, and AVAsset *asset will point to that newly created AVURLAsset. Please don't set both assetURL and asset. - ** @return Current URL the asset was initialized or nil if no URL was given. - **/ -@property (nullable, copy) NSURL *assetURL; - -// TODO: copy both of these. -@property (nullable) AVVideoComposition *videoComposition; -@property (nullable) AVAudioMix *audioMix; - -@property (nullable, readonly) AVPlayer *player; - -// TODO: copy -@property (nullable, readonly) AVPlayerItem *currentItem; - -@property (nullable, nonatomic, readonly) AVPlayerLayer *playerLayer; - - -/** - * When shouldAutoplay is set to true, a video node will play when it has both loaded and entered the "visible" interfaceState. - * If it leaves the visible interfaceState it will pause but will resume once it has returned. - */ -@property BOOL shouldAutoplay; -@property BOOL shouldAutorepeat; - -@property BOOL muted; -@property BOOL shouldAggressivelyRecoverFromStall; - -@property (readonly) ASVideoNodePlayerState playerState; -//! Defaults to 1000 -@property int32_t periodicTimeObserverTimescale; - -//! Defaults to AVLayerVideoGravityResizeAspect -@property (null_resettable, copy) NSString *gravity; - -@property (nullable, weak) id delegate; - -@end - -@protocol ASVideoNodeDelegate -@optional -/** - * @abstract Delegate method invoked when the node's video has played to its end time. - * @param videoNode The video node has played to its end time. - */ -- (void)videoDidPlayToEnd:(ASVideoNode *)videoNode; -/** - * @abstract Delegate method invoked the node is tapped. - * @param videoNode The video node that was tapped. - * @discussion The video's play state is toggled if this method is not implemented. - */ -- (void)didTapVideoNode:(ASVideoNode *)videoNode; -/** - * @abstract Delegate method invoked when player changes state. - * @param videoNode The video node. - * @param state player state before this change. - * @param toState player new state. - * @discussion This method is called after each state change - */ -- (void)videoNode:(ASVideoNode *)videoNode willChangePlayerState:(ASVideoNodePlayerState)state toState:(ASVideoNodePlayerState)toState; -/** - * @abstract Ssks delegate if state change is allowed - * ASVideoNodePlayerStatePlaying or ASVideoNodePlayerStatePaused. - * asks delegate if state change is allowed. - * @param videoNode The video node. - * @param state player state that is going to be set. - * @discussion Delegate method invoked when player changes it's state to - * ASVideoNodePlayerStatePlaying or ASVideoNodePlayerStatePaused - * and asks delegate if state change is valid - */ -- (BOOL)videoNode:(ASVideoNode*)videoNode shouldChangePlayerStateTo:(ASVideoNodePlayerState)state; -/** - * @abstract Delegate method invoked when player playback time is updated. - * @param videoNode The video node. - * @param timeInterval current playback time in seconds. - */ -- (void)videoNode:(ASVideoNode *)videoNode didPlayToTimeInterval:(NSTimeInterval)timeInterval; -/** - * @abstract Delegate method invoked when the video player stalls. - * @param videoNode The video node that has experienced the stall - * @param timeInterval Current playback time when the stall happens - */ -- (void)videoNode:(ASVideoNode *)videoNode didStallAtTimeInterval:(NSTimeInterval)timeInterval; -/** - * @abstract Delegate method invoked when the video player starts the inital asset loading - * @param videoNode The videoNode - */ -- (void)videoNodeDidStartInitialLoading:(ASVideoNode *)videoNode; -/** - * @abstract Delegate method invoked when the video is done loading the asset and can start the playback - * @param videoNode The videoNode - */ -- (void)videoNodeDidFinishInitialLoading:(ASVideoNode *)videoNode; -/** - * @abstract Delegate method invoked when the AVPlayerItem for the asset has been set up and can be accessed throught currentItem. - * @param videoNode The videoNode. - * @param currentItem The AVPlayerItem that was constructed from the asset. - */ -- (void)videoNode:(ASVideoNode *)videoNode didSetCurrentItem:(AVPlayerItem *)currentItem; -/** - * @abstract Delegate method invoked when the video node has recovered from the stall - * @param videoNode The videoNode - */ -- (void)videoNodeDidRecoverFromStall:(ASVideoNode *)videoNode; -/** - * @abstract Delegate method invoked when an error occurs while trying to load an asset - * @param videoNode The videoNode. - * @param key The key of value that failed to load. - * @param asset The asset. - * @param error The error that occurs. - */ -- (void)videoNode:(ASVideoNode *)videoNode didFailToLoadValueForKey:(NSString *)key asset:(AVAsset *)asset error:(NSError *)error; - -@end - -@interface ASVideoNode (Unavailable) - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END -#endif - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASVideoNode.mm b/submodules/AsyncDisplayKit/Source/ASVideoNode.mm deleted file mode 100644 index fefa5bae2f..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASVideoNode.mm +++ /dev/null @@ -1,864 +0,0 @@ -// -// ASVideoNode.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 - -#if AS_USE_VIDEO - -#import -#import -#import -#import -#import -#import "Private/ASInternalHelpers.h" -#import -#import - -static BOOL ASAssetIsEqual(AVAsset *asset1, AVAsset *asset2) { - return ASObjectIsEqual(asset1, asset2) - || ([asset1 isKindOfClass:[AVURLAsset class]] - && [asset2 isKindOfClass:[AVURLAsset class]] - && ASObjectIsEqual(((AVURLAsset *)asset1).URL, ((AVURLAsset *)asset2).URL)); -} - -static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { - if ([videoGravity isEqualToString:AVLayerVideoGravityResizeAspectFill]) { - return UIViewContentModeScaleAspectFill; - } else if ([videoGravity isEqualToString:AVLayerVideoGravityResize]) { - return UIViewContentModeScaleToFill; - } else { - return UIViewContentModeScaleAspectFit; - } -} - -static void *ASVideoNodeContext = &ASVideoNodeContext; -static NSString * const kPlaybackLikelyToKeepUpKey = @"playbackLikelyToKeepUp"; -static NSString * const kplaybackBufferEmpty = @"playbackBufferEmpty"; -static NSString * const kStatus = @"status"; -static NSString * const kRate = @"rate"; - -@interface ASVideoNode () -{ - struct { - unsigned int delegateVideNodeShouldChangePlayerStateTo:1; - unsigned int delegateVideoDidPlayToEnd:1; - unsigned int delegateDidTapVideoNode:1; - unsigned int delegateVideoNodeWillChangePlayerStateToState:1; - unsigned int delegateVideoNodeDidPlayToTimeInterval:1; - unsigned int delegateVideoNodeDidStartInitialLoading:1; - unsigned int delegateVideoNodeDidFinishInitialLoading:1; - unsigned int delegateVideoNodeDidSetCurrentItem:1; - unsigned int delegateVideoNodeDidStallAtTimeInterval:1; - unsigned int delegateVideoNodeDidRecoverFromStall:1; - unsigned int delegateVideoNodeDidFailToLoadValueForKey:1; - } _delegateFlags; - - BOOL _shouldBePlaying; - - BOOL _shouldAutorepeat; - BOOL _shouldAutoplay; - BOOL _shouldAggressivelyRecoverFromStall; - BOOL _muted; - - ASVideoNodePlayerState _playerState; - - AVAsset *_asset; - NSURL *_assetURL; - AVVideoComposition *_videoComposition; - AVAudioMix *_audioMix; - - AVPlayerItem *_currentPlayerItem; - AVPlayer *_player; - - id _timeObserver; - int32_t _periodicTimeObserverTimescale; - CMTime _timeObserverInterval; - - CMTime _lastPlaybackTime; - - ASDisplayNode *_playerNode; - NSString *_gravity; -} - -@end - -@implementation ASVideoNode - -@dynamic delegate; - -// TODO: Support preview images with HTTP Live Streaming videos. - -#pragma mark - Construction and Layout - -- (instancetype)initWithCache:(id)cache downloader:(id)downloader -{ - if (!(self = [super initWithCache:cache downloader:downloader])) { - return nil; - } - - self.gravity = AVLayerVideoGravityResizeAspect; - _periodicTimeObserverTimescale = 10000; - [self addTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; - _lastPlaybackTime = kCMTimeZero; - - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; - - return self; -} - -- (ASDisplayNode *)constructPlayerNode -{ - ASVideoNode * __weak weakSelf = self; - - return [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{ - AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init]; - playerLayer.player = weakSelf.player; - playerLayer.videoGravity = weakSelf.gravity; - return playerLayer; - }]; -} - -- (AVPlayerItem *)constructPlayerItem -{ - ASDisplayNodeAssertMainThread(); - ASLockScopeSelf(); - - AVPlayerItem *playerItem = nil; - if (_assetURL != nil) { - playerItem = [[AVPlayerItem alloc] initWithURL:_assetURL]; - _asset = [playerItem asset]; - } else { - playerItem = [[AVPlayerItem alloc] initWithAsset:_asset]; - } - - playerItem.videoComposition = _videoComposition; - playerItem.audioMix = _audioMix; - return playerItem; -} - -- (void)prepareToPlayAsset:(AVAsset *)asset withKeys:(NSArray *)requestedKeys -{ - ASDisplayNodeAssertMainThread(); - - for (NSString *key in requestedKeys) { - NSError *error = nil; - AVKeyValueStatus keyStatus = [asset statusOfValueForKey:key error:&error]; - if (keyStatus == AVKeyValueStatusFailed) { - NSLog(@"Asset loading failed with error: %@", error); - if (_delegateFlags.delegateVideoNodeDidFailToLoadValueForKey) { - [self.delegate videoNode:self didFailToLoadValueForKey:key asset:asset error:error]; - } - } - } - - if ([asset isPlayable] == NO) { - NSLog(@"Asset is not playable."); - return; - } - - AVPlayerItem *playerItem = [self constructPlayerItem]; - [self setCurrentItem:playerItem]; - - if (_player != nil) { - [_player replaceCurrentItemWithPlayerItem:playerItem]; - } else { - self.player = [AVPlayer playerWithPlayerItem:playerItem]; - } - - if (_delegateFlags.delegateVideoNodeDidSetCurrentItem) { - [self.delegate videoNode:self didSetCurrentItem:playerItem]; - } - - if (self.image == nil && self.URL == nil) { - [self generatePlaceholderImage]; - } -} - -- (void)addPlayerItemObservers:(AVPlayerItem *)playerItem -{ - if (playerItem == nil) { - return; - } - - [playerItem addObserver:self forKeyPath:kStatus options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:ASVideoNodeContext]; - [playerItem addObserver:self forKeyPath:kPlaybackLikelyToKeepUpKey options:NSKeyValueObservingOptionNew context:ASVideoNodeContext]; - [playerItem addObserver:self forKeyPath:kplaybackBufferEmpty options:NSKeyValueObservingOptionNew context:ASVideoNodeContext]; - - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter addObserver:self selector:@selector(didPlayToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; - [notificationCenter addObserver:self selector:@selector(videoNodeDidStall:) name:AVPlayerItemPlaybackStalledNotification object:playerItem]; - [notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem]; - [notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemNewErrorLogEntryNotification object:playerItem]; -} - -- (void)removePlayerItemObservers:(AVPlayerItem *)playerItem -{ - @try { - [playerItem removeObserver:self forKeyPath:kStatus context:ASVideoNodeContext]; - [playerItem removeObserver:self forKeyPath:kPlaybackLikelyToKeepUpKey context:ASVideoNodeContext]; - [playerItem removeObserver:self forKeyPath:kplaybackBufferEmpty context:ASVideoNodeContext]; - } - @catch (NSException * __unused exception) { - NSLog(@"Unnecessary KVO removal"); - } - - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; - [notificationCenter removeObserver:self name: AVPlayerItemPlaybackStalledNotification object:playerItem]; - [notificationCenter removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem]; - [notificationCenter removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:playerItem]; -} - -- (void)addPlayerObservers:(AVPlayer *)player -{ - if (player == nil) { - return; - } - - [player addObserver:self forKeyPath:kRate options:NSKeyValueObservingOptionNew context:ASVideoNodeContext]; - - __weak __typeof(self) weakSelf = self; - _timeObserverInterval = CMTimeMake(1, _periodicTimeObserverTimescale); - _timeObserver = [player addPeriodicTimeObserverForInterval:_timeObserverInterval queue:NULL usingBlock:^(CMTime time){ - [weakSelf periodicTimeObserver:time]; - }]; -} - -- (void) removePlayerObservers:(AVPlayer *)player -{ - if (_timeObserver != nil) { - [player removeTimeObserver:_timeObserver]; - _timeObserver = nil; - } - - @try { - [player removeObserver:self forKeyPath:kRate context:ASVideoNodeContext]; - } - @catch (NSException * __unused exception) { - NSLog(@"Unnecessary KVO removal"); - } -} - -- (void)layout -{ - [super layout]; - // The _playerNode wraps AVPlayerLayer, and therefore should extend across the entire bounds. - _playerNode.frame = self.bounds; -} - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - ASDisplayNode *playerNode = ASLockedSelf(_playerNode); - - CGSize calculatedSize = constrainedSize; - - // Prevent crashes through if infinite width or height - if (isinf(calculatedSize.width) || isinf(calculatedSize.height)) { - ASDisplayNodeAssert(NO, @"Infinite width or height in ASVideoNode"); - calculatedSize = CGSizeZero; - } - - if (playerNode != nil) { - playerNode.style.preferredSize = calculatedSize; - [playerNode layoutThatFits:ASSizeRangeMake(CGSizeZero, calculatedSize)]; - } - - return calculatedSize; -} - -- (void)generatePlaceholderImage -{ - ASVideoNode * __weak weakSelf = self; - AVAsset *asset = self.asset; - - [self imageAtTime:kCMTimeZero completionHandler:^(UIImage *image) { - ASPerformBlockOnMainThread(^{ - // Ensure the asset hasn't changed since the image request was made - if (ASAssetIsEqual(weakSelf.asset, asset)) { - [weakSelf setVideoPlaceholderImage:image]; - } - }); - }]; -} - -- (void)imageAtTime:(CMTime)imageTime completionHandler:(void(^)(UIImage *image))completionHandler -{ - ASPerformBlockOnBackgroundThread(^{ - AVAsset *asset = self.asset; - - // Skip the asset image generation if we don't have any tracks available that are capable of supporting it - NSArray* visualAssetArray = [asset tracksWithMediaCharacteristic:AVMediaCharacteristicVisual]; - if (visualAssetArray.count == 0) { - completionHandler(nil); - return; - } - - AVAssetImageGenerator *previewImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset]; - previewImageGenerator.appliesPreferredTrackTransform = YES; - previewImageGenerator.videoComposition = _videoComposition; - - [previewImageGenerator generateCGImagesAsynchronouslyForTimes:@[[NSValue valueWithCMTime:imageTime]] - completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) { - if (error != nil && result != AVAssetImageGeneratorCancelled) { - NSLog(@"Asset preview image generation failed with error: %@", error); - } - completionHandler(image ? [UIImage imageWithCGImage:image] : nil); - }]; - }); -} - -- (void)setVideoPlaceholderImage:(UIImage *)image -{ - NSString *gravity = self.gravity; - - if (image != nil) { - self.contentMode = ASContentModeFromVideoGravity(gravity); - } - self.image = image; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context -{ - AS::UniqueLock l(__instanceLock__); - - if (object == _currentPlayerItem) { - if ([keyPath isEqualToString:kStatus]) { - if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) { - if (self.playerState != ASVideoNodePlayerStatePlaying) { - self.playerState = ASVideoNodePlayerStateReadyToPlay; - if (_shouldBePlaying && ASInterfaceStateIncludesVisible(self.interfaceState)) { - l.unlock(); - [self play]; - l.lock(); - } - } - // If we don't yet have a placeholder image update it now that we should have data available for it - if (self.image == nil && self.URL == nil) { - [self generatePlaceholderImage]; - } - } - } else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUpKey]) { - BOOL likelyToKeepUp = [change[NSKeyValueChangeNewKey] boolValue]; - if (likelyToKeepUp && self.playerState == ASVideoNodePlayerStatePlaying) { - return; - } - if (!likelyToKeepUp) { - self.playerState = ASVideoNodePlayerStateLoading; - } else if (self.playerState != ASVideoNodePlayerStateFinished) { - self.playerState = ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying; - } - if (_shouldBePlaying && (_shouldAggressivelyRecoverFromStall || likelyToKeepUp) && ASInterfaceStateIncludesVisible(self.interfaceState)) { - if (self.playerState == ASVideoNodePlayerStateLoading && _delegateFlags.delegateVideoNodeDidRecoverFromStall) { - [self.delegate videoNodeDidRecoverFromStall:self]; - } - - l.unlock(); - [self play]; // autoresume after buffer catches up - // NOTE: Early return without re-locking. - return; - } - } else if ([keyPath isEqualToString:kplaybackBufferEmpty]) { - if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == YES && ASInterfaceStateIncludesVisible(self.interfaceState)) { - self.playerState = ASVideoNodePlayerStateLoading; - } - } - } else if (object == _player) { - if ([keyPath isEqualToString:kRate]) { - if ([change[NSKeyValueChangeNewKey] floatValue] == 0.0) { - if (self.playerState == ASVideoNodePlayerStatePlaying) { - self.playerState = ASVideoNodePlayerStatePaused; - } - } else { - self.playerState = ASVideoNodePlayerStatePlaying; - } - } - } - - // NOTE: Early return above. -} - -- (void)tapped -{ - if (_delegateFlags.delegateDidTapVideoNode) { - [self.delegate didTapVideoNode:self]; - - } else { - if (_shouldBePlaying) { - [self pause]; - } else { - [self play]; - } - } -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - - ASLockScopeSelf(); - AVAsset *asset = self.asset; - // Return immediately if the asset is nil; - if (asset == nil || self.playerState != ASVideoNodePlayerStateUnknown) { - return; - } - - self.playerState = ASVideoNodePlayerStateLoading; - if (_delegateFlags.delegateVideoNodeDidStartInitialLoading) { - [self.delegate videoNodeDidStartInitialLoading:self]; - } - - NSArray *requestedKeys = @[@"playable"]; - [asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:^{ - ASPerformBlockOnMainThread(^{ - if (_delegateFlags.delegateVideoNodeDidFinishInitialLoading) { - [self.delegate videoNodeDidFinishInitialLoading:self]; - } - [self prepareToPlayAsset:asset withKeys:requestedKeys]; - }); - }]; -} - -- (void)periodicTimeObserver:(CMTime)time -{ - NSTimeInterval timeInSeconds = CMTimeGetSeconds(time); - if (timeInSeconds <= 0) { - return; - } - - if (_delegateFlags.delegateVideoNodeDidPlayToTimeInterval) { - [self.delegate videoNode:self didPlayToTimeInterval:timeInSeconds]; - - } -} - -- (void)didExitPreloadState -{ - [super didExitPreloadState]; - - { - ASLockScopeSelf(); - - self.player = nil; - self.currentItem = nil; - self.playerState = ASVideoNodePlayerStateUnknown; - } -} - -- (void)didEnterVisibleState -{ - [super didEnterVisibleState]; - - BOOL shouldPlay = NO; - { - ASLockScopeSelf(); - if (_shouldBePlaying || _shouldAutoplay) { - if (_player != nil && CMTIME_IS_VALID(_lastPlaybackTime)) { - [_player seekToTime:_lastPlaybackTime]; - } - shouldPlay = YES; - } - } - - if (shouldPlay) { - [self play]; - } -} - -- (void)didExitVisibleState -{ - [super didExitVisibleState]; - - ASLockScopeSelf(); - - if (_shouldBePlaying) { - [self pause]; - if (_player != nil && CMTIME_IS_VALID(_player.currentTime)) { - _lastPlaybackTime = _player.currentTime; - } - _shouldBePlaying = YES; - } -} - -#pragma mark - Video Properties - -- (void)setPlayerState:(ASVideoNodePlayerState)playerState -{ - ASLockScopeSelf(); - - ASVideoNodePlayerState oldState = _playerState; - - if (oldState == playerState) { - return; - } - - if (_delegateFlags.delegateVideoNodeWillChangePlayerStateToState) { - [self.delegate videoNode:self willChangePlayerState:oldState toState:playerState]; - } - - _playerState = playerState; -} - -- (void)setAssetURL:(NSURL *)assetURL -{ - ASDisplayNodeAssertMainThread(); - - if (ASObjectIsEqual(assetURL, self.assetURL) == NO) { - [self setAndFetchAsset:[AVURLAsset assetWithURL:assetURL] url:assetURL]; - } -} - -- (NSURL *)assetURL -{ - ASLockScopeSelf(); - - if (_assetURL != nil) { - return _assetURL; - } else if ([_asset isKindOfClass:AVURLAsset.class]) { - return ((AVURLAsset *)_asset).URL; - } - - return nil; -} - -- (void)setAsset:(AVAsset *)asset -{ - ASDisplayNodeAssertMainThread(); - - if (ASAssetIsEqual(asset, self.asset) == NO) { - [self setAndFetchAsset:asset url:nil]; - } -} - -- (AVAsset *)asset -{ - ASLockScopeSelf(); - return _asset; -} - -- (void)setAndFetchAsset:(AVAsset *)asset url:(NSURL *)assetURL -{ - ASDisplayNodeAssertMainThread(); - - [self didExitPreloadState]; - - { - ASLockScopeSelf(); - self.videoPlaceholderImage = nil; - _asset = asset; - _assetURL = assetURL; - } - - [self setNeedsPreload]; -} - -- (void)setVideoComposition:(AVVideoComposition *)videoComposition -{ - ASLockScopeSelf(); - - _videoComposition = videoComposition; - _currentPlayerItem.videoComposition = videoComposition; -} - -- (AVVideoComposition *)videoComposition -{ - ASLockScopeSelf(); - return _videoComposition; -} - -- (void)setAudioMix:(AVAudioMix *)audioMix -{ - ASLockScopeSelf(); - - _audioMix = audioMix; - _currentPlayerItem.audioMix = audioMix; -} - -- (AVAudioMix *)audioMix -{ - ASLockScopeSelf(); - return _audioMix; -} - -- (AVPlayer *)player -{ - ASLockScopeSelf(); - return _player; -} - -- (AVPlayerLayer *)playerLayer -{ - ASLockScopeSelf(); - return (AVPlayerLayer *)_playerNode.layer; -} - -- (void)setDelegate:(id)delegate -{ - [super setDelegate:delegate]; - - if (delegate == nil) { - memset(&_delegateFlags, 0, sizeof(_delegateFlags)); - } else { - _delegateFlags.delegateVideNodeShouldChangePlayerStateTo = [delegate respondsToSelector:@selector(videoNode:shouldChangePlayerStateTo:)]; - _delegateFlags.delegateVideoDidPlayToEnd = [delegate respondsToSelector:@selector(videoDidPlayToEnd:)]; - _delegateFlags.delegateDidTapVideoNode = [delegate respondsToSelector:@selector(didTapVideoNode:)]; - _delegateFlags.delegateVideoNodeWillChangePlayerStateToState = [delegate respondsToSelector:@selector(videoNode:willChangePlayerState:toState:)]; - _delegateFlags.delegateVideoNodeDidPlayToTimeInterval = [delegate respondsToSelector:@selector(videoNode:didPlayToTimeInterval:)]; - _delegateFlags.delegateVideoNodeDidStartInitialLoading = [delegate respondsToSelector:@selector(videoNodeDidStartInitialLoading:)]; - _delegateFlags.delegateVideoNodeDidFinishInitialLoading = [delegate respondsToSelector:@selector(videoNodeDidFinishInitialLoading:)]; - _delegateFlags.delegateVideoNodeDidSetCurrentItem = [delegate respondsToSelector:@selector(videoNode:didSetCurrentItem:)]; - _delegateFlags.delegateVideoNodeDidStallAtTimeInterval = [delegate respondsToSelector:@selector(videoNode:didStallAtTimeInterval:)]; - _delegateFlags.delegateVideoNodeDidRecoverFromStall = [delegate respondsToSelector:@selector(videoNodeDidRecoverFromStall:)]; - _delegateFlags.delegateVideoNodeDidFailToLoadValueForKey = [delegate respondsToSelector:@selector(videoNode:didFailToLoadValueForKey:asset:error:)]; - } -} - -- (void)setGravity:(NSString *)gravity -{ - ASLockScopeSelf(); - if (!gravity) { - gravity = AVLayerVideoGravityResizeAspect; - } - - if (!ASCompareAssignObjects(_gravity, gravity)) { - return; - } - - if (_playerNode.isNodeLoaded) { - ((AVPlayerLayer *)_playerNode.layer).videoGravity = gravity; - } - self.contentMode = ASContentModeFromVideoGravity(gravity); - _gravity = gravity; -} - -- (NSString *)gravity -{ - ASLockScopeSelf(); - return _gravity; -} - -- (BOOL)muted -{ - ASLockScopeSelf(); - return _muted; -} - -- (void)setMuted:(BOOL)muted -{ - ASLockScopeSelf(); - - _player.muted = muted; - _muted = muted; -} - -#pragma mark - Video Playback - -- (void)play -{ - ASLockScopeSelf(); - - if (![self isStateChangeValid:ASVideoNodePlayerStatePlaying]) { - return; - } - - if (_player == nil) { - ASUnlockScope(self); - [self setNeedsPreload]; - } - - if (_playerNode == nil) { - _playerNode = [self constructPlayerNode]; - - { - ASUnlockScope(self); - [self addSubnode:_playerNode]; - } - - [self setNeedsLayout]; - } - - - [_player play]; - _shouldBePlaying = YES; -} - -- (BOOL)ready -{ - return _currentPlayerItem.status == AVPlayerItemStatusReadyToPlay; -} - -- (void)pause -{ - ASLockScopeSelf(); - if (![self isStateChangeValid:ASVideoNodePlayerStatePaused]) { - return; - } - [_player pause]; - _shouldBePlaying = NO; -} - -- (BOOL)isPlaying -{ - ASLockScopeSelf(); - - return (_player.rate > 0 && !_player.error); -} - -- (BOOL)isStateChangeValid:(ASVideoNodePlayerState)state -{ - if (_delegateFlags.delegateVideNodeShouldChangePlayerStateTo) { - if (![self.delegate videoNode:self shouldChangePlayerStateTo:state]) { - return NO; - } - } - return YES; -} - -- (void)resetToPlaceholder -{ - ASLockScopeSelf(); - - if (_playerNode != nil) { - [_playerNode removeFromSupernode]; - _playerNode = nil; - } - - [_player seekToTime:kCMTimeZero]; - [self pause]; -} - - -#pragma mark - Playback observers - -- (void)applicationDidBecomeActive:(NSNotification *)notification -{ - if (self.shouldBePlaying && self.isVisible) { - [self play]; - } -} - -- (void)didPlayToEnd:(NSNotification *)notification -{ - self.playerState = ASVideoNodePlayerStateFinished; - if (_delegateFlags.delegateVideoDidPlayToEnd) { - [self.delegate videoDidPlayToEnd:self]; - } - - if (_shouldAutorepeat) { - [_player seekToTime:kCMTimeZero]; - [self play]; - } else { - [self pause]; - } -} - -- (void)videoNodeDidStall:(NSNotification *)notification -{ - self.playerState = ASVideoNodePlayerStateLoading; - if (_delegateFlags.delegateVideoNodeDidStallAtTimeInterval) { - [self.delegate videoNode:self didStallAtTimeInterval:CMTimeGetSeconds(_player.currentItem.currentTime)]; - } -} - -- (void)errorWhilePlaying:(NSNotification *)notification -{ - if ([notification.name isEqualToString:AVPlayerItemFailedToPlayToEndTimeNotification]) { - NSLog(@"Failed to play video"); - } else if ([notification.name isEqualToString:AVPlayerItemNewErrorLogEntryNotification]) { - AVPlayerItem *item = (AVPlayerItem *)notification.object; - AVPlayerItemErrorLogEvent *logEvent = item.errorLog.events.lastObject; - NSLog(@"AVPlayerItem error log entry added for video with error %@ status %@", item.error, - (item.status == AVPlayerItemStatusFailed ? @"FAILED" : [NSString stringWithFormat:@"%ld", (long)item.status])); - NSLog(@"Item is %@", item); - - if (logEvent) { - NSLog(@"Log code %ld domain %@ comment %@", (long)logEvent.errorStatusCode, logEvent.errorDomain, logEvent.errorComment); - } - } -} - -#pragma mark - Internal Properties - -- (AVPlayerItem *)currentItem -{ - ASLockScopeSelf(); - return _currentPlayerItem; -} - -- (void)setCurrentItem:(AVPlayerItem *)currentItem -{ - ASLockScopeSelf(); - - [self removePlayerItemObservers:_currentPlayerItem]; - - _currentPlayerItem = currentItem; - - if (currentItem != nil) { - [self addPlayerItemObservers:currentItem]; - } -} - -- (ASDisplayNode *)playerNode -{ - ASLockScopeSelf(); - return _playerNode; -} - -- (void)setPlayerNode:(ASDisplayNode *)playerNode -{ - { - ASLockScopeSelf(); - _playerNode = playerNode; - } - - [self setNeedsLayout]; -} - -- (void)setPlayer:(AVPlayer *)player -{ - ASLockScopeSelf(); - - [self removePlayerObservers:_player]; - - _player = player; - player.muted = _muted; - ((AVPlayerLayer *)_playerNode.layer).player = player; - - if (player != nil) { - [self addPlayerObservers:player]; - } -} - -- (BOOL)shouldBePlaying -{ - ASLockScopeSelf(); - return _shouldBePlaying; -} - -- (void)setShouldBePlaying:(BOOL)shouldBePlaying -{ - ASLockScopeSelf(); - _shouldBePlaying = shouldBePlaying; -} - -#pragma mark - Lifecycle - -- (void)dealloc -{ - [self removePlayerItemObservers:_currentPlayerItem]; - [self removePlayerObservers:_player]; - - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; -} - -@end -#endif -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASVideoPlayerNode.h b/submodules/AsyncDisplayKit/Source/ASVideoPlayerNode.h deleted file mode 100644 index 879628051a..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASVideoPlayerNode.h +++ /dev/null @@ -1,228 +0,0 @@ -// -// ASVideoPlayerNode.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 - -#if AS_USE_VIDEO - -#if TARGET_OS_IOS -#import -#import -#import -#import - -@class AVAsset; -@class ASButtonNode; -@protocol ASVideoPlayerNodeDelegate; - -typedef NS_ENUM(NSInteger, ASVideoPlayerNodeControlType) { - ASVideoPlayerNodeControlTypePlaybackButton, - ASVideoPlayerNodeControlTypeElapsedText, - ASVideoPlayerNodeControlTypeDurationText, - ASVideoPlayerNodeControlTypeScrubber, - ASVideoPlayerNodeControlTypeFullScreenButton, - ASVideoPlayerNodeControlTypeFlexGrowSpacer, -}; - -NS_ASSUME_NONNULL_BEGIN - -@interface ASVideoPlayerNode : ASDisplayNode - -@property (nullable, nonatomic, weak) id delegate; - -@property (nonatomic, readonly) CMTime duration; - -@property (nonatomic) BOOL controlsDisabled; - -#pragma mark - ASVideoNode property proxy -/** - * When shouldAutoplay is set to true, a video node will play when it has both loaded and entered the "visible" interfaceState. - * If it leaves the visible interfaceState it will pause but will resume once it has returned. - */ -@property (nonatomic) BOOL shouldAutoPlay; -@property (nonatomic) BOOL shouldAutoRepeat; -@property (nonatomic) BOOL muted; -@property (nonatomic, readonly) ASVideoNodePlayerState playerState; -@property (nonatomic) BOOL shouldAggressivelyRecoverFromStall; -@property (nullable, nonatomic) NSURL *placeholderImageURL; - -@property (nullable, nonatomic) AVAsset *asset; -/** - ** @abstract The URL with which the asset was initialized. - ** @discussion Setting the URL will override the current asset with a newly created AVURLAsset created from the given URL, and AVAsset *asset will point to that newly created AVURLAsset. Please don't set both assetURL and asset. - ** @return Current URL the asset was initialized or nil if no URL was given. - **/ -@property (nullable, nonatomic) NSURL *assetURL; - -/// You should never set any value on the backing video node. Use exclusivively the video player node to set properties -@property (nonatomic, readonly) ASVideoNode *videoNode; - -//! Defaults to 100 -@property (nonatomic) int32_t periodicTimeObserverTimescale; -//! Defaults to AVLayerVideoGravityResizeAspect -@property (nonatomic, copy) NSString *gravity; - -#pragma mark - Lifecycle -- (instancetype)initWithURL:(NSURL *)URL; -- (instancetype)initWithAsset:(AVAsset *)asset; -- (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix; - -#pragma mark - Public API -- (void)seekToTime:(CGFloat)percentComplete; -- (void)play; -- (void)pause; -- (BOOL)isPlaying; -- (void)resetToPlaceholder; - -@end - -#pragma mark - ASVideoPlayerNodeDelegate - -@protocol ASVideoPlayerNodeDelegate -@optional -/** - * @abstract Delegate method invoked before creating controlbar controls - * @param videoPlayer The sender - */ -- (NSArray *)videoPlayerNodeNeededDefaultControls:(ASVideoPlayerNode*)videoPlayer; - -/** - * @abstract Delegate method invoked before creating default controls, asks delegate for custom controls dictionary. - * This dictionary must constain only ASDisplayNode subclass objects. - * @param videoPlayer The sender - * @discussion - This method is invoked only when developer implements videoPlayerNodeLayoutSpec:forControls:forMaximumSize: - * and gives ability to add custom constrols to ASVideoPlayerNode, for example mute button. - */ -- (NSDictionary *)videoPlayerNodeCustomControls:(ASVideoPlayerNode*)videoPlayer; - -/** - * @abstract Delegate method invoked in layoutSpecThatFits: - * @param videoPlayer The sender - * @param controls - Dictionary of controls which are used in videoPlayer; Dictionary keys are ASVideoPlayerNodeControlType - * @param maxSize - Maximum size for ASVideoPlayerNode - * @discussion - Developer can layout whole ASVideoPlayerNode as he wants. ASVideoNode is locked and it can't be changed - */ -- (ASLayoutSpec *)videoPlayerNodeLayoutSpec:(ASVideoPlayerNode *)videoPlayer - forControls:(NSDictionary *)controls - forMaximumSize:(CGSize)maxSize; - -#pragma mark Text delegate methods -/** - * @abstract Delegate method invoked before creating ASVideoPlayerNodeControlTypeElapsedText and ASVideoPlayerNodeControlTypeDurationText - * @param videoPlayer The sender - * @param timeLabelType The of the time label - */ -- (NSDictionary *)videoPlayerNodeTimeLabelAttributes:(ASVideoPlayerNode *)videoPlayer timeLabelType:(ASVideoPlayerNodeControlType)timeLabelType; -- (NSString *)videoPlayerNode:(ASVideoPlayerNode *)videoPlayerNode - timeStringForTimeLabelType:(ASVideoPlayerNodeControlType)timeLabelType - forTime:(CMTime)time; - -#pragma mark Scrubber delegate methods -- (UIColor *)videoPlayerNodeScrubberMaximumTrackTint:(ASVideoPlayerNode *)videoPlayer; -- (UIColor *)videoPlayerNodeScrubberMinimumTrackTint:(ASVideoPlayerNode *)videoPlayer; -- (UIColor *)videoPlayerNodeScrubberThumbTint:(ASVideoPlayerNode *)videoPlayer; -- (UIImage *)videoPlayerNodeScrubberThumbImage:(ASVideoPlayerNode *)videoPlayer; - -#pragma mark - Spinner delegate methods -- (UIColor *)videoPlayerNodeSpinnerTint:(ASVideoPlayerNode *)videoPlayer; -- (UIActivityIndicatorViewStyle)videoPlayerNodeSpinnerStyle:(ASVideoPlayerNode *)videoPlayer; - -#pragma mark - Playback button delegate methods -- (UIColor *)videoPlayerNodePlaybackButtonTint:(ASVideoPlayerNode *)videoPlayer; - -#pragma mark - Fullscreen button delegate methods - -- (UIImage *)videoPlayerNodeFullScreenButtonImage:(ASVideoPlayerNode *)videoPlayer; - - -#pragma mark ASVideoNodeDelegate proxy methods -/** - * @abstract Delegate method invoked when ASVideoPlayerNode is taped. - * @param videoPlayer The ASVideoPlayerNode that was tapped. - */ -- (void)didTapVideoPlayerNode:(ASVideoPlayerNode *)videoPlayer; - -/** - * @abstract Delegate method invoked when fullcreen button is taped. - * @param buttonNode The fullscreen button node that was tapped. - */ -- (void)didTapFullScreenButtonNode:(ASButtonNode *)buttonNode; - -/** - * @abstract Delegate method invoked when ASVideoNode playback time is updated. - * @param videoPlayer The video player node - * @param time current playback time. - */ -- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer didPlayToTime:(CMTime)time; - -/** - * @abstract Delegate method invoked when ASVideoNode changes state. - * @param videoPlayer The ASVideoPlayerNode whose ASVideoNode is changing state. - * @param state ASVideoNode state before this change. - * @param toState ASVideoNode new state. - * @discussion This method is called after each state change - */ -- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer willChangeVideoNodeState:(ASVideoNodePlayerState)state toVideoNodeState:(ASVideoNodePlayerState)toState; - -/** - * @abstract Delegate method is invoked when ASVideoNode decides to change state. - * @param videoPlayer The ASVideoPlayerNode whose ASVideoNode is changing state. - * @param state ASVideoNode that is going to be set. - * @discussion Delegate method invoked when player changes it's state to - * ASVideoNodePlayerStatePlaying or ASVideoNodePlayerStatePaused - * and asks delegate if state change is valid - */ -- (BOOL)videoPlayerNode:(ASVideoPlayerNode*)videoPlayer shouldChangeVideoNodeStateTo:(ASVideoNodePlayerState)state; - -/** - * @abstract Delegate method invoked when the ASVideoNode has played to its end time. - * @param videoPlayer The video node has played to its end time. - */ -- (void)videoPlayerNodeDidPlayToEnd:(ASVideoPlayerNode *)videoPlayer; - -/** - * @abstract Delegate method invoked when the ASVideoNode has constructed its AVPlayerItem for the asset. - * @param videoPlayer The video player node. - * @param currentItem The AVPlayerItem that was constructed from the asset. - */ -- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer didSetCurrentItem:(AVPlayerItem *)currentItem; - -/** - * @abstract Delegate method invoked when the ASVideoNode stalls. - * @param videoPlayer The video player node that has experienced the stall - * @param timeInterval Current playback time when the stall happens - */ -- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer didStallAtTimeInterval:(NSTimeInterval)timeInterval; - -/** - * @abstract Delegate method invoked when the ASVideoNode starts the inital asset loading - * @param videoPlayer The videoPlayer - */ -- (void)videoPlayerNodeDidStartInitialLoading:(ASVideoPlayerNode *)videoPlayer; - -/** - * @abstract Delegate method invoked when the ASVideoNode is done loading the asset and can start the playback - * @param videoPlayer The videoPlayer - */ -- (void)videoPlayerNodeDidFinishInitialLoading:(ASVideoPlayerNode *)videoPlayer; - -/** - * @abstract Delegate method invoked when the ASVideoNode has recovered from the stall - * @param videoPlayer The videoplayer - */ -- (void)videoPlayerNodeDidRecoverFromStall:(ASVideoPlayerNode *)videoPlayer; - - -@end -NS_ASSUME_NONNULL_END -#endif - -#endif -#endif // TARGET_OS_IOS diff --git a/submodules/AsyncDisplayKit/Source/ASVideoPlayerNode.mm b/submodules/AsyncDisplayKit/Source/ASVideoPlayerNode.mm deleted file mode 100644 index eaa8cd91a9..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASVideoPlayerNode.mm +++ /dev/null @@ -1,1024 +0,0 @@ -// -// ASVideoPlayerNode.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 - -#ifndef MINIMAL_ASDK - -#import - -#if AS_USE_VIDEO -#if TARGET_OS_IOS - -#import - -#import -#import -#import -#import -#import - -static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - -@interface ASVideoPlayerNode() -{ - __weak id _delegate; - - struct { - unsigned int delegateNeededDefaultControls:1; - unsigned int delegateCustomControls:1; - unsigned int delegateSpinnerTintColor:1; - unsigned int delegateSpinnerStyle:1; - unsigned int delegatePlaybackButtonTint:1; - unsigned int delegateFullScreenButtonImage:1; - unsigned int delegateScrubberMaximumTrackTintColor:1; - unsigned int delegateScrubberMinimumTrackTintColor:1; - unsigned int delegateScrubberThumbTintColor:1; - unsigned int delegateScrubberThumbImage:1; - unsigned int delegateTimeLabelAttributes:1; - unsigned int delegateTimeLabelAttributedString:1; - unsigned int delegateLayoutSpecForControls:1; - unsigned int delegateVideoNodeDidPlayToTime:1; - unsigned int delegateVideoNodeWillChangeState:1; - unsigned int delegateVideoNodeShouldChangeState:1; - unsigned int delegateVideoNodePlaybackDidFinish:1; - unsigned int delegateDidTapVideoPlayerNode:1; - unsigned int delegateDidTapFullScreenButtonNode:1; - unsigned int delegateVideoPlayerNodeDidSetCurrentItem:1; - unsigned int delegateVideoPlayerNodeDidStallAtTimeInterval:1; - unsigned int delegateVideoPlayerNodeDidStartInitialLoading:1; - unsigned int delegateVideoPlayerNodeDidFinishInitialLoading:1; - unsigned int delegateVideoPlayerNodeDidRecoverFromStall:1; - } _delegateFlags; - - // The asset passed in the initializer will be assigned as pending asset. As soon as the first - // preload state happened all further asset handling is made by using the asset of the backing - // video node - AVAsset *_pendingAsset; - - // The backing video node. Ideally this is the source of truth and the video player node should - // not handle anything related to asset management - ASVideoNode *_videoNode; - - NSArray *_neededDefaultControls; - - NSMutableDictionary *_cachedControls; - - ASDefaultPlaybackButton *_playbackButtonNode; - ASButtonNode *_fullScreenButtonNode; - ASTextNode *_elapsedTextNode; - ASTextNode *_durationTextNode; - ASDisplayNode *_scrubberNode; - ASStackLayoutSpec *_controlFlexGrowSpacerSpec; - ASDisplayNode *_spinnerNode; - - BOOL _isSeeking; - CMTime _duration; - - BOOL _controlsDisabled; - - BOOL _shouldAutoPlay; - BOOL _shouldAutoRepeat; - BOOL _muted; - int32_t _periodicTimeObserverTimescale; - NSString *_gravity; - - BOOL _shouldAggressivelyRecoverFromStall; - - UIColor *_defaultControlsColor; -} - -@end - -@implementation ASVideoPlayerNode - -@dynamic placeholderImageURL; - -#pragma mark - Lifecycle - -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - - [self _initControlsAndVideoNode]; - - return self; -} - -- (instancetype)initWithAsset:(AVAsset *)asset -{ - if (!(self = [self init])) { - return nil; - } - - _pendingAsset = asset; - - return self; -} - -- (instancetype)initWithURL:(NSURL *)URL -{ - return [self initWithAsset:[AVAsset assetWithURL:URL]]; -} - -- (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix -{ - if (!(self = [self initWithAsset:asset])) { - return nil; - } - - _videoNode.videoComposition = videoComposition; - _videoNode.audioMix = audioMix; - - return self; -} - -- (void)_initControlsAndVideoNode -{ - _defaultControlsColor = [UIColor whiteColor]; - _cachedControls = [[NSMutableDictionary alloc] init]; - - _videoNode = [[ASVideoNode alloc] init]; - _videoNode.delegate = self; - [self addSubnode:_videoNode]; -} - -#pragma mark - Setter / Getter - -- (void)setAssetURL:(NSURL *)assetURL -{ - ASDisplayNodeAssertMainThread(); - - self.asset = [AVAsset assetWithURL:assetURL]; -} - -- (NSURL *)assetURL -{ - NSURL *url = nil; - { - ASLockScopeSelf(); - if ([_pendingAsset isKindOfClass:AVURLAsset.class]) { - url = ((AVURLAsset *)_pendingAsset).URL; - } - } - - return url ?: _videoNode.assetURL; -} - -- (void)setAsset:(AVAsset *)asset -{ - ASDisplayNodeAssertMainThread(); - - [self lock]; - - // Clean out pending asset - _pendingAsset = nil; - - // Set asset based on interface state - if ((ASInterfaceStateIncludesPreload(self.interfaceState))) { - // Don't hold the lock while accessing the subnode - [self unlock]; - _videoNode.asset = asset; - return; - } - - _pendingAsset = asset; - [self unlock]; -} - -- (AVAsset *)asset -{ - return ASLockedSelf(_pendingAsset) ?: _videoNode.asset; -} - -#pragma mark - ASDisplayNode - -- (void)didLoad -{ - [super didLoad]; - - [self createControls]; -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - - AVAsset *pendingAsset = nil; - { - ASLockScopeSelf(); - pendingAsset = _pendingAsset; - _pendingAsset = nil; - } - - // If we enter preload state we apply the pending asset to load to the video node so it can start and fetch the asset - if (pendingAsset != nil && _videoNode.asset != pendingAsset) { - _videoNode.asset = pendingAsset; - } -} - -- (BOOL)supportsLayerBacking -{ - return NO; -} - -#pragma mark - UI - -- (void)createControls -{ - { - ASLockScopeSelf(); - - if (_controlsDisabled) { - return; - } - - if (_neededDefaultControls == nil) { - _neededDefaultControls = [self createDefaultControlElementArray]; - } - - if (_cachedControls == nil) { - _cachedControls = [[NSMutableDictionary alloc] init]; - } - - for (id object in _neededDefaultControls) { - ASVideoPlayerNodeControlType type = (ASVideoPlayerNodeControlType)[object integerValue]; - switch (type) { - case ASVideoPlayerNodeControlTypePlaybackButton: - [self _locked_createPlaybackButton]; - break; - case ASVideoPlayerNodeControlTypeElapsedText: - [self _locked_createElapsedTextField]; - break; - case ASVideoPlayerNodeControlTypeDurationText: - [self _locked_createDurationTextField]; - break; - case ASVideoPlayerNodeControlTypeScrubber: - [self _locked_createScrubber]; - break; - case ASVideoPlayerNodeControlTypeFullScreenButton: - [self _locked_createFullScreenButton]; - break; - case ASVideoPlayerNodeControlTypeFlexGrowSpacer: - [self _locked_createControlFlexGrowSpacer]; - break; - default: - break; - } - } - - if (_delegateFlags.delegateCustomControls && _delegateFlags.delegateLayoutSpecForControls) { - NSDictionary *customControls = [_delegate videoPlayerNodeCustomControls:self]; - std::vector subnodes; - for (id key in customControls) { - id node = customControls[key]; - if (![node isKindOfClass:[ASDisplayNode class]]) { - continue; - } - - subnodes.push_back(node); - [_cachedControls setObject:node forKey:key]; - } - - { - ASUnlockScope(self); - for (ASDisplayNode *subnode : subnodes) { - [self addSubnode:subnode]; - } - } - } - } - - ASPerformBlockOnMainThread(^{ - [self setNeedsLayout]; - }); -} - -- (NSArray *)createDefaultControlElementArray -{ - if (_delegateFlags.delegateNeededDefaultControls) { - return [_delegate videoPlayerNodeNeededDefaultControls:self]; - } - - return @[ @(ASVideoPlayerNodeControlTypePlaybackButton), - @(ASVideoPlayerNodeControlTypeElapsedText), - @(ASVideoPlayerNodeControlTypeScrubber), - @(ASVideoPlayerNodeControlTypeDurationText) ]; -} - -- (void)removeControls -{ - NSMutableDictionary *cachedControls = nil; - { - ASLockScope(self); - - // Grab the cached controls for removing it - cachedControls = [_cachedControls copy]; - [self _locked_cleanCachedControls]; - } - - for (ASDisplayNode *node in [cachedControls objectEnumerator]) { - [node removeFromSupernode]; - } -} - -- (void)_locked_cleanCachedControls -{ - [_cachedControls removeAllObjects]; - - _playbackButtonNode = nil; - _fullScreenButtonNode = nil; - _elapsedTextNode = nil; - _durationTextNode = nil; - _scrubberNode = nil; -} - -- (void)_locked_createPlaybackButton -{ - ASAssertLocked(__instanceLock__); - - if (_playbackButtonNode == nil) { - _playbackButtonNode = [[ASDefaultPlaybackButton alloc] init]; - _playbackButtonNode.style.preferredSize = CGSizeMake(16.0, 22.0); - - if (_delegateFlags.delegatePlaybackButtonTint) { - _playbackButtonNode.tintColor = [_delegate videoPlayerNodePlaybackButtonTint:self]; - } else { - _playbackButtonNode.tintColor = _defaultControlsColor; - } - - if (_videoNode.playerState == ASVideoNodePlayerStatePlaying) { - _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePause; - } - - [_playbackButtonNode addTarget:self action:@selector(didTapPlaybackButton:) forControlEvents:ASControlNodeEventTouchUpInside]; - [_cachedControls setObject:_playbackButtonNode forKey:@(ASVideoPlayerNodeControlTypePlaybackButton)]; - } - - { - ASUnlockScope(self); - [self addSubnode:_playbackButtonNode]; - } -} - -- (void)_locked_createFullScreenButton -{ - ASAssertLocked(__instanceLock__); - - if (_fullScreenButtonNode == nil) { - _fullScreenButtonNode = [[ASButtonNode alloc] init]; - _fullScreenButtonNode.style.preferredSize = CGSizeMake(16.0, 22.0); - - if (_delegateFlags.delegateFullScreenButtonImage) { - [_fullScreenButtonNode setImage:[_delegate videoPlayerNodeFullScreenButtonImage:self] forState:UIControlStateNormal]; - } - - [_fullScreenButtonNode addTarget:self action:@selector(didTapFullScreenButton:) forControlEvents:ASControlNodeEventTouchUpInside]; - [_cachedControls setObject:_fullScreenButtonNode forKey:@(ASVideoPlayerNodeControlTypeFullScreenButton)]; - } - - { - ASUnlockScope(self); - [self addSubnode:_fullScreenButtonNode]; - } -} - -- (void)_locked_createElapsedTextField -{ - ASAssertLocked(__instanceLock__); - - if (_elapsedTextNode == nil) { - _elapsedTextNode = [[ASTextNode alloc] init]; - _elapsedTextNode.attributedText = [self timeLabelAttributedStringForString:@"00:00" - forControlType:ASVideoPlayerNodeControlTypeElapsedText]; - _elapsedTextNode.truncationMode = NSLineBreakByClipping; - - [_cachedControls setObject:_elapsedTextNode forKey:@(ASVideoPlayerNodeControlTypeElapsedText)]; - } - { - ASUnlockScope(self); - [self addSubnode:_elapsedTextNode]; - } -} - -- (void)_locked_createDurationTextField -{ - ASAssertLocked(__instanceLock__); - - if (_durationTextNode == nil) { - _durationTextNode = [[ASTextNode alloc] init]; - _durationTextNode.attributedText = [self timeLabelAttributedStringForString:@"00:00" - forControlType:ASVideoPlayerNodeControlTypeDurationText]; - _durationTextNode.truncationMode = NSLineBreakByClipping; - - [_cachedControls setObject:_durationTextNode forKey:@(ASVideoPlayerNodeControlTypeDurationText)]; - } - [self updateDurationTimeLabel]; - { - ASUnlockScope(self); - [self addSubnode:_durationTextNode]; - } -} - -- (void)_locked_createScrubber -{ - ASAssertLocked(__instanceLock__); - - if (_scrubberNode == nil) { - __weak __typeof__(self) weakSelf = self; - _scrubberNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull { - __typeof__(self) strongSelf = weakSelf; - - UISlider *slider = [[UISlider alloc] initWithFrame:CGRectZero]; - slider.minimumValue = 0.0; - slider.maximumValue = 1.0; - - if (_delegateFlags.delegateScrubberMinimumTrackTintColor) { - slider.minimumTrackTintColor = [strongSelf.delegate videoPlayerNodeScrubberMinimumTrackTint:strongSelf]; - } - - if (_delegateFlags.delegateScrubberMaximumTrackTintColor) { - slider.maximumTrackTintColor = [strongSelf.delegate videoPlayerNodeScrubberMaximumTrackTint:strongSelf]; - } - - if (_delegateFlags.delegateScrubberThumbTintColor) { - slider.thumbTintColor = [strongSelf.delegate videoPlayerNodeScrubberThumbTint:strongSelf]; - } - - if (_delegateFlags.delegateScrubberThumbImage) { - UIImage *thumbImage = [strongSelf.delegate videoPlayerNodeScrubberThumbImage:strongSelf]; - [slider setThumbImage:thumbImage forState:UIControlStateNormal]; - } - - - [slider addTarget:strongSelf action:@selector(beginSeek) forControlEvents:UIControlEventTouchDown]; - [slider addTarget:strongSelf action:@selector(endSeek) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchUpOutside|UIControlEventTouchCancel]; - [slider addTarget:strongSelf action:@selector(seekTimeDidChange:) forControlEvents:UIControlEventValueChanged]; - - return slider; - }]; - - _scrubberNode.style.flexShrink = 1; - - [_cachedControls setObject:_scrubberNode forKey:@(ASVideoPlayerNodeControlTypeScrubber)]; - } - { - ASUnlockScope(self); - [self addSubnode:_scrubberNode]; - } -} - -- (void)_locked_createControlFlexGrowSpacer -{ - ASAssertLocked(__instanceLock__); - - if (_controlFlexGrowSpacerSpec == nil) { - _controlFlexGrowSpacerSpec = [[ASStackLayoutSpec alloc] init]; - _controlFlexGrowSpacerSpec.style.flexGrow = 1.0; - } - - [_cachedControls setObject:_controlFlexGrowSpacerSpec forKey:@(ASVideoPlayerNodeControlTypeFlexGrowSpacer)]; -} - -- (void)updateDurationTimeLabel -{ - if (!_durationTextNode) { - return; - } - NSString *formattedDuration = [self timeStringForCMTime:_duration forTimeLabelType:ASVideoPlayerNodeControlTypeDurationText]; - _durationTextNode.attributedText = [self timeLabelAttributedStringForString:formattedDuration forControlType:ASVideoPlayerNodeControlTypeDurationText]; -} - -- (void)updateElapsedTimeLabel:(NSTimeInterval)seconds -{ - if (!_elapsedTextNode) { - return; - } - NSString *formattedElapsed = [self timeStringForCMTime:CMTimeMakeWithSeconds( seconds, _videoNode.periodicTimeObserverTimescale ) forTimeLabelType:ASVideoPlayerNodeControlTypeElapsedText]; - _elapsedTextNode.attributedText = [self timeLabelAttributedStringForString:formattedElapsed forControlType:ASVideoPlayerNodeControlTypeElapsedText]; -} - -- (NSAttributedString*)timeLabelAttributedStringForString:(NSString*)string forControlType:(ASVideoPlayerNodeControlType)controlType -{ - NSDictionary *options; - if (_delegateFlags.delegateTimeLabelAttributes) { - options = [_delegate videoPlayerNodeTimeLabelAttributes:self timeLabelType:controlType]; - } else { - options = @{ - NSFontAttributeName : [UIFont systemFontOfSize:12.0], - NSForegroundColorAttributeName: _defaultControlsColor - }; - } - - - NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:options]; - - return attributedString; -} - -#pragma mark - ASVideoNodeDelegate -- (void)videoNode:(ASVideoNode *)videoNode willChangePlayerState:(ASVideoNodePlayerState)state toState:(ASVideoNodePlayerState)toState -{ - if (_delegateFlags.delegateVideoNodeWillChangeState) { - [_delegate videoPlayerNode:self willChangeVideoNodeState:state toVideoNodeState:toState]; - } - - if (toState == ASVideoNodePlayerStateReadyToPlay) { - _duration = _videoNode.currentItem.duration; - [self updateDurationTimeLabel]; - } - - if (toState == ASVideoNodePlayerStatePlaying) { - _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePause; - [self removeSpinner]; - } else if (toState != ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying && toState != ASVideoNodePlayerStateReadyToPlay) { - _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePlay; - } - - if (toState == ASVideoNodePlayerStateLoading || toState == ASVideoNodePlayerStateInitialLoading) { - [self showSpinner]; - } - - if (toState == ASVideoNodePlayerStateReadyToPlay || toState == ASVideoNodePlayerStatePaused || toState == ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying) { - [self removeSpinner]; - } -} - -- (BOOL)videoNode:(ASVideoNode *)videoNode shouldChangePlayerStateTo:(ASVideoNodePlayerState)state -{ - if (_delegateFlags.delegateVideoNodeShouldChangeState) { - return [_delegate videoPlayerNode:self shouldChangeVideoNodeStateTo:state]; - } - return YES; -} - -- (void)videoNode:(ASVideoNode *)videoNode didPlayToTimeInterval:(NSTimeInterval)timeInterval -{ - if (_delegateFlags.delegateVideoNodeDidPlayToTime) { - [_delegate videoPlayerNode:self didPlayToTime:_videoNode.player.currentTime]; - } - - if (_isSeeking) { - return; - } - - if (_elapsedTextNode) { - [self updateElapsedTimeLabel:timeInterval]; - } - - if (_scrubberNode) { - [(UISlider*)_scrubberNode.view setValue:( timeInterval / CMTimeGetSeconds(_duration) ) animated:NO]; - } -} - -- (void)videoDidPlayToEnd:(ASVideoNode *)videoNode -{ - if (_delegateFlags.delegateVideoNodePlaybackDidFinish) { - [_delegate videoPlayerNodeDidPlayToEnd:self]; - } -} - -- (void)didTapVideoNode:(ASVideoNode *)videoNode -{ - if (_delegateFlags.delegateDidTapVideoPlayerNode) { - [_delegate didTapVideoPlayerNode:self]; - } else { - [self togglePlayPause]; - } -} - -- (void)videoNode:(ASVideoNode *)videoNode didSetCurrentItem:(AVPlayerItem *)currentItem -{ - if (_delegateFlags.delegateVideoPlayerNodeDidSetCurrentItem) { - [_delegate videoPlayerNode:self didSetCurrentItem:currentItem]; - } -} - -- (void)videoNode:(ASVideoNode *)videoNode didStallAtTimeInterval:(NSTimeInterval)timeInterval -{ - if (_delegateFlags.delegateVideoPlayerNodeDidStallAtTimeInterval) { - [_delegate videoPlayerNode:self didStallAtTimeInterval:timeInterval]; - } -} - -- (void)videoNodeDidStartInitialLoading:(ASVideoNode *)videoNode -{ - if (_delegateFlags.delegateVideoPlayerNodeDidStartInitialLoading) { - [_delegate videoPlayerNodeDidStartInitialLoading:self]; - } -} - -- (void)videoNodeDidFinishInitialLoading:(ASVideoNode *)videoNode -{ - if (_delegateFlags.delegateVideoPlayerNodeDidFinishInitialLoading) { - [_delegate videoPlayerNodeDidFinishInitialLoading:self]; - } -} - -- (void)videoNodeDidRecoverFromStall:(ASVideoNode *)videoNode -{ - if (_delegateFlags.delegateVideoPlayerNodeDidRecoverFromStall) { - [_delegate videoPlayerNodeDidRecoverFromStall:self]; - } -} - -#pragma mark - Actions -- (void)togglePlayPause -{ - if (_videoNode.playerState == ASVideoNodePlayerStatePlaying) { - [_videoNode pause]; - } else { - [_videoNode play]; - } -} - -- (void)showSpinner -{ - ASLockScopeSelf(); - - if (!_spinnerNode) { - __weak __typeof__(self) weakSelf = self; - _spinnerNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{ - __typeof__(self) strongSelf = weakSelf; - UIActivityIndicatorView *spinnnerView = [[UIActivityIndicatorView alloc] init]; - spinnnerView.backgroundColor = [UIColor clearColor]; - - if (_delegateFlags.delegateSpinnerTintColor) { - spinnnerView.color = [_delegate videoPlayerNodeSpinnerTint:strongSelf]; - } else { - spinnnerView.color = _defaultControlsColor; - } - - if (_delegateFlags.delegateSpinnerStyle) { - spinnnerView.activityIndicatorViewStyle = [_delegate videoPlayerNodeSpinnerStyle:strongSelf]; - } - - return spinnnerView; - }]; - _spinnerNode.style.preferredSize = CGSizeMake(44.0, 44.0); - - const auto spinnerNode = _spinnerNode; - { - ASUnlockScope(self); - [self addSubnode:spinnerNode]; - [self setNeedsLayout]; - } - } - [(UIActivityIndicatorView *)_spinnerNode.view startAnimating]; -} - -- (void)removeSpinner -{ - ASDisplayNode *spinnerNode = nil; - { - ASLockScopeSelf(); - if (!_spinnerNode) { - return; - } - - spinnerNode = _spinnerNode; - _spinnerNode = nil; - } - - [spinnerNode removeFromSupernode]; -} - -- (void)didTapPlaybackButton:(ASControlNode*)node -{ - [self togglePlayPause]; -} - -- (void)didTapFullScreenButton:(ASButtonNode*)node -{ - if (_delegateFlags.delegateDidTapFullScreenButtonNode) { - [_delegate didTapFullScreenButtonNode:node]; - } -} - -- (void)beginSeek -{ - _isSeeking = YES; -} - -- (void)endSeek -{ - _isSeeking = NO; -} - -- (void)seekTimeDidChange:(UISlider*)slider -{ - CGFloat percentage = slider.value * 100; - [self seekToTime:percentage]; -} - -#pragma mark - Public API -- (void)seekToTime:(CGFloat)percentComplete -{ - CGFloat seconds = ( CMTimeGetSeconds(_duration) * percentComplete ) / 100; - - [self updateElapsedTimeLabel:seconds]; - [_videoNode.player seekToTime:CMTimeMakeWithSeconds(seconds, _videoNode.periodicTimeObserverTimescale)]; - - if (_videoNode.playerState != ASVideoNodePlayerStatePlaying) { - [self togglePlayPause]; - } -} - -- (void)play -{ - [_videoNode play]; -} - -- (void)pause -{ - [_videoNode pause]; -} - -- (BOOL)isPlaying -{ - return [_videoNode isPlaying]; -} - -- (void)resetToPlaceholder -{ - [_videoNode resetToPlaceholder]; -} - -- (NSArray *)controlsForLayoutSpec -{ - NSMutableArray *controls = [[NSMutableArray alloc] initWithCapacity:_cachedControls.count]; - - if (_cachedControls[ @(ASVideoPlayerNodeControlTypePlaybackButton) ]) { - [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypePlaybackButton) ]]; - } - - if (_cachedControls[ @(ASVideoPlayerNodeControlTypeElapsedText) ]) { - [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeElapsedText) ]]; - } - - if (_cachedControls[ @(ASVideoPlayerNodeControlTypeScrubber) ]) { - [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeScrubber) ]]; - } - - if (_cachedControls[ @(ASVideoPlayerNodeControlTypeDurationText) ]) { - [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeDurationText) ]]; - } - - if (_cachedControls[ @(ASVideoPlayerNodeControlTypeFullScreenButton) ]) { - [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeFullScreenButton) ]]; - } - - return controls; -} - - -#pragma mark - Layout - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - CGSize maxSize = constrainedSize.max; - - // Prevent crashes through if infinite width or height - if (isinf(maxSize.width) || isinf(maxSize.height)) { - ASDisplayNodeAssert(NO, @"Infinite width or height in ASVideoPlayerNode"); - maxSize = CGSizeZero; - } - - _videoNode.style.preferredSize = maxSize; - - ASLayoutSpec *layoutSpec; - if (_delegateFlags.delegateLayoutSpecForControls) { - layoutSpec = [_delegate videoPlayerNodeLayoutSpec:self forControls:_cachedControls forMaximumSize:maxSize]; - } else { - layoutSpec = [self defaultLayoutSpecThatFits:maxSize]; - } - - NSMutableArray *children = [[NSMutableArray alloc] init]; - - if (_spinnerNode) { - ASCenterLayoutSpec *centerLayoutSpec = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionDefault child:_spinnerNode]; - centerLayoutSpec.style.preferredSize = maxSize; - [children addObject:centerLayoutSpec]; - } - - ASOverlayLayoutSpec *overlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_videoNode overlay:layoutSpec]; - overlaySpec.style.preferredSize = maxSize; - [children addObject:overlaySpec]; - - return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:children]; -} - -- (ASLayoutSpec *)defaultLayoutSpecThatFits:(CGSize)maxSize -{ - _scrubberNode.style.preferredSize = CGSizeMake(maxSize.width, 44.0); - - ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; - spacer.style.flexGrow = 1.0; - - ASStackLayoutSpec *controlbarSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal - spacing:10.0 - justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsCenter - children: [self controlsForLayoutSpec] ]; - controlbarSpec.style.alignSelf = ASStackLayoutAlignSelfStretch; - - UIEdgeInsets insets = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0); - - ASInsetLayoutSpec *controlbarInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:controlbarSpec]; - - controlbarInsetSpec.style.alignSelf = ASStackLayoutAlignSelfStretch; - - ASStackLayoutSpec *mainVerticalStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical - spacing:0.0 - justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsStart - children:@[spacer,controlbarInsetSpec]]; - - return mainVerticalStack; -} - -#pragma mark - Properties -- (id)delegate -{ - return _delegate; -} - -- (void)setDelegate:(id)delegate -{ - if (delegate == _delegate) { - return; - } - - _delegate = delegate; - - if (_delegate == nil) { - memset(&_delegateFlags, 0, sizeof(_delegateFlags)); - } else { - _delegateFlags.delegateNeededDefaultControls = [_delegate respondsToSelector:@selector(videoPlayerNodeNeededDefaultControls:)]; - _delegateFlags.delegateCustomControls = [_delegate respondsToSelector:@selector(videoPlayerNodeCustomControls:)]; - _delegateFlags.delegateSpinnerTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeSpinnerTint:)]; - _delegateFlags.delegateSpinnerStyle = [_delegate respondsToSelector:@selector(videoPlayerNodeSpinnerStyle:)]; - _delegateFlags.delegateScrubberMaximumTrackTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberMaximumTrackTint:)]; - _delegateFlags.delegateScrubberMinimumTrackTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberMinimumTrackTint:)]; - _delegateFlags.delegateScrubberThumbTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberThumbTint:)]; - _delegateFlags.delegateScrubberThumbImage = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberThumbImage:)]; - _delegateFlags.delegateTimeLabelAttributes = [_delegate respondsToSelector:@selector(videoPlayerNodeTimeLabelAttributes:timeLabelType:)]; - _delegateFlags.delegateLayoutSpecForControls = [_delegate respondsToSelector:@selector(videoPlayerNodeLayoutSpec:forControls:forMaximumSize:)]; - _delegateFlags.delegateVideoNodeDidPlayToTime = [_delegate respondsToSelector:@selector(videoPlayerNode:didPlayToTime:)]; - _delegateFlags.delegateVideoNodeWillChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:willChangeVideoNodeState:toVideoNodeState:)]; - _delegateFlags.delegateVideoNodePlaybackDidFinish = [_delegate respondsToSelector:@selector(videoPlayerNodeDidPlayToEnd:)]; - _delegateFlags.delegateVideoNodeShouldChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:shouldChangeVideoNodeStateTo:)]; - _delegateFlags.delegateTimeLabelAttributedString = [_delegate respondsToSelector:@selector(videoPlayerNode:timeStringForTimeLabelType:forTime:)]; - _delegateFlags.delegatePlaybackButtonTint = [_delegate respondsToSelector:@selector(videoPlayerNodePlaybackButtonTint:)]; - _delegateFlags.delegateFullScreenButtonImage = [_delegate respondsToSelector:@selector(videoPlayerNodeFullScreenButtonImage:)]; - _delegateFlags.delegateDidTapVideoPlayerNode = [_delegate respondsToSelector:@selector(didTapVideoPlayerNode:)]; - _delegateFlags.delegateDidTapFullScreenButtonNode = [_delegate respondsToSelector:@selector(didTapFullScreenButtonNode:)]; - _delegateFlags.delegateVideoPlayerNodeDidSetCurrentItem = [_delegate respondsToSelector:@selector(videoPlayerNode:didSetCurrentItem:)]; - _delegateFlags.delegateVideoPlayerNodeDidStallAtTimeInterval = [_delegate respondsToSelector:@selector(videoPlayerNode:didStallAtTimeInterval:)]; - _delegateFlags.delegateVideoPlayerNodeDidStartInitialLoading = [_delegate respondsToSelector:@selector(videoPlayerNodeDidStartInitialLoading:)]; - _delegateFlags.delegateVideoPlayerNodeDidFinishInitialLoading = [_delegate respondsToSelector:@selector(videoPlayerNodeDidFinishInitialLoading:)]; - _delegateFlags.delegateVideoPlayerNodeDidRecoverFromStall = [_delegate respondsToSelector:@selector(videoPlayerNodeDidRecoverFromStall:)]; - } -} - -- (void)setControlsDisabled:(BOOL)controlsDisabled -{ - if (_controlsDisabled == controlsDisabled) { - return; - } - - _controlsDisabled = controlsDisabled; - - if (_controlsDisabled && _cachedControls.count > 0) { - [self removeControls]; - } else if (!_controlsDisabled) { - [self createControls]; - } -} - -- (void)setShouldAutoPlay:(BOOL)shouldAutoPlay -{ - if (_shouldAutoPlay == shouldAutoPlay) { - return; - } - _shouldAutoPlay = shouldAutoPlay; - _videoNode.shouldAutoplay = _shouldAutoPlay; -} - -- (void)setShouldAutoRepeat:(BOOL)shouldAutoRepeat -{ - if (_shouldAutoRepeat == shouldAutoRepeat) { - return; - } - _shouldAutoRepeat = shouldAutoRepeat; - _videoNode.shouldAutorepeat = _shouldAutoRepeat; -} - -- (void)setMuted:(BOOL)muted -{ - if (_muted == muted) { - return; - } - _muted = muted; - _videoNode.muted = _muted; -} - -- (void)setPeriodicTimeObserverTimescale:(int32_t)periodicTimeObserverTimescale -{ - if (_periodicTimeObserverTimescale == periodicTimeObserverTimescale) { - return; - } - _periodicTimeObserverTimescale = periodicTimeObserverTimescale; - _videoNode.periodicTimeObserverTimescale = _periodicTimeObserverTimescale; -} - -- (NSString *)gravity -{ - if (_gravity == nil) { - _gravity = _videoNode.gravity; - } - return _gravity; -} - -- (void)setGravity:(NSString *)gravity -{ - if (_gravity == gravity) { - return; - } - _gravity = gravity; - _videoNode.gravity = _gravity; -} - -- (ASVideoNodePlayerState)playerState -{ - return _videoNode.playerState; -} - -- (BOOL)shouldAggressivelyRecoverFromStall -{ - return _videoNode.shouldAggressivelyRecoverFromStall; -} - -- (void) setPlaceholderImageURL:(NSURL *)placeholderImageURL -{ - _videoNode.URL = placeholderImageURL; -} - -- (NSURL*) placeholderImageURL -{ - return _videoNode.URL; -} - -- (ASVideoNode*)videoNode -{ - return _videoNode; -} - -- (void)setShouldAggressivelyRecoverFromStall:(BOOL)shouldAggressivelyRecoverFromStall -{ - if (_shouldAggressivelyRecoverFromStall == shouldAggressivelyRecoverFromStall) { - return; - } - _shouldAggressivelyRecoverFromStall = shouldAggressivelyRecoverFromStall; - _videoNode.shouldAggressivelyRecoverFromStall = _shouldAggressivelyRecoverFromStall; -} - -#pragma mark - Helpers -- (NSString *)timeStringForCMTime:(CMTime)time forTimeLabelType:(ASVideoPlayerNodeControlType)type -{ - if (!CMTIME_IS_VALID(time)) { - return @"00:00"; - } - if (_delegateFlags.delegateTimeLabelAttributedString) { - return [_delegate videoPlayerNode:self timeStringForTimeLabelType:type forTime:time]; - } - - NSUInteger dTotalSeconds = CMTimeGetSeconds(time); - - NSUInteger dHours = floor(dTotalSeconds / 3600); - NSUInteger dMinutes = floor(dTotalSeconds % 3600 / 60); - NSUInteger dSeconds = floor(dTotalSeconds % 3600 % 60); - - NSString *videoDurationText; - if (dHours > 0) { - videoDurationText = [NSString stringWithFormat:@"%i:%02i:%02i", (int)dHours, (int)dMinutes, (int)dSeconds]; - } else { - videoDurationText = [NSString stringWithFormat:@"%02i:%02i", (int)dMinutes, (int)dSeconds]; - } - return videoDurationText; -} - -@end - -#endif // TARGET_OS_IOS -#endif - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASViewController.h b/submodules/AsyncDisplayKit/Source/ASViewController.h deleted file mode 100644 index 18c1be5456..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASViewController.h +++ /dev/null @@ -1,96 +0,0 @@ -// -// ASViewController.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 -#import -#import - -@class ASTraitCollection; - -NS_ASSUME_NONNULL_BEGIN - -typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitCollectionBlock)(UITraitCollection *traitCollection); -typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitWindowSizeBlock)(CGSize windowSize); - -/** - * ASViewController allows you to have a completely node backed hierarchy. It automatically - * handles @c ASVisibilityDepth, automatic range mode and propogating @c ASDisplayTraits to contained nodes. - * - * You can opt-out of node backed hierarchy and use it like a normal UIViewController. - * More importantly, you can use it as a base class for all of your view controllers among which some use a node hierarchy and some don't. - * See examples/ASDKgram project for actual implementation. - */ -@interface ASViewController<__covariant DisplayNodeType : ASDisplayNode *> : UIViewController - -/** - * ASViewController initializer. - * - * @param node An ASDisplayNode which will provide the root view (self.view) - * @return An ASViewController instance whose root view will be backed by the provided ASDisplayNode. - * - * @see ASVisibilityDepth - */ -- (instancetype)initWithNode:(DisplayNodeType)node NS_DESIGNATED_INITIALIZER; - -NS_ASSUME_NONNULL_END - -/** - * @return node Returns the ASDisplayNode which provides the backing view to the view controller. - */ -@property (nonatomic, readonly, null_unspecified) DisplayNodeType node; - -NS_ASSUME_NONNULL_BEGIN - -/** - * Set this block to customize the ASDisplayTraits returned when the VC transitions to the given traitCollection. - */ -@property (nonatomic, copy) ASDisplayTraitsForTraitCollectionBlock overrideDisplayTraitsWithTraitCollection; - -/** - * Set this block to customize the ASDisplayTraits returned when the VC transitions to the given window size. - */ -@property (nonatomic, copy) ASDisplayTraitsForTraitWindowSizeBlock overrideDisplayTraitsWithWindowSize ASDISPLAYNODE_DEPRECATED_MSG("This property is actually never accessed inside the framework"); - -/** - * @abstract Passthrough property to the the .interfaceState of the node. - * @return The current ASInterfaceState of the node, indicating whether it is visible and other situational properties. - * @see ASInterfaceState - */ -@property (nonatomic, readonly) ASInterfaceState interfaceState; - - -// AsyncDisplayKit 2.0 BETA: This property is still being tested, but it allows -// blocking as a view controller becomes visible to ensure no placeholders flash onscreen. -// Refer to examples/SynchronousConcurrency, AsyncViewController.m -@property (nonatomic) BOOL neverShowPlaceholders; - -/* Custom container UIViewController subclasses can use this property to add to the overlay - that UIViewController calculates for the safeAreaInsets for contained view controllers. - */ -@property(nonatomic) UIEdgeInsets additionalSafeAreaInsets; - -@end - -@interface ASViewController (ASRangeControllerUpdateRangeProtocol) - -/** - * Automatically adjust range mode based on view events. If you set this to YES, the view controller or its node - * must conform to the ASRangeControllerUpdateRangeProtocol. - * - * Default value is YES *if* node or view controller conform to ASRangeControllerUpdateRangeProtocol otherwise it is NO. - */ -@property (nonatomic) BOOL automaticallyAdjustRangeModeBasedOnViewEvents; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASViewController.mm b/submodules/AsyncDisplayKit/Source/ASViewController.mm deleted file mode 100644 index 09ddd44c14..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASViewController.mm +++ /dev/null @@ -1,362 +0,0 @@ -// -// ASViewController.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 -#import -#import -#import -#import -#import -#import -#import "Private/ASInternalHelpers.h" - -@implementation ASViewController -{ - BOOL _ensureDisplayed; - BOOL _automaticallyAdjustRangeModeBasedOnViewEvents; - BOOL _parentManagesVisibilityDepth; - NSInteger _visibilityDepth; - BOOL _selfConformsToRangeModeProtocol; - BOOL _nodeConformsToRangeModeProtocol; - UIEdgeInsets _fallbackAdditionalSafeAreaInsets; -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wobjc-designated-initializers" - -- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil -{ - if (!(self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) { - return nil; - } - - [self _initializeInstance]; - - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)aDecoder -{ - if (!(self = [super initWithCoder:aDecoder])) { - return nil; - } - - [self _initializeInstance]; - - return self; -} - -#pragma clang diagnostic pop - -- (instancetype)initWithNode:(ASDisplayNode *)node -{ - if (!(self = [super initWithNibName:nil bundle:nil])) { - return nil; - } - - _node = node; - [self _initializeInstance]; - - return self; -} - -- (void)_initializeInstance -{ - if (_node == nil) { - return; - } - - _node.viewControllerRoot = YES; - - _selfConformsToRangeModeProtocol = [self conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]; - _nodeConformsToRangeModeProtocol = [_node conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]; - _automaticallyAdjustRangeModeBasedOnViewEvents = _selfConformsToRangeModeProtocol || _nodeConformsToRangeModeProtocol; - - _fallbackAdditionalSafeAreaInsets = UIEdgeInsetsZero; - - // In case the node will get loaded - if (_node.nodeLoaded) { - // Node already loaded the view - [self view]; - } else { - // If the node didn't load yet add ourselves as on did load observer to load the view in case the node gets loaded - // before the view controller - __weak __typeof__(self) weakSelf = self; - [_node onDidLoad:^(__kindof ASDisplayNode * _Nonnull node) { - if ([weakSelf isViewLoaded] == NO) { - [weakSelf view]; - } - }]; - } -} - -- (void)dealloc -{ - ASPerformBackgroundDeallocation(&_node); -} - -- (void)loadView -{ - // Apple applies a frame and autoresizing masks we need. Allocating a view is not - // nearly as expensive as adding and removing it from a hierarchy, and fortunately - // we can avoid that here. Enabling layerBacking on a single node in the hierarchy - // will have a greater performance benefit than the impact of this transient view. - [super loadView]; - - if (_node == nil) { - return; - } - - ASDisplayNodeAssertTrue(!_node.layerBacked); - - UIView *view = self.view; - CGRect frame = view.frame; - UIViewAutoresizing autoresizingMask = view.autoresizingMask; - - // We have what we need, so now create and assign the view we actually want. - view = _node.view; - _node.frame = frame; - _node.autoresizingMask = autoresizingMask; - self.view = view; - - // ensure that self.node has a valid trait collection before a subclass's implementation of viewDidLoad. - // Any subnodes added in viewDidLoad will then inherit the proper environment. - ASPrimitiveTraitCollection traitCollection = [self primitiveTraitCollectionForUITraitCollection:self.traitCollection]; - [self propagateNewTraitCollection:traitCollection]; -} - -- (void)viewWillLayoutSubviews -{ - [super viewWillLayoutSubviews]; - - // Before layout, make sure that our trait collection containerSize actually matches the size of our bounds. - // If not, we need to update the traits and propagate them. - - CGSize boundsSize = self.view.bounds.size; - if (CGSizeEqualToSize(self.node.primitiveTraitCollection.containerSize, boundsSize) == NO) { - [UIView performWithoutAnimation:^{ - ASPrimitiveTraitCollection traitCollection = [self primitiveTraitCollectionForUITraitCollection:self.traitCollection]; - traitCollection.containerSize = boundsSize; - - // this method will call measure - [self propagateNewTraitCollection:traitCollection]; - }]; - } else { - // Call layoutThatFits: to let the node prepare for a layout that will happen shortly in the layout pass of the view. - // If the node's constrained size didn't change between the last layout pass it's a no-op - [_node layoutThatFits:[self nodeConstrainedSize]]; - } -} - -- (void)viewDidLayoutSubviews -{ - if (_ensureDisplayed && self.neverShowPlaceholders) { - _ensureDisplayed = NO; - [_node recursivelyEnsureDisplaySynchronously:YES]; - } - [super viewDidLayoutSubviews]; - - if (!AS_AT_LEAST_IOS11) { - [self _updateNodeFallbackSafeArea]; - } -} - -- (void)_updateNodeFallbackSafeArea -{ - UIEdgeInsets safeArea = UIEdgeInsetsMake(self.topLayoutGuide.length, 0, self.bottomLayoutGuide.length, 0); - UIEdgeInsets additionalInsets = self.additionalSafeAreaInsets; - - safeArea = ASConcatInsets(safeArea, additionalInsets); - - _node.fallbackSafeAreaInsets = safeArea; -} - -ASVisibilityDidMoveToParentViewController; - -- (void)viewWillAppear:(BOOL)animated -{ - as_activity_create_for_scope("ASViewController will appear"); - as_log_debug(ASNodeLog(), "View controller %@ will appear", self); - - [super viewWillAppear:animated]; - - _ensureDisplayed = YES; - - // A layout pass is forced this early to get nodes like ASCollectionNode, ASTableNode etc. - // into the hierarchy before UIKit applies the scroll view inset adjustments, if automatic subnode management - // is enabled. Otherwise the insets would not be applied. - [_node.view layoutIfNeeded]; - - if (_parentManagesVisibilityDepth == NO) { - [self setVisibilityDepth:0]; - } -} - -ASVisibilitySetVisibilityDepth; - -ASVisibilityViewDidDisappearImplementation; - -ASVisibilityDepthImplementation; - -- (void)visibilityDepthDidChange -{ - ASLayoutRangeMode rangeMode = ASLayoutRangeModeForVisibilityDepth(self.visibilityDepth); -#if ASEnableVerboseLogging - NSString *rangeModeString; - switch (rangeMode) { - case ASLayoutRangeModeMinimum: - rangeModeString = @"Minimum"; - break; - - case ASLayoutRangeModeFull: - rangeModeString = @"Full"; - break; - - case ASLayoutRangeModeVisibleOnly: - rangeModeString = @"Visible Only"; - break; - - case ASLayoutRangeModeLowMemory: - rangeModeString = @"Low Memory"; - break; - - default: - break; - } - as_log_verbose(ASNodeLog(), "Updating visibility of %@ to: %@ (visibility depth: %zd)", self, rangeModeString, self.visibilityDepth); -#endif - [self updateCurrentRangeModeWithModeIfPossible:rangeMode]; -} - -#pragma mark - Automatic range mode - -- (BOOL)automaticallyAdjustRangeModeBasedOnViewEvents -{ - return _automaticallyAdjustRangeModeBasedOnViewEvents; -} - -- (void)setAutomaticallyAdjustRangeModeBasedOnViewEvents:(BOOL)automaticallyAdjustRangeModeBasedOnViewEvents -{ - if (automaticallyAdjustRangeModeBasedOnViewEvents != _automaticallyAdjustRangeModeBasedOnViewEvents) { - if (automaticallyAdjustRangeModeBasedOnViewEvents && _selfConformsToRangeModeProtocol == NO && _nodeConformsToRangeModeProtocol == NO) { - NSLog(@"Warning: automaticallyAdjustRangeModeBasedOnViewEvents set to YES in %@, but range mode updating is not possible because neither view controller nor node %@ conform to ASRangeControllerUpdateRangeProtocol.", self, _node); - } - _automaticallyAdjustRangeModeBasedOnViewEvents = automaticallyAdjustRangeModeBasedOnViewEvents; - } -} - -- (void)updateCurrentRangeModeWithModeIfPossible:(ASLayoutRangeMode)rangeMode -{ - if (!_automaticallyAdjustRangeModeBasedOnViewEvents) { - return; - } - - if (_selfConformsToRangeModeProtocol) { - id rangeUpdater = (id)self; - [rangeUpdater updateCurrentRangeWithMode:rangeMode]; - } - - if (_nodeConformsToRangeModeProtocol) { - id rangeUpdater = (id)_node; - [rangeUpdater updateCurrentRangeWithMode:rangeMode]; - } -} - -#pragma mark - Layout Helpers - -- (ASSizeRange)nodeConstrainedSize -{ - return ASSizeRangeMake(self.view.bounds.size); -} - -- (ASInterfaceState)interfaceState -{ - return _node.interfaceState; -} - -- (UIEdgeInsets)additionalSafeAreaInsets -{ - if (AS_AVAILABLE_IOS(11.0)) { - return super.additionalSafeAreaInsets; - } - - return _fallbackAdditionalSafeAreaInsets; -} - -- (void)setAdditionalSafeAreaInsets:(UIEdgeInsets)additionalSafeAreaInsets -{ - if (AS_AVAILABLE_IOS(11.0)) { - [super setAdditionalSafeAreaInsets:additionalSafeAreaInsets]; - } else { - _fallbackAdditionalSafeAreaInsets = additionalSafeAreaInsets; - [self _updateNodeFallbackSafeArea]; - } -} - -#pragma mark - ASTraitEnvironment - -- (ASPrimitiveTraitCollection)primitiveTraitCollectionForUITraitCollection:(UITraitCollection *)traitCollection -{ - if (self.overrideDisplayTraitsWithTraitCollection) { - ASTraitCollection *asyncTraitCollection = self.overrideDisplayTraitsWithTraitCollection(traitCollection); - return [asyncTraitCollection primitiveTraitCollection]; - } - - ASDisplayNodeAssertMainThread(); - ASPrimitiveTraitCollection asyncTraitCollection = ASPrimitiveTraitCollectionFromUITraitCollection(traitCollection); - asyncTraitCollection.containerSize = self.view.frame.size; - return asyncTraitCollection; -} - -- (void)propagateNewTraitCollection:(ASPrimitiveTraitCollection)traitCollection -{ - ASPrimitiveTraitCollection oldTraitCollection = self.node.primitiveTraitCollection; - - if (ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(traitCollection, oldTraitCollection) == NO) { - as_activity_scope_verbose(as_activity_create("Propagate ASViewController trait collection", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)); - as_log_debug(ASNodeLog(), "Propagating new traits for %@: %@", self, NSStringFromASPrimitiveTraitCollection(traitCollection)); - self.node.primitiveTraitCollection = traitCollection; - - NSArray> *children = [self.node sublayoutElements]; - for (id child in children) { - ASTraitCollectionPropagateDown(child, traitCollection); - } - - // Once we've propagated all the traits, layout this node. - // Remeasure the node with the latest constrained size – old constrained size may be incorrect. - as_activity_scope_verbose(as_activity_create("Layout ASViewController node with new traits", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)); - [_node layoutThatFits:[self nodeConstrainedSize]]; - } -} - -- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection -{ - [super traitCollectionDidChange:previousTraitCollection]; - - ASPrimitiveTraitCollection traitCollection = [self primitiveTraitCollectionForUITraitCollection:self.traitCollection]; - traitCollection.containerSize = self.view.bounds.size; - [self propagateNewTraitCollection:traitCollection]; -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation -{ - [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; - - ASPrimitiveTraitCollection traitCollection = _node.primitiveTraitCollection; - traitCollection.containerSize = self.view.bounds.size; - [self propagateNewTraitCollection:traitCollection]; -} -#pragma clang diagnostic pop - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/ASYogaUtilities.h b/submodules/AsyncDisplayKit/Source/ASYogaUtilities.h deleted file mode 100644 index c3d1d8233c..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASYogaUtilities.h +++ /dev/null @@ -1,78 +0,0 @@ -// -// ASYogaUtilities.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if YOGA /* YOGA */ - -#import -#import -#import - -// Should pass a string literal, not an NSString as the first argument to ASYogaLog -#define ASYogaLog(x, ...) as_log_verbose(ASLayoutLog(), x, ##__VA_ARGS__); - -@interface ASDisplayNode (YogaHelpers) - -+ (ASDisplayNode *)yogaNode; -+ (ASDisplayNode *)yogaSpacerNode; -+ (ASDisplayNode *)yogaVerticalStack; -+ (ASDisplayNode *)yogaHorizontalStack; - -@end - -// pre-order, depth-first -AS_EXTERN void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode *node, void(^block)(ASDisplayNode *node)); - -#pragma mark - Yoga Type Conversion Helpers - -AS_EXTERN YGAlign yogaAlignItems(ASStackLayoutAlignItems alignItems); -AS_EXTERN YGJustify yogaJustifyContent(ASStackLayoutJustifyContent justifyContent); -AS_EXTERN YGAlign yogaAlignSelf(ASStackLayoutAlignSelf alignSelf); -AS_EXTERN YGFlexDirection yogaFlexDirection(ASStackLayoutDirection direction); -AS_EXTERN float yogaFloatForCGFloat(CGFloat value); -AS_EXTERN float yogaDimensionToPoints(ASDimension dimension); -AS_EXTERN float yogaDimensionToPercent(ASDimension dimension); -AS_EXTERN ASDimension dimensionForEdgeWithEdgeInsets(YGEdge edge, ASEdgeInsets insets); - -AS_EXTERN void ASLayoutElementYogaUpdateMeasureFunc(YGNodeRef yogaNode, id layoutElement); -AS_EXTERN float ASLayoutElementYogaBaselineFunc(YGNodeRef yogaNode, const float width, const float height); -AS_EXTERN YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, - float width, YGMeasureMode widthMode, - float height, YGMeasureMode heightMode); - -#pragma mark - Yoga Style Setter Helpers - -#define YGNODE_STYLE_SET_DIMENSION(yogaNode, property, dimension) \ - if (dimension.unit == ASDimensionUnitPoints) { \ - YGNodeStyleSet##property(yogaNode, yogaDimensionToPoints(dimension)); \ - } else if (dimension.unit == ASDimensionUnitFraction) { \ - YGNodeStyleSet##property##Percent(yogaNode, yogaDimensionToPercent(dimension)); \ - } else { \ - YGNodeStyleSet##property(yogaNode, YGUndefined); \ - }\ - -#define YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, property, dimension, edge) \ - if (dimension.unit == ASDimensionUnitPoints) { \ - YGNodeStyleSet##property(yogaNode, edge, yogaDimensionToPoints(dimension)); \ - } else if (dimension.unit == ASDimensionUnitFraction) { \ - YGNodeStyleSet##property##Percent(yogaNode, edge, yogaDimensionToPercent(dimension)); \ - } else { \ - YGNodeStyleSet##property(yogaNode, edge, YGUndefined); \ - } \ - -#define YGNODE_STYLE_SET_FLOAT_WITH_EDGE(yogaNode, property, dimension, edge) \ - if (dimension.unit == ASDimensionUnitPoints) { \ - YGNodeStyleSet##property(yogaNode, edge, yogaDimensionToPoints(dimension)); \ - } else if (dimension.unit == ASDimensionUnitFraction) { \ - ASDisplayNodeAssert(NO, @"Unexpected Fraction value in applying ##property## values to YGNode"); \ - } else { \ - YGNodeStyleSet##property(yogaNode, edge, YGUndefined); \ - } \ - -#endif /* YOGA */ diff --git a/submodules/AsyncDisplayKit/Source/ASYogaUtilities.mm b/submodules/AsyncDisplayKit/Source/ASYogaUtilities.mm deleted file mode 100644 index f572851a37..0000000000 --- a/submodules/AsyncDisplayKit/Source/ASYogaUtilities.mm +++ /dev/null @@ -1,241 +0,0 @@ -// -// ASYogaUtilities.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#if YOGA /* YOGA */ - -#import -#import - -@implementation ASDisplayNode (YogaHelpers) - -+ (ASDisplayNode *)yogaNode -{ - ASDisplayNode *node = [[ASDisplayNode alloc] init]; - node.automaticallyManagesSubnodes = YES; - [node.style yogaNodeCreateIfNeeded]; - return node; -} - -+ (ASDisplayNode *)yogaSpacerNode -{ - ASDisplayNode *node = [ASDisplayNode yogaNode]; - node.style.flexGrow = 1.0f; - return node; -} - -+ (ASDisplayNode *)yogaVerticalStack -{ - ASDisplayNode *node = [self yogaNode]; - node.style.flexDirection = ASStackLayoutDirectionVertical; - return node; -} - -+ (ASDisplayNode *)yogaHorizontalStack -{ - ASDisplayNode *node = [self yogaNode]; - node.style.flexDirection = ASStackLayoutDirectionHorizontal; - return node; -} - -@end - -void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode *node, void(^block)(ASDisplayNode *node)) -{ - if (node == nil) { - return; - } - block(node); - // We use the accessor here despite the copy, because the block may modify the yoga tree e.g. - // replacing a node. - for (ASDisplayNode *child in node.yogaChildren) { - ASDisplayNodePerformBlockOnEveryYogaChild(child, block); - } -} - -#pragma mark - Yoga Type Conversion Helpers - -YGAlign yogaAlignItems(ASStackLayoutAlignItems alignItems) -{ - switch (alignItems) { - case ASStackLayoutAlignItemsNotSet: return YGAlignAuto; - case ASStackLayoutAlignItemsStart: return YGAlignFlexStart; - case ASStackLayoutAlignItemsEnd: return YGAlignFlexEnd; - case ASStackLayoutAlignItemsCenter: return YGAlignCenter; - case ASStackLayoutAlignItemsStretch: return YGAlignStretch; - case ASStackLayoutAlignItemsBaselineFirst: return YGAlignBaseline; - // FIXME: WARNING, Yoga does not currently support last-baseline item alignment. - case ASStackLayoutAlignItemsBaselineLast: return YGAlignBaseline; - } -} - -YGJustify yogaJustifyContent(ASStackLayoutJustifyContent justifyContent) -{ - switch (justifyContent) { - case ASStackLayoutJustifyContentStart: return YGJustifyFlexStart; - case ASStackLayoutJustifyContentCenter: return YGJustifyCenter; - case ASStackLayoutJustifyContentEnd: return YGJustifyFlexEnd; - case ASStackLayoutJustifyContentSpaceBetween: return YGJustifySpaceBetween; - case ASStackLayoutJustifyContentSpaceAround: return YGJustifySpaceAround; - } -} - -YGAlign yogaAlignSelf(ASStackLayoutAlignSelf alignSelf) -{ - switch (alignSelf) { - case ASStackLayoutAlignSelfStart: return YGAlignFlexStart; - case ASStackLayoutAlignSelfCenter: return YGAlignCenter; - case ASStackLayoutAlignSelfEnd: return YGAlignFlexEnd; - case ASStackLayoutAlignSelfStretch: return YGAlignStretch; - case ASStackLayoutAlignSelfAuto: return YGAlignAuto; - } -} - -YGFlexDirection yogaFlexDirection(ASStackLayoutDirection direction) -{ - return direction == ASStackLayoutDirectionVertical ? YGFlexDirectionColumn : YGFlexDirectionRow; -} - -float yogaFloatForCGFloat(CGFloat value) -{ - if (value < CGFLOAT_MAX / 2) { - return value; - } else { - return YGUndefined; - } -} - -float cgFloatForYogaFloat(float yogaFloat) -{ - return (yogaFloat == YGUndefined) ? CGFLOAT_MAX : yogaFloat; -} - -float yogaDimensionToPoints(ASDimension dimension) -{ - ASDisplayNodeCAssert(dimension.unit == ASDimensionUnitPoints, - @"Dimensions should not be type Fraction for this method: %f", dimension.value); - return yogaFloatForCGFloat(dimension.value); -} - -float yogaDimensionToPercent(ASDimension dimension) -{ - ASDisplayNodeCAssert(dimension.unit == ASDimensionUnitFraction, - @"Dimensions should not be type Points for this method: %f", dimension.value); - return 100.0 * yogaFloatForCGFloat(dimension.value); - -} - -ASDimension dimensionForEdgeWithEdgeInsets(YGEdge edge, ASEdgeInsets insets) -{ - switch (edge) { - case YGEdgeLeft: return insets.left; - case YGEdgeTop: return insets.top; - case YGEdgeRight: return insets.right; - case YGEdgeBottom: return insets.bottom; - case YGEdgeStart: return insets.start; - case YGEdgeEnd: return insets.end; - case YGEdgeHorizontal: return insets.horizontal; - case YGEdgeVertical: return insets.vertical; - case YGEdgeAll: return insets.all; - default: ASDisplayNodeCAssert(NO, @"YGEdge other than ASEdgeInsets is not supported."); - return ASDimensionAuto; - } -} - -void ASLayoutElementYogaUpdateMeasureFunc(YGNodeRef yogaNode, id layoutElement) -{ - if (yogaNode == NULL) { - return; - } - - BOOL shouldHaveMeasureFunc = [layoutElement implementsLayoutMethod]; - // How expensive is it to set a baselineFunc on all (leaf) nodes? - BOOL shouldHaveBaselineFunc = YES; - - if (layoutElement != nil) { - if (shouldHaveMeasureFunc || shouldHaveBaselineFunc) { - // Retain the Context object. This must be explicitly released with a - // __bridge_transfer - YGNodeFree() is not sufficient. - YGNodeSetContext(yogaNode, (__bridge_retained void *)layoutElement); - } - if (shouldHaveMeasureFunc) { - YGNodeSetMeasureFunc(yogaNode, &ASLayoutElementYogaMeasureFunc); - } - if (shouldHaveBaselineFunc) { - YGNodeSetBaselineFunc(yogaNode, &ASLayoutElementYogaBaselineFunc); - } - ASDisplayNodeCAssert(YGNodeGetContext(yogaNode) == (__bridge void *)layoutElement, - @"Yoga node context should contain layoutElement: %@", layoutElement); - } else { - // If we lack any of the conditions above, and currently have a measureFn/baselineFn/context, - // get rid of it. - // Release the __bridge_retained Context object. - __unused id element = (__bridge_transfer id)YGNodeGetContext(yogaNode); - YGNodeSetContext(yogaNode, NULL); - YGNodeSetMeasureFunc(yogaNode, NULL); - YGNodeSetBaselineFunc(yogaNode, NULL); - } -} - -float ASLayoutElementYogaBaselineFunc(YGNodeRef yogaNode, const float width, const float height) -{ - id layoutElement = (__bridge id)YGNodeGetContext(yogaNode); - ASDisplayNodeCAssert([layoutElement conformsToProtocol:@protocol(ASLayoutElement)], - @"Yoga context must be "); - - ASDisplayNode *displayNode = ASDynamicCast(layoutElement, ASDisplayNode); - - switch (displayNode.style.parentAlignStyle) { - case ASStackLayoutAlignItemsBaselineFirst: - return layoutElement.style.ascender; - case ASStackLayoutAlignItemsBaselineLast: - return height + layoutElement.style.descender; - default: - return 0; - } -} - -YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, float width, YGMeasureMode widthMode, - float height, YGMeasureMode heightMode) -{ - id layoutElement = (__bridge id )YGNodeGetContext(yogaNode); - ASDisplayNodeCAssert([layoutElement conformsToProtocol:@protocol(ASLayoutElement)], @"Yoga context must be "); - - width = cgFloatForYogaFloat(width); - height = cgFloatForYogaFloat(height); - - ASSizeRange sizeRange; - sizeRange.min = CGSizeZero; - sizeRange.max = CGSizeMake(width, height); - if (widthMode == YGMeasureModeExactly) { - sizeRange.min.width = sizeRange.max.width; - } else { - // Mode is (YGMeasureModeAtMost | YGMeasureModeUndefined) - ASDimension minWidth = layoutElement.style.minWidth; - sizeRange.min.width = (minWidth.unit == ASDimensionUnitPoints ? yogaDimensionToPoints(minWidth) : 0.0); - } - if (heightMode == YGMeasureModeExactly) { - sizeRange.min.height = sizeRange.max.height; - } else { - // Mode is (YGMeasureModeAtMost | YGMeasureModeUndefined) - ASDimension minHeight = layoutElement.style.minHeight; - sizeRange.min.height = (minHeight.unit == ASDimensionUnitPoints ? yogaDimensionToPoints(minHeight) : 0.0); - } - - ASDisplayNodeCAssert(isnan(sizeRange.min.width) == NO && isnan(sizeRange.min.height) == NO, @"Yoga size range for measurement should not have NaN in minimum"); - if (isnan(sizeRange.max.width)) { - sizeRange.max.width = CGFLOAT_MAX; - } - if (isnan(sizeRange.max.height)) { - sizeRange.max.height = CGFLOAT_MAX; - } - - CGSize size = [[layoutElement layoutThatFits:sizeRange] size]; - return (YGSize){ .width = (float)size.width, .height = (float)size.height }; -} - -#endif /* YOGA */ diff --git a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+Debug.h b/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+Debug.h deleted file mode 100644 index 2f0826289a..0000000000 --- a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+Debug.h +++ /dev/null @@ -1,55 +0,0 @@ -// -// AsyncDisplayKit+Debug.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 -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ASImageNode (Debugging) - -/** - * Enables an ASImageNode debug label that shows the ratio of pixels in the source image to those in - * the displayed bounds (including cropRect). This helps detect excessive image fetching / downscaling, - * as well as upscaling (such as providing a URL not suitable for a Retina device). For dev purposes only. - * Specify YES to show the label on all ASImageNodes with non-1.0x source-to-bounds pixel ratio. - */ -@property (class, nonatomic) BOOL shouldShowImageScalingOverlay; - -@end - -@interface ASControlNode (Debugging) - -/** - * Class method to enable a visualization overlay of the tappable area on the ASControlNode. For app debugging purposes only. - * NOTE: GESTURE RECOGNIZERS, (including tap gesture recognizers on a control node) WILL NOT BE VISUALIZED!!! - * Overlay = translucent GREEN color, - * edges that are clipped by the tappable area of any parent (their bounds + hitTestSlop) in the hierarchy = DARK GREEN BORDERED EDGE, - * edges that are clipped by clipToBounds = YES of any parent in the hierarchy = ORANGE BORDERED EDGE (may still receive touches beyond - * overlay rect, but can't be visualized). - * Specify YES to make this debug feature enabled when messaging the ASControlNode class. - */ -@property (class, nonatomic) BOOL enableHitTestDebug; - -@end - -#ifndef MINIMAL_ASDK -@interface ASDisplayNode (RangeDebugging) - -/** - * Enable a visualization overlay of the all table/collection tuning parameters. For dev purposes only. - * To use, set this in the AppDelegate --> ASDisplayNode.shouldShowRangeDebugOverlay = YES - */ -@property (class, nonatomic) BOOL shouldShowRangeDebugOverlay; - -@end -#endif - - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+Debug.mm b/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+Debug.mm deleted file mode 100644 index f527098dcc..0000000000 --- a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+Debug.mm +++ /dev/null @@ -1,758 +0,0 @@ -// -// AsyncDisplayKit+Debug.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 -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - - -#pragma mark - ASImageNode (Debugging) - -static BOOL __shouldShowImageScalingOverlay = NO; - -@implementation ASImageNode (Debugging) - -+ (void)setShouldShowImageScalingOverlay:(BOOL)show; -{ - __shouldShowImageScalingOverlay = show; -} - -+ (BOOL)shouldShowImageScalingOverlay -{ - return __shouldShowImageScalingOverlay; -} - -@end - -#pragma mark - ASControlNode (DebuggingInternal) - -static BOOL __enableHitTestDebug = NO; - -@interface ASControlNode (DebuggingInternal) - -- (ASImageNode *)debugHighlightOverlay; - -@end - -@implementation ASControlNode (Debugging) - -+ (void)setEnableHitTestDebug:(BOOL)enable -{ - __enableHitTestDebug = enable; -} - -+ (BOOL)enableHitTestDebug -{ - return __enableHitTestDebug; -} - -// layout method required ONLY when hitTestDebug is enabled -- (void)layout -{ - [super layout]; - - if ([ASControlNode enableHitTestDebug]) { - - // Construct hitTestDebug highlight overlay frame indicating tappable area of a node, which can be restricted by two things: - - // (1) Any parent's tapable area (its own bounds + hitTestSlop) may restrict the desired tappable area expansion using - // hitTestSlop of a child as UIKit event delivery (hitTest:) will not search sub-hierarchies if one of our parents does - // not return YES for pointInside:. To circumvent this restriction, a developer will need to set / adjust the hitTestSlop - // on the limiting parent. This is indicated in the overlay by a dark GREEN edge. This is an ACTUAL restriction. - - // (2) Any parent's .clipToBounds. If a parent is clipping, we cannot show the overlay outside that area - // (although it still respond to touch). To indicate that the overlay cannot accurately display the true tappable area, - // the overlay will have an ORANGE edge. This is a VISUALIZATION restriction. - - CGRect intersectRect = UIEdgeInsetsInsetRect(self.bounds, [self hitTestSlop]); - UIRectEdge clippedEdges = UIRectEdgeNone; - UIRectEdge clipsToBoundsClippedEdges = UIRectEdgeNone; - CALayer *layer = self.layer; - CALayer *intersectLayer = layer; - CALayer *intersectSuperlayer = layer.superlayer; - - // FIXED: Stop climbing hierarchy if UIScrollView is encountered (its offset bounds origin may make it seem like our events - // will be clipped when scrolling will actually reveal them (because this process will not re-run due to scrolling)) - while (intersectSuperlayer && ![intersectSuperlayer.delegate respondsToSelector:@selector(contentOffset)]) { - - // Get parent's tappable area - CGRect parentHitRect = intersectSuperlayer.bounds; - BOOL parentClipsToBounds = NO; - - // If parent is a node, tappable area may be expanded by hitTestSlop - ASDisplayNode *parentNode = ASLayerToDisplayNode(intersectSuperlayer); - if (parentNode) { - UIEdgeInsets parentSlop = [parentNode hitTestSlop]; - - // If parent has hitTestSlop, expand tappable area (if parent doesn't clipToBounds) - if (!UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, parentSlop)) { - parentClipsToBounds = parentNode.clipsToBounds; - if (!parentClipsToBounds) { - parentHitRect = UIEdgeInsetsInsetRect(parentHitRect, [parentNode hitTestSlop]); - } - } - } - - // Convert our current rect to parent coordinates - CGRect intersectRectInParentCoordinates = [intersectSuperlayer convertRect:intersectRect fromLayer:intersectLayer]; - - // Intersect rect with the parent's tappable area rect - intersectRect = CGRectIntersection(parentHitRect, intersectRectInParentCoordinates); - if (!CGSizeEqualToSize(parentHitRect.size, intersectRectInParentCoordinates.size)) { - clippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates - parentRect:parentHitRect rectEdge:clippedEdges]; - if (parentClipsToBounds) { - clipsToBoundsClippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates - parentRect:parentHitRect rectEdge:clipsToBoundsClippedEdges]; - } - } - - // move up hierarchy - intersectLayer = intersectSuperlayer; - intersectSuperlayer = intersectLayer.superlayer; - } - - // produce final overlay image (or fill background if edges aren't restricted) - CGRect finalRect = [intersectLayer convertRect:intersectRect toLayer:layer]; - UIColor *fillColor = [[UIColor greenColor] colorWithAlphaComponent:0.4]; - - ASImageNode *debugOverlay = [self debugHighlightOverlay]; - - // determine if edges are clipped and if so, highlight the restricted edges - if (clippedEdges == UIRectEdgeNone) { - debugOverlay.backgroundColor = fillColor; - } else { - const CGFloat borderWidth = 2.0; - UIColor *borderColor = [[UIColor orangeColor] colorWithAlphaComponent:0.8]; - UIColor *clipsBorderColor = [UIColor colorWithRed:30/255.0 green:90/255.0 blue:50/255.0 alpha:0.7]; - CGRect imgRect = CGRectMake(0, 0, 2.0 * borderWidth + 1.0, 2.0 * borderWidth + 1.0); - - ASGraphicsBeginImageContextWithOptions(imgRect.size, NO, 1); - - [fillColor setFill]; - UIRectFill(imgRect); - - [self drawEdgeIfClippedWithEdges:clippedEdges color:clipsBorderColor borderWidth:borderWidth imgRect:imgRect]; - [self drawEdgeIfClippedWithEdges:clipsToBoundsClippedEdges color:borderColor borderWidth:borderWidth imgRect:imgRect]; - - UIImage *debugHighlightImage = ASGraphicsGetImageAndEndCurrentContext(); - - UIEdgeInsets edgeInsets = UIEdgeInsetsMake(borderWidth, borderWidth, borderWidth, borderWidth); - debugOverlay.image = [debugHighlightImage resizableImageWithCapInsets:edgeInsets resizingMode:UIImageResizingModeStretch]; - debugOverlay.backgroundColor = nil; - } - - debugOverlay.frame = finalRect; - } -} - -- (UIRectEdge)setEdgesOfIntersectionForChildRect:(CGRect)childRect parentRect:(CGRect)parentRect rectEdge:(UIRectEdge)rectEdge -{ - // determine which edges of childRect are outside parentRect (and thus will be clipped) - if (childRect.origin.y < parentRect.origin.y) { - rectEdge |= UIRectEdgeTop; - } - if (childRect.origin.x < parentRect.origin.x) { - rectEdge |= UIRectEdgeLeft; - } - if (CGRectGetMaxY(childRect) > CGRectGetMaxY(parentRect)) { - rectEdge |= UIRectEdgeBottom; - } - if (CGRectGetMaxX(childRect) > CGRectGetMaxX(parentRect)) { - rectEdge |= UIRectEdgeRight; - } - - return rectEdge; -} - -- (void)drawEdgeIfClippedWithEdges:(UIRectEdge)rectEdge color:(UIColor *)color borderWidth:(CGFloat)borderWidth imgRect:(CGRect)imgRect -{ - [color setFill]; - - // highlight individual edges of overlay if edge is restricted by parentRect - // so that the developer is aware that increasing hitTestSlop will not result in an expanded tappable area - if (rectEdge & UIRectEdgeTop) { - UIRectFill(CGRectMake(0.0, 0.0, imgRect.size.width, borderWidth)); - } - if (rectEdge & UIRectEdgeLeft) { - UIRectFill(CGRectMake(0.0, 0.0, borderWidth, imgRect.size.height)); - } - if (rectEdge & UIRectEdgeBottom) { - UIRectFill(CGRectMake(0.0, imgRect.size.height - borderWidth, imgRect.size.width, borderWidth)); - } - if (rectEdge & UIRectEdgeRight) { - UIRectFill(CGRectMake(imgRect.size.width - borderWidth, 0.0, borderWidth, imgRect.size.height)); - } -} - -@end - -#pragma mark - ASRangeController (Debugging) - -#ifndef MINIMAL_ASDK - -@interface _ASRangeDebugOverlayView : UIView - -+ (instancetype)sharedInstance NS_RETURNS_RETAINED; - -- (void)addRangeController:(ASRangeController *)rangeController; - -- (void)updateRangeController:(ASRangeController *)controller - withScrollableDirections:(ASScrollDirection)scrollableDirections - scrollDirection:(ASScrollDirection)direction - rangeMode:(ASLayoutRangeMode)mode - displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters - preloadTuningParameters:(ASRangeTuningParameters)preloadTuningParameters - interfaceState:(ASInterfaceState)interfaceState; - -@end - -@interface _ASRangeDebugBarView : UIView - -@property (nonatomic, weak) ASRangeController *rangeController; -@property (nonatomic) BOOL destroyOnLayout; -@property (nonatomic) NSString *debugString; - -- (instancetype)initWithRangeController:(ASRangeController *)rangeController; - -- (void)updateWithVisibleRatio:(CGFloat)visibleRatio - displayRatio:(CGFloat)displayRatio - leadingDisplayRatio:(CGFloat)leadingDisplayRatio - preloadRatio:(CGFloat)preloadRatio - leadingpreloadRatio:(CGFloat)leadingpreloadRatio - direction:(ASScrollDirection)direction; - -@end - -static BOOL __shouldShowRangeDebugOverlay = NO; - -@implementation ASDisplayNode (RangeDebugging) - -+ (void)setShouldShowRangeDebugOverlay:(BOOL)show -{ - __shouldShowRangeDebugOverlay = show; -} - -+ (BOOL)shouldShowRangeDebugOverlay -{ - return __shouldShowRangeDebugOverlay; -} - -@end - -@implementation ASRangeController (DebugInternal) - -+ (void)layoutDebugOverlayIfNeeded -{ - [[_ASRangeDebugOverlayView sharedInstance] setNeedsLayout]; -} - -- (void)addRangeControllerToRangeDebugOverlay -{ - [[_ASRangeDebugOverlayView sharedInstance] addRangeController:self]; -} - -- (void)updateRangeController:(ASRangeController *)controller - withScrollableDirections:(ASScrollDirection)scrollableDirections - scrollDirection:(ASScrollDirection)direction - rangeMode:(ASLayoutRangeMode)mode - displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters - preloadTuningParameters:(ASRangeTuningParameters)preloadTuningParameters - interfaceState:(ASInterfaceState)interfaceState -{ - [[_ASRangeDebugOverlayView sharedInstance] updateRangeController:controller - withScrollableDirections:scrollableDirections - scrollDirection:direction - rangeMode:mode - displayTuningParameters:displayTuningParameters - preloadTuningParameters:preloadTuningParameters - interfaceState:interfaceState]; -} - -@end - - -#pragma mark _ASRangeDebugOverlayView - -@interface _ASRangeDebugOverlayView () -@end - -@implementation _ASRangeDebugOverlayView -{ - NSMutableArray *_rangeControllerViews; - NSInteger _newControllerCount; - NSInteger _removeControllerCount; - BOOL _animating; -} - -+ (UIWindow *)keyWindow -{ - // hack to work around app extensions not having UIApplication...not sure of a better way to do this? - return [[NSClassFromString(@"UIApplication") sharedApplication] keyWindow]; -} - -+ (_ASRangeDebugOverlayView *)sharedInstance NS_RETURNS_RETAINED -{ - static _ASRangeDebugOverlayView *__rangeDebugOverlay = nil; - - if (!__rangeDebugOverlay && ASDisplayNode.shouldShowRangeDebugOverlay) { - __rangeDebugOverlay = [[self alloc] initWithFrame:CGRectZero]; - [[self keyWindow] addSubview:__rangeDebugOverlay]; - } - - return __rangeDebugOverlay; -} - -#define OVERLAY_INSET 10 -#define OVERLAY_SCALE 3 -- (instancetype)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - - if (self) { - _rangeControllerViews = [[NSMutableArray alloc] init]; - self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4]; - self.layer.zPosition = 1000; - self.clipsToBounds = YES; - - CGSize windowSize = [[[self class] keyWindow] bounds].size; - self.frame = CGRectMake(windowSize.width - (windowSize.width / OVERLAY_SCALE) - OVERLAY_INSET, windowSize.height - OVERLAY_INSET, - windowSize.width / OVERLAY_SCALE, 0.0); - - UIPanGestureRecognizer *panGR = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(rangeDebugOverlayWasPanned:)]; - [self addGestureRecognizer:panGR]; - } - - return self; -} - -#define BAR_THICKNESS 24 - -- (void)layoutSubviews -{ - [super layoutSubviews]; - [UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ - [self layoutToFitAllBarsExcept:0]; - } completion:^(BOOL finished) { - - }]; -} - -- (void)layoutToFitAllBarsExcept:(NSInteger)barsToClip -{ - CGSize boundsSize = self.bounds.size; - CGFloat totalHeight = 0.0; - - CGRect barRect = CGRectMake(0, boundsSize.height - BAR_THICKNESS, self.bounds.size.width, BAR_THICKNESS); - NSMutableArray *displayedBars = [NSMutableArray array]; - - for (_ASRangeDebugBarView *barView in [_rangeControllerViews copy]) { - barView.frame = barRect; - - ASInterfaceState interfaceState = [barView.rangeController.dataSource interfaceStateForRangeController:barView.rangeController]; - - if (!(interfaceState & (ASInterfaceStateVisible))) { - if (barView.destroyOnLayout && barView.alpha == 0.0) { - [_rangeControllerViews removeObjectIdenticalTo:barView]; - [barView removeFromSuperview]; - } else { - barView.alpha = 0.0; - } - } else { - assert(!barView.destroyOnLayout); // In this case we should not have a visible interfaceState - barView.alpha = 1.0; - totalHeight += BAR_THICKNESS; - barRect.origin.y -= BAR_THICKNESS; - [displayedBars addObject:barView]; - } - } - - if (totalHeight > 0) { - totalHeight -= (BAR_THICKNESS * barsToClip); - } - - if (barsToClip == 0) { - CGRect overlayFrame = self.frame; - CGFloat heightChange = (overlayFrame.size.height - totalHeight); - - overlayFrame.origin.y += heightChange; - overlayFrame.size.height = totalHeight; - self.frame = overlayFrame; - - for (_ASRangeDebugBarView *barView in displayedBars) { - [self offsetYOrigin:-heightChange forView:barView]; - } - } -} - -- (void)setOrigin:(CGPoint)origin forView:(UIView *)view -{ - CGRect newFrame = view.frame; - newFrame.origin = origin; - view.frame = newFrame; -} - -- (void)offsetYOrigin:(CGFloat)offset forView:(UIView *)view -{ - CGRect newFrame = view.frame; - newFrame.origin = CGPointMake(newFrame.origin.x, newFrame.origin.y + offset); - view.frame = newFrame; -} - -- (void)addRangeController:(ASRangeController *)rangeController -{ - for (_ASRangeDebugBarView *rangeView in _rangeControllerViews) { - if (rangeView.rangeController == rangeController) { - return; - } - } - _ASRangeDebugBarView *rangeView = [[_ASRangeDebugBarView alloc] initWithRangeController:rangeController]; - [_rangeControllerViews addObject:rangeView]; - [self addSubview:rangeView]; - - if (!_animating) { - [self layoutToFitAllBarsExcept:1]; - } - - [UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ - _animating = YES; - [self layoutToFitAllBarsExcept:0]; - } completion:^(BOOL finished) { - _animating = NO; - }]; -} - -- (void)updateRangeController:(ASRangeController *)controller - withScrollableDirections:(ASScrollDirection)scrollableDirections - scrollDirection:(ASScrollDirection)scrollDirection - rangeMode:(ASLayoutRangeMode)rangeMode - displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters - preloadTuningParameters:(ASRangeTuningParameters)preloadTuningParameters - interfaceState:(ASInterfaceState)interfaceState; -{ - _ASRangeDebugBarView *viewToUpdate = [self barViewForRangeController:controller]; - - CGRect boundsRect = self.bounds; - CGRect visibleRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, ASRangeTuningParametersZero, scrollableDirections, scrollDirection); - CGRect displayRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, displayTuningParameters, scrollableDirections, scrollDirection); - CGRect preloadRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, preloadTuningParameters, scrollableDirections, scrollDirection); - - // figure out which is biggest and assume that is full bounds - BOOL displayRangeLargerThanPreload = NO; - CGFloat visibleRatio = 0; - CGFloat displayRatio = 0; - CGFloat preloadRatio = 0; - CGFloat leadingDisplayTuningRatio = 0; - CGFloat leadingPreloadTuningRatio = 0; - - if (!((displayTuningParameters.leadingBufferScreenfuls + displayTuningParameters.trailingBufferScreenfuls) == 0)) { - leadingDisplayTuningRatio = displayTuningParameters.leadingBufferScreenfuls / (displayTuningParameters.leadingBufferScreenfuls + displayTuningParameters.trailingBufferScreenfuls); - } - if (!((preloadTuningParameters.leadingBufferScreenfuls + preloadTuningParameters.trailingBufferScreenfuls) == 0)) { - leadingPreloadTuningRatio = preloadTuningParameters.leadingBufferScreenfuls / (preloadTuningParameters.leadingBufferScreenfuls + preloadTuningParameters.trailingBufferScreenfuls); - } - - if (ASScrollDirectionContainsVerticalDirection(scrollDirection)) { - - if (displayRect.size.height >= preloadRect.size.height) { - displayRangeLargerThanPreload = YES; - } else { - displayRangeLargerThanPreload = NO; - } - - if (displayRangeLargerThanPreload) { - visibleRatio = visibleRect.size.height / displayRect.size.height; - displayRatio = 1.0; - preloadRatio = preloadRect.size.height / displayRect.size.height; - } else { - visibleRatio = visibleRect.size.height / preloadRect.size.height; - displayRatio = displayRect.size.height / preloadRect.size.height; - preloadRatio = 1.0; - } - - } else { - - if (displayRect.size.width >= preloadRect.size.width) { - displayRangeLargerThanPreload = YES; - } else { - displayRangeLargerThanPreload = NO; - } - - if (displayRangeLargerThanPreload) { - visibleRatio = visibleRect.size.width / displayRect.size.width; - displayRatio = 1.0; - preloadRatio = preloadRect.size.width / displayRect.size.width; - } else { - visibleRatio = visibleRect.size.width / preloadRect.size.width; - displayRatio = displayRect.size.width / preloadRect.size.width; - preloadRatio = 1.0; - } - } - - [viewToUpdate updateWithVisibleRatio:visibleRatio - displayRatio:displayRatio - leadingDisplayRatio:leadingDisplayTuningRatio - preloadRatio:preloadRatio - leadingpreloadRatio:leadingPreloadTuningRatio - direction:scrollDirection]; - - [self setNeedsLayout]; -} - -- (_ASRangeDebugBarView *)barViewForRangeController:(ASRangeController *)controller -{ - _ASRangeDebugBarView *rangeControllerBarView = nil; - - for (_ASRangeDebugBarView *rangeView in [[_rangeControllerViews reverseObjectEnumerator] allObjects]) { - // remove barView if its rangeController has been deleted - if (!rangeView.rangeController) { - rangeView.destroyOnLayout = YES; - [self setNeedsLayout]; - } - ASInterfaceState interfaceState = [rangeView.rangeController.dataSource interfaceStateForRangeController:rangeView.rangeController]; - if (!(interfaceState & (ASInterfaceStateVisible | ASInterfaceStateDisplay))) { - [self setNeedsLayout]; - } - - if ([rangeView.rangeController isEqual:controller]) { - rangeControllerBarView = rangeView; - } - } - - return rangeControllerBarView; -} - -#define MIN_VISIBLE_INSET 40 -- (void)rangeDebugOverlayWasPanned:(UIPanGestureRecognizer *)recognizer -{ - CGPoint translation = [recognizer translationInView:recognizer.view]; - CGFloat newCenterX = recognizer.view.center.x + translation.x; - CGFloat newCenterY = recognizer.view.center.y + translation.y; - CGSize boundsSize = recognizer.view.bounds.size; - CGSize superBoundsSize = recognizer.view.superview.bounds.size; - CGFloat minAllowableX = -boundsSize.width / 2.0 + MIN_VISIBLE_INSET; - CGFloat maxAllowableX = superBoundsSize.width + boundsSize.width / 2.0 - MIN_VISIBLE_INSET; - - if (newCenterX > maxAllowableX) { - newCenterX = maxAllowableX; - } else if (newCenterX < minAllowableX) { - newCenterX = minAllowableX; - } - - CGFloat minAllowableY = -boundsSize.height / 2.0 + MIN_VISIBLE_INSET; - CGFloat maxAllowableY = superBoundsSize.height + boundsSize.height / 2.0 - MIN_VISIBLE_INSET; - - if (newCenterY > maxAllowableY) { - newCenterY = maxAllowableY; - } else if (newCenterY < minAllowableY) { - newCenterY = minAllowableY; - } - - recognizer.view.center = CGPointMake(newCenterX, newCenterY); - [recognizer setTranslation:CGPointMake(0, 0) inView:recognizer.view]; -} - -@end - -#pragma mark _ASRangeDebugBarView - -@implementation _ASRangeDebugBarView -{ - ASTextNode *_debugText; - ASTextNode *_leftDebugText; - ASTextNode *_rightDebugText; - ASImageNode *_visibleRect; - ASImageNode *_displayRect; - ASImageNode *_preloadRect; - CGFloat _visibleRatio; - CGFloat _displayRatio; - CGFloat _preloadRatio; - CGFloat _leadingDisplayRatio; - CGFloat _leadingpreloadRatio; - ASScrollDirection _scrollDirection; - BOOL _firstLayoutOfRects; -} - -- (instancetype)initWithRangeController:(ASRangeController *)rangeController -{ - self = [super initWithFrame:CGRectZero]; - if (self) { - _firstLayoutOfRects = YES; - _rangeController = rangeController; - _debugText = [self createDebugTextNode]; - _leftDebugText = [self createDebugTextNode]; - _rightDebugText = [self createDebugTextNode]; - _preloadRect = [self createRangeNodeWithColor:[UIColor orangeColor]]; - _displayRect = [self createRangeNodeWithColor:[UIColor yellowColor]]; - _visibleRect = [self createRangeNodeWithColor:[UIColor greenColor]]; - } - - return self; -} - -#define HORIZONTAL_INSET 10 -- (void)layoutSubviews -{ - [super layoutSubviews]; - - CGSize boundsSize = self.bounds.size; - CGFloat subCellHeight = 9.0; - [self setBarDebugLabelsWithSize:subCellHeight]; - [self setBarSubviewOrder]; - - CGRect rect = CGRectIntegral(CGRectMake(0, 0, boundsSize.width, floorf(boundsSize.height / 2.0))); - rect.size = [_debugText layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))].size; - rect.origin.x = (boundsSize.width - rect.size.width) / 2.0; - _debugText.frame = rect; - rect.origin.y += rect.size.height; - - rect.origin.x = 0; - rect.size = CGSizeMake(HORIZONTAL_INSET, boundsSize.height / 2.0); - _leftDebugText.frame = rect; - - rect.origin.x = boundsSize.width - HORIZONTAL_INSET; - _rightDebugText.frame = rect; - - CGFloat visibleDimension = (boundsSize.width - 2 * HORIZONTAL_INSET) * _visibleRatio; - CGFloat displayDimension = (boundsSize.width - 2 * HORIZONTAL_INSET) * _displayRatio; - CGFloat preloadDimension = (boundsSize.width - 2 * HORIZONTAL_INSET) * _preloadRatio; - CGFloat visiblePoint = 0; - CGFloat displayPoint = 0; - CGFloat preloadPoint = 0; - - BOOL displayLargerThanPreload = (_displayRatio == 1.0) ? YES : NO; - - if (ASScrollDirectionContainsLeft(_scrollDirection) || ASScrollDirectionContainsUp(_scrollDirection)) { - - if (displayLargerThanPreload) { - visiblePoint = (displayDimension - visibleDimension) * _leadingDisplayRatio; - preloadPoint = visiblePoint - (preloadDimension - visibleDimension) * _leadingpreloadRatio; - } else { - visiblePoint = (preloadDimension - visibleDimension) * _leadingpreloadRatio; - displayPoint = visiblePoint - (displayDimension - visibleDimension) * _leadingDisplayRatio; - } - } else if (ASScrollDirectionContainsRight(_scrollDirection) || ASScrollDirectionContainsDown(_scrollDirection)) { - - if (displayLargerThanPreload) { - visiblePoint = (displayDimension - visibleDimension) * (1 - _leadingDisplayRatio); - preloadPoint = visiblePoint - (preloadDimension - visibleDimension) * (1 - _leadingpreloadRatio); - } else { - visiblePoint = (preloadDimension - visibleDimension) * (1 - _leadingpreloadRatio); - displayPoint = visiblePoint - (displayDimension - visibleDimension) * (1 - _leadingDisplayRatio); - } - } - - BOOL animate = !_firstLayoutOfRects; - [UIView animateWithDuration:animate ? 0.3 : 0.0 delay:0.0 options:UIViewAnimationOptionLayoutSubviews animations:^{ - _visibleRect.frame = CGRectMake(HORIZONTAL_INSET + visiblePoint, rect.origin.y, visibleDimension, subCellHeight); - _displayRect.frame = CGRectMake(HORIZONTAL_INSET + displayPoint, rect.origin.y, displayDimension, subCellHeight); - _preloadRect.frame = CGRectMake(HORIZONTAL_INSET + preloadPoint, rect.origin.y, preloadDimension, subCellHeight); - } completion:^(BOOL finished) {}]; - - if (!animate) { - _visibleRect.alpha = _displayRect.alpha = _preloadRect.alpha = 0; - [UIView animateWithDuration:0.3 animations:^{ - _visibleRect.alpha = _displayRect.alpha = _preloadRect.alpha = 1; - }]; - } - - _firstLayoutOfRects = NO; -} - -- (void)updateWithVisibleRatio:(CGFloat)visibleRatio - displayRatio:(CGFloat)displayRatio - leadingDisplayRatio:(CGFloat)leadingDisplayRatio - preloadRatio:(CGFloat)preloadRatio - leadingpreloadRatio:(CGFloat)leadingpreloadRatio - direction:(ASScrollDirection)scrollDirection -{ - _visibleRatio = visibleRatio; - _displayRatio = displayRatio; - _leadingDisplayRatio = leadingDisplayRatio; - _preloadRatio = preloadRatio; - _leadingpreloadRatio = leadingpreloadRatio; - _scrollDirection = scrollDirection; - - [self setNeedsLayout]; -} - -- (void)setBarSubviewOrder -{ - if (_preloadRatio == 1.0) { - [self sendSubviewToBack:_preloadRect.view]; - } else { - [self sendSubviewToBack:_displayRect.view]; - } - - [self bringSubviewToFront:_visibleRect.view]; -} - -- (void)setBarDebugLabelsWithSize:(CGFloat)size -{ - if (!_debugString) { - _debugString = [[_rangeController dataSource] nameForRangeControllerDataSource]; - } - if (_debugString) { - _debugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:_debugString withSize:size]; - } - - if (ASScrollDirectionContainsVerticalDirection(_scrollDirection)) { - _leftDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"▲" withSize:size]; - _rightDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"▼" withSize:size]; - } else if (ASScrollDirectionContainsHorizontalDirection(_scrollDirection)) { - _leftDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"◀︎" withSize:size]; - _rightDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"▶︎" withSize:size]; - } - - _leftDebugText.hidden = (_scrollDirection != ASScrollDirectionLeft && _scrollDirection != ASScrollDirectionUp); - _rightDebugText.hidden = (_scrollDirection != ASScrollDirectionRight && _scrollDirection != ASScrollDirectionDown); -} - -- (ASTextNode *)createDebugTextNode -{ - ASTextNode *label = [[ASTextNode alloc] init]; - [self addSubnode:label]; - return label; -} - -#define RANGE_BAR_CORNER_RADIUS 3 -#define RANGE_BAR_BORDER_WIDTH 1 -- (ASImageNode *)createRangeNodeWithColor:(UIColor *)color -{ - ASImageNode *rangeBarImageNode = [[ASImageNode alloc] init]; - rangeBarImageNode.image = [UIImage as_resizableRoundedImageWithCornerRadius:RANGE_BAR_CORNER_RADIUS - cornerColor:[UIColor clearColor] - fillColor:[color colorWithAlphaComponent:0.5] - borderColor:[[UIColor blackColor] colorWithAlphaComponent:0.9] - borderWidth:RANGE_BAR_BORDER_WIDTH - roundedCorners:UIRectCornerAllCorners - scale:[[UIScreen mainScreen] scale]]; - [self addSubnode:rangeBarImageNode]; - - return rangeBarImageNode; -} - -+ (NSAttributedString *)whiteAttributedStringFromString:(NSString *)string withSize:(CGFloat)size NS_RETURNS_RETAINED -{ - NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor whiteColor], - NSFontAttributeName : [UIFont systemFontOfSize:size]}; - return [[NSAttributedString alloc] initWithString:string attributes:attributes]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+IGListKitMethods.h b/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+IGListKitMethods.h deleted file mode 100644 index aa0de42510..0000000000 --- a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+IGListKitMethods.h +++ /dev/null @@ -1,64 +0,0 @@ -// -// AsyncDisplayKit+IGListKitMethods.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 - -#if AS_IG_LIST_KIT - -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * If you are using AsyncDisplayKit with IGListKit, you should use - * these methods to provide implementations for methods like - * -cellForItemAtIndex: that don't apply when used with AsyncDisplayKit. - * - * Your section controllers should also conform to @c ASSectionController and your - * supplementary view sources should conform to @c ASSupplementaryNodeSource. - */ - -AS_SUBCLASSING_RESTRICTED -@interface ASIGListSectionControllerMethods : NSObject - -/** - * Call this for your section controller's @c cellForItemAtIndex: method. - */ -+ (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController; - -/** - * Call this for your section controller's @c sizeForItemAtIndex: method. - */ -+ (CGSize)sizeForItemAtIndex:(NSInteger)index; - -@end - -AS_SUBCLASSING_RESTRICTED -@interface ASIGListSupplementaryViewSourceMethods : NSObject - -/** - * Call this for your supplementary source's @c viewForSupplementaryElementOfKind:atIndex: method. - */ -+ (__kindof UICollectionReusableView *)viewForSupplementaryElementOfKind:(NSString *)elementKind - atIndex:(NSInteger)index - sectionController:(IGListSectionController *)sectionController; - -/** - * Call this for your supplementary source's @c sizeForSupplementaryViewOfKind:atIndex: method. - */ -+ (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind atIndex:(NSInteger)index; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+IGListKitMethods.mm b/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+IGListKitMethods.mm deleted file mode 100644 index 50d7a219f8..0000000000 --- a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+IGListKitMethods.mm +++ /dev/null @@ -1,53 +0,0 @@ -// -// AsyncDisplayKit+IGListKitMethods.m -// 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 - -#if AS_IG_LIST_KIT - -#import "AsyncDisplayKit+IGListKitMethods.h" -#import -#import -#import - - -@implementation ASIGListSectionControllerMethods - -+ (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController -{ - // Cast to id for backwards-compatibility until 3.0.0 is officially released – IGListSectionType was removed. This is safe. - return [sectionController.collectionContext dequeueReusableCellOfClass:[_ASCollectionViewCell class] forSectionController:(id)sectionController atIndex:index]; -} - -+ (CGSize)sizeForItemAtIndex:(NSInteger)index -{ - ASDisplayNodeFailAssert(@"Did not expect %@ to be called.", NSStringFromSelector(_cmd)); - return CGSizeZero; -} - -@end - -@implementation ASIGListSupplementaryViewSourceMethods - -+ (__kindof UICollectionReusableView *)viewForSupplementaryElementOfKind:(NSString *)elementKind - atIndex:(NSInteger)index - sectionController:(IGListSectionController *)sectionController -{ - return [sectionController.collectionContext dequeueReusableSupplementaryViewOfKind:elementKind forSectionController:(id)sectionController class:[_ASCollectionReusableView class] atIndex:index]; -} - -+ (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind atIndex:(NSInteger)index -{ - ASDisplayNodeFailAssert(@"Did not expect %@ to be called.", NSStringFromSelector(_cmd)); - return CGSizeZero; -} - -@end - -#endif // AS_IG_LIST_KIT diff --git a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+Tips.h b/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+Tips.h deleted file mode 100644 index 6232746a57..0000000000 --- a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+Tips.h +++ /dev/null @@ -1,43 +0,0 @@ -// -// AsyncDisplayKit+Tips.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 -#import - -NS_ASSUME_NONNULL_BEGIN - -typedef void(^ASTipDisplayBlock)(ASDisplayNode *node, NSString *message); - -/** - * The methods added to ASDisplayNode to control the tips system. - * - * To enable tips, define AS_ENABLE_TIPS=1 (e.g. modify ASBaseDefines.h). - */ -@interface ASDisplayNode (Tips) - -/** - * Whether this class should have tips active. Default YES. - * - * NOTE: This property is for _disabling_ tips on a per-class basis, - * if they become annoying or have false-positives. The tips system - * is completely disabled unless you define AS_ENABLE_TIPS=1. - */ -@property (class) BOOL enableTips; - -/** - * A block to be run on the main thread to show text when a tip is tapped. - * - * If nil, the default, the message is just logged to the console with the - * ancestry of the node. - */ -@property (class, nonatomic, null_resettable) ASTipDisplayBlock tipDisplayBlock; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+Tips.mm b/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+Tips.mm deleted file mode 100644 index 792349351f..0000000000 --- a/submodules/AsyncDisplayKit/Source/AsyncDisplayKit+Tips.mm +++ /dev/null @@ -1,48 +0,0 @@ -// -// AsyncDisplayKit+Tips.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+Tips.h" -#import - -@implementation ASDisplayNode (Tips) - -static char ASDisplayNodeEnableTipsKey; -static ASTipDisplayBlock _Nullable __tipDisplayBlock; - -/** - * Use associated objects with NSNumbers. This is a debug property - simplicity is king. - */ -+ (void)setEnableTips:(BOOL)enableTips -{ - objc_setAssociatedObject(self, &ASDisplayNodeEnableTipsKey, @(enableTips), OBJC_ASSOCIATION_COPY); -} - -+ (BOOL)enableTips -{ - NSNumber *result = objc_getAssociatedObject(self, &ASDisplayNodeEnableTipsKey); - if (result == nil) { - return YES; - } - return result.boolValue; -} - - -+ (void)setTipDisplayBlock:(ASTipDisplayBlock)tipDisplayBlock -{ - __tipDisplayBlock = tipDisplayBlock; -} - -+ (ASTipDisplayBlock)tipDisplayBlock -{ - return __tipDisplayBlock ?: ^(ASDisplayNode *node, NSString *string) { - NSLog(@"%@. Node ancestry: %@", string, node.ancestryDescription); - }; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/IGListAdapter+AsyncDisplayKit.h b/submodules/AsyncDisplayKit/Source/IGListAdapter+AsyncDisplayKit.h deleted file mode 100644 index 76b53c1933..0000000000 --- a/submodules/AsyncDisplayKit/Source/IGListAdapter+AsyncDisplayKit.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// IGListAdapter+AsyncDisplayKit.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 - -#if AS_IG_LIST_KIT - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASCollectionNode; - -@interface IGListAdapter (AsyncDisplayKit) - -/** - * Connect this list adapter to the given collection node. - * - * @param collectionNode The collection node to drive with this list adapter. - * - * @note This method may only be called once per list adapter, - * and it must be called on the main thread. -[UIViewController init] - * is a good place to call it. This method does not retain the collection node. - */ -- (void)setASDKCollectionNode:(ASCollectionNode *)collectionNode; - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_IG_LIST_KIT diff --git a/submodules/AsyncDisplayKit/Source/IGListAdapter+AsyncDisplayKit.mm b/submodules/AsyncDisplayKit/Source/IGListAdapter+AsyncDisplayKit.mm deleted file mode 100644 index c3e81d3e33..0000000000 --- a/submodules/AsyncDisplayKit/Source/IGListAdapter+AsyncDisplayKit.mm +++ /dev/null @@ -1,53 +0,0 @@ -// -// IGListAdapter+AsyncDisplayKit.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 - -#if AS_IG_LIST_KIT - -#import -#import -#import -#import - -@implementation IGListAdapter (AsyncDisplayKit) - -- (void)setASDKCollectionNode:(ASCollectionNode *)collectionNode -{ - ASDisplayNodeAssertMainThread(); - - // Attempt to retrieve previous data source. - ASIGListAdapterBasedDataSource *dataSource = objc_getAssociatedObject(self, _cmd); - // Bomb if we already made one. - if (dataSource != nil) { - ASDisplayNodeFailAssert(@"Attempt to call %@ multiple times on the same list adapter. Not currently allowed!", NSStringFromSelector(_cmd)); - return; - } - - // Make a data source and retain it. - dataSource = [[ASIGListAdapterBasedDataSource alloc] initWithListAdapter:self collectionDelegate:collectionNode.delegate]; - objc_setAssociatedObject(self, _cmd, dataSource, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - - // Attach the data source to the collection node. - collectionNode.dataSource = dataSource; - collectionNode.delegate = dataSource; - __weak IGListAdapter *weakSelf = self; - [collectionNode onDidLoad:^(__kindof ASCollectionNode * _Nonnull collectionNode) { -#if IG_LIST_COLLECTION_VIEW - // We manually set the superclass of ASCollectionView to IGListCollectionView at runtime if needed. - weakSelf.collectionView = (IGListCollectionView *)collectionNode.view; -#else - weakSelf.collectionView = collectionNode.view; -#endif - }]; -} - -@end - -#endif // AS_IG_LIST_KIT diff --git a/submodules/AsyncDisplayKit/Source/ASAbstractLayoutController+FrameworkPrivate.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASAbstractLayoutController+FrameworkPrivate.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASAbstractLayoutController+FrameworkPrivate.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASAbstractLayoutController+FrameworkPrivate.h diff --git a/submodules/AsyncDisplayKit/Source/ASAbstractLayoutController.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASAbstractLayoutController.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASAbstractLayoutController.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASAbstractLayoutController.h diff --git a/submodules/AsyncDisplayKit/Source/ASAbstractLayoutController.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASAbstractLayoutController.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASAbstractLayoutController.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASAbstractLayoutController.mm index 4fe2ad091b..9d6b61f689 100644 --- a/submodules/AsyncDisplayKit/Source/ASAbstractLayoutController.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASAbstractLayoutController.mm @@ -7,8 +7,8 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASAbstractLayoutController.h" -#import "Private/ASAbstractLayoutController+FrameworkPrivate.h" +#import +#import #import ASRangeTuningParameters const ASRangeTuningParametersZero = {}; diff --git a/submodules/AsyncDisplayKit/Source/ASAssert.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASAssert.mm similarity index 97% rename from submodules/AsyncDisplayKit/Source/ASAssert.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASAssert.mm index c63d92d822..6a78b06eaf 100644 --- a/submodules/AsyncDisplayKit/Source/ASAssert.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASAssert.mm @@ -6,7 +6,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASAssert.h" +#import #import #if AS_TLS_AVAILABLE diff --git a/submodules/AsyncDisplayKit/Source/ASCGImageBuffer.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASCGImageBuffer.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASCGImageBuffer.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASCGImageBuffer.h diff --git a/submodules/AsyncDisplayKit/Source/ASCGImageBuffer.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASCGImageBuffer.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASCGImageBuffer.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASCGImageBuffer.mm index 6f05300e23..de397471e0 100644 --- a/submodules/AsyncDisplayKit/Source/ASCGImageBuffer.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASCGImageBuffer.mm @@ -6,7 +6,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASCGImageBuffer.h" +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASCollections.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASCollections.h old mode 100644 new mode 100755 similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASCollections.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASCollections.h diff --git a/submodules/AsyncDisplayKit/Source/ASCollections.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASCollections.mm old mode 100644 new mode 100755 similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASCollections.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASCollections.mm diff --git a/submodules/AsyncDisplayKit/Source/ASConfiguration.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASConfiguration.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASConfiguration.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASConfiguration.h diff --git a/submodules/AsyncDisplayKit/Source/ASConfiguration.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASConfiguration.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASConfiguration.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASConfiguration.mm diff --git a/submodules/AsyncDisplayKit/Source/ASConfigurationDelegate.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASConfigurationDelegate.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASConfigurationDelegate.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASConfigurationDelegate.h diff --git a/submodules/AsyncDisplayKit/Source/ASConfigurationInternal.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASConfigurationInternal.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASConfigurationInternal.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASConfigurationInternal.h diff --git a/submodules/AsyncDisplayKit/Source/ASConfigurationInternal.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASConfigurationInternal.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASConfigurationInternal.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASConfigurationInternal.mm index 485fef0c58..2fb190103a 100644 --- a/submodules/AsyncDisplayKit/Source/ASConfigurationInternal.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASConfigurationInternal.mm @@ -6,7 +6,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASConfigurationInternal.h" +#import #import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASContextTransitioning.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASContextTransitioning.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASContextTransitioning.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASContextTransitioning.h diff --git a/submodules/AsyncDisplayKit/Source/ASDelegateProxy.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDelegateProxy.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDelegateProxy.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDelegateProxy.h diff --git a/submodules/AsyncDisplayKit/Source/ASDelegateProxy.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDelegateProxy.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDelegateProxy.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDelegateProxy.mm diff --git a/submodules/AsyncDisplayKit/Source/ASDimension.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDimension.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDimension.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDimension.mm diff --git a/submodules/AsyncDisplayKit/Source/ASDimensionInternal.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDimensionInternal.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDimensionInternal.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDimensionInternal.mm diff --git a/submodules/AsyncDisplayKit/Source/ASDispatch.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDispatch.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDispatch.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDispatch.h diff --git a/submodules/AsyncDisplayKit/Source/ASDispatch.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDispatch.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASDispatch.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDispatch.mm index edc2feba46..769a9185d4 100644 --- a/submodules/AsyncDisplayKit/Source/ASDispatch.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDispatch.mm @@ -6,7 +6,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASDispatch.h" +#import #import diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Ancestry.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+Ancestry.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+Ancestry.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+Ancestry.h diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Ancestry.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+Ancestry.mm similarity index 97% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+Ancestry.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+Ancestry.mm index 7003064c70..ea1376ed54 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Ancestry.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+Ancestry.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASDisplayNode+Ancestry.h" +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+AsyncDisplay.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+AsyncDisplay.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+AsyncDisplay.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+AsyncDisplay.mm index ff6e01da58..4efd2b0a79 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+AsyncDisplay.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+AsyncDisplay.mm @@ -14,7 +14,7 @@ #import #import #import -#import "Private/ASInternalHelpers.h" +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Beta.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+Beta.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+Beta.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+Beta.h diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Convenience.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+Convenience.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+Convenience.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+Convenience.h diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Convenience.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+Convenience.mm similarity index 89% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+Convenience.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+Convenience.mm index 72cc4c23f1..9673f93678 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Convenience.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+Convenience.mm @@ -7,12 +7,12 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASDisplayNode+Convenience.h" +#import #import #import -#import "Private/ASResponderChainEnumerator.h" +#import @implementation ASDisplayNode (Convenience) diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+DebugTiming.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+DebugTiming.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+DebugTiming.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+DebugTiming.h diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+DebugTiming.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+DebugTiming.mm similarity index 97% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+DebugTiming.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+DebugTiming.mm index 7433c2f9ac..d9311a10d9 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+DebugTiming.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+DebugTiming.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASDisplayNode+DebugTiming.h" +#import #import @implementation ASDisplayNode (DebugTiming) diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Deprecated.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+Deprecated.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+Deprecated.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+Deprecated.h diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+FrameworkPrivate.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+FrameworkPrivate.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Layout.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+Layout.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+Layout.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+Layout.mm index 041acc4929..6145457a42 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Layout.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+Layout.mm @@ -8,18 +8,17 @@ // #import -#import #import -#import "Private/ASDisplayNodeInternal.h" +#import #import #import -#import "Private/ASInternalHelpers.h" +#import #import #import #import #import -#import #import +#import using AS::MutexLocker; @@ -75,7 +74,7 @@ using AS::MutexLocker; - (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize { - ASScopedLockSelfOrToRoot(); + ASLockScopeSelf(); // If one or multiple layout transitions are in flight it still can happen that layout information is requested // on other threads. As the pending and calculated layout to be updated in the layout transition in here just a @@ -316,7 +315,7 @@ ASLayoutElementStyleExtensibilityForwarding - (void)_u_measureNodeWithBoundsIfNecessary:(CGRect)bounds { // ASAssertUnlocked(__instanceLock__); - ASScopedLockSelfOrToRoot(); + ASLockScopeSelf(); // Check if we are a subnode in a layout transition. // In this case no measurement is needed as it's part of the layout transition @@ -640,7 +639,7 @@ ASLayoutElementStyleExtensibilityForwarding NSUInteger newLayoutVersion = _layoutVersion; ASLayout *newLayout; { - ASScopedLockSelfOrToRoot(); + ASLockScopeSelf(); ASLayoutElementContext *ctx = [[ASLayoutElementContext alloc] init]; ctx.transitionID = transitionID; diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+LayoutSpec.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+LayoutSpec.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+LayoutSpec.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+LayoutSpec.h diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+LayoutSpec.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+LayoutSpec.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+LayoutSpec.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+LayoutSpec.mm index 9a156ff61a..d79073ffdd 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+LayoutSpec.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+LayoutSpec.mm @@ -9,12 +9,12 @@ #import #import -#import "Private/_ASScopeTimer.h" +#import #import #import #import #import -#import "Private/ASLayoutSpecPrivate.h" +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+Subclasses.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+Subclasses.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+Subclasses.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+Subclasses.h diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+UIViewBridge.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+UIViewBridge.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+UIViewBridge.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+UIViewBridge.mm index 7868d87edc..a6d60a7748 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode+UIViewBridge.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode+UIViewBridge.mm @@ -7,9 +7,9 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "_ASCoreAnimationExtras.h" +#import #import -#import "Private/ASInternalHelpers.h" +#import #import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode.mm index 5d072338cb..a767a77a0d 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNode.mm @@ -7,14 +7,12 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASDisplayNodeInternal.h" +#import #import #import #import -#import #import -#import #import #include @@ -29,16 +27,15 @@ #import #import #import -#import #import #import #import #import #import -#import "Private/ASInternalHelpers.h" +#import #import #import -#import "Private/ASLayoutSpecPrivate.h" +#import #import #import #import @@ -46,8 +43,7 @@ #import #import #import -#import "Private/ASResponderChainEnumerator.h" -#import +#import // Conditionally time these scopes to our debug ivars (only exist in debug/profile builds) #if TIME_DISPLAYNODE_OPS @@ -1578,8 +1574,9 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) }); } -- (void)_setClipCornerLayersVisible:(BOOL)visible +- (void)_setClipCornerLayersVisible:(BOOL)__unused visible { + /* ASPerformBlockOnMainThread(^{ ASDisplayNodeAssertMainThread(); if (visible) { @@ -1603,6 +1600,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) } } }); + */ } - (void)updateCornerRoundingWithType:(ASCornerRoundingType)newRoundingType cornerRadius:(CGFloat)newCornerRadius diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNodeExtras.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNodeExtras.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDisplayNodeExtras.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNodeExtras.h diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNodeExtras.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNodeExtras.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDisplayNodeExtras.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNodeExtras.mm diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNodeInternal.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNodeInternal.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDisplayNodeInternal.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNodeInternal.h diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNodeLayout.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNodeLayout.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDisplayNodeLayout.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASDisplayNodeLayout.h diff --git a/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASEditableTextNode.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASEditableTextNode.mm index 0a34894c0c..21f94b4493 100644 --- a/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASEditableTextNode.mm @@ -15,7 +15,7 @@ #import #import #import -#import "TextKit/ASTextNodeWordKerner.h" +#import #import @implementation ASEditableTextNodeTargetForAction diff --git a/submodules/AsyncDisplayKit/Source/ASElementMap.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASElementMap.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASElementMap.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASElementMap.h diff --git a/submodules/AsyncDisplayKit/Source/ASElementMap.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASElementMap.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASElementMap.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASElementMap.mm diff --git a/submodules/AsyncDisplayKit/Source/ASEqualityHelpers.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASEqualityHelpers.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASEqualityHelpers.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASEqualityHelpers.h diff --git a/submodules/AsyncDisplayKit/Source/ASEventLog.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASEventLog.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASEventLog.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASEventLog.h diff --git a/submodules/AsyncDisplayKit/Source/ASEventLog.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASEventLog.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASEventLog.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASEventLog.mm diff --git a/submodules/AsyncDisplayKit/Source/ASExperimentalFeatures.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASExperimentalFeatures.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASExperimentalFeatures.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASExperimentalFeatures.h diff --git a/submodules/AsyncDisplayKit/Source/ASExperimentalFeatures.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASExperimentalFeatures.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASExperimentalFeatures.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASExperimentalFeatures.mm index 653db2c370..d03248462f 100644 --- a/submodules/AsyncDisplayKit/Source/ASExperimentalFeatures.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASExperimentalFeatures.mm @@ -7,7 +7,6 @@ // #import - #import NSArray *ASExperimentalFeaturesGetNames(ASExperimentalFeatures flags) diff --git a/submodules/AsyncDisplayKit/Source/ASGraphicsContext.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASGraphicsContext.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASGraphicsContext.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASGraphicsContext.h diff --git a/submodules/AsyncDisplayKit/Source/ASGraphicsContext.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASGraphicsContext.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASGraphicsContext.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASGraphicsContext.mm index 1d8cf33cfd..b950613d0d 100644 --- a/submodules/AsyncDisplayKit/Source/ASGraphicsContext.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASGraphicsContext.mm @@ -10,7 +10,7 @@ #import #import #import -#import "Private/ASInternalHelpers.h" +#import #import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASHashing.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASHashing.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASHashing.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASHashing.h diff --git a/submodules/AsyncDisplayKit/Source/ASHashing.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASHashing.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASHashing.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASHashing.mm diff --git a/submodules/AsyncDisplayKit/Source/ASHighlightOverlayLayer.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASHighlightOverlayLayer.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASHighlightOverlayLayer.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASHighlightOverlayLayer.h diff --git a/submodules/AsyncDisplayKit/Source/ASHighlightOverlayLayer.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASHighlightOverlayLayer.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASHighlightOverlayLayer.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASHighlightOverlayLayer.mm index a8fd8c6233..03cf468165 100644 --- a/submodules/AsyncDisplayKit/Source/ASHighlightOverlayLayer.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASHighlightOverlayLayer.mm @@ -12,7 +12,7 @@ #import #import -#import "Private/ASInternalHelpers.h" +#import static const CGFloat kCornerRadius = 2.5; static const UIEdgeInsets padding = {2, 4, 1.5, 4}; diff --git a/submodules/AsyncDisplayKit/Source/ASImageContainerProtocolCategories.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASImageContainerProtocolCategories.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASImageContainerProtocolCategories.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASImageContainerProtocolCategories.h diff --git a/submodules/AsyncDisplayKit/Source/ASImageContainerProtocolCategories.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASImageContainerProtocolCategories.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASImageContainerProtocolCategories.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASImageContainerProtocolCategories.mm diff --git a/submodules/AsyncDisplayKit/Source/ASImageProtocols.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASImageProtocols.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASImageProtocols.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASImageProtocols.h diff --git a/submodules/AsyncDisplayKit/Source/ASInsetLayoutSpec.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASInsetLayoutSpec.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASInsetLayoutSpec.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASInsetLayoutSpec.h diff --git a/submodules/AsyncDisplayKit/Source/ASInsetLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASInsetLayoutSpec.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASInsetLayoutSpec.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASInsetLayoutSpec.mm index 3fa325b580..450480f1c4 100644 --- a/submodules/AsyncDisplayKit/Source/ASInsetLayoutSpec.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASInsetLayoutSpec.mm @@ -12,7 +12,7 @@ #import #import -#import "Private/ASInternalHelpers.h" +#import @interface ASInsetLayoutSpec () { diff --git a/submodules/AsyncDisplayKit/Source/ASIntegerMap.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASIntegerMap.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASIntegerMap.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASIntegerMap.h diff --git a/submodules/AsyncDisplayKit/Source/ASIntegerMap.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASIntegerMap.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASIntegerMap.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASIntegerMap.mm index b07a7a1806..2c3e03e413 100644 --- a/submodules/AsyncDisplayKit/Source/ASIntegerMap.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASIntegerMap.mm @@ -6,7 +6,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASIntegerMap.h" +#import #import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASInternalHelpers.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASInternalHelpers.h similarity index 86% rename from submodules/AsyncDisplayKit/Source/ASInternalHelpers.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASInternalHelpers.h index 22391b1b2f..780566f395 100644 --- a/submodules/AsyncDisplayKit/Source/ASInternalHelpers.h +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASInternalHelpers.h @@ -13,7 +13,6 @@ #import #import -#import NS_ASSUME_NONNULL_BEGIN @@ -97,22 +96,6 @@ ASDISPLAYNODE_INLINE UIEdgeInsets ASConcatInsets(UIEdgeInsets insetsA, UIEdgeIns return insetsA; } -ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASImageDownloaderPriority ASImageDownloaderPriorityWithInterfaceState(ASInterfaceState interfaceState) { - if (ASInterfaceStateIncludesVisible(interfaceState)) { - return ASImageDownloaderPriorityVisible; - } - - if (ASInterfaceStateIncludesDisplay(interfaceState)) { - return ASImageDownloaderPriorityImminent; - } - - if (ASInterfaceStateIncludesPreload(interfaceState)) { - return ASImageDownloaderPriorityPreload; - } - - return ASImageDownloaderPriorityPreload; -} - @interface NSIndexPath (ASInverseComparison) - (NSComparisonResult)asdk_inverseCompare:(NSIndexPath *)otherIndexPath; @end diff --git a/submodules/AsyncDisplayKit/Source/ASInternalHelpers.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASInternalHelpers.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASInternalHelpers.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASInternalHelpers.mm index d3dd743b7c..a9926ccca4 100644 --- a/submodules/AsyncDisplayKit/Source/ASInternalHelpers.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASInternalHelpers.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASInternalHelpers.h" +#import #import diff --git a/submodules/AsyncDisplayKit/Source/ASLayout.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayout.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASLayout.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayout.h diff --git a/submodules/AsyncDisplayKit/Source/ASLayout.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayout.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASLayout.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayout.mm index 5a2e8a916b..bc2c46de57 100644 --- a/submodules/AsyncDisplayKit/Source/ASLayout.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayout.mm @@ -12,14 +12,14 @@ #import #import -#import #import #import #import #import -#import "Private/ASInternalHelpers.h" +#import #import +#import NSString *const ASThreadDictMaxConstraintSizeKey = @"kASThreadDictMaxConstraintSizeKey"; diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutController.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutController.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASLayoutController.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutController.h diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutElement.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutElement.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASLayoutElement.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutElement.mm index 434d9800f3..887f609e99 100644 --- a/submodules/AsyncDisplayKit/Source/ASLayoutElement.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutElement.mm @@ -13,7 +13,7 @@ #import #import #import -#import "Private/ASInternalHelpers.h" +#import #import #include diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutElementStylePrivate.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutElementStylePrivate.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASLayoutElementStylePrivate.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutElementStylePrivate.h diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutManager.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutManager.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASLayoutManager.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutManager.h diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutManager.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutManager.mm similarity index 96% rename from submodules/AsyncDisplayKit/Source/ASLayoutManager.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutManager.mm index 9eca28e489..fbb3b49ea4 100644 --- a/submodules/AsyncDisplayKit/Source/ASLayoutManager.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutManager.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASLayoutManager.h" +#import @implementation ASLayoutManager diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutRangeType.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutRangeType.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASLayoutRangeType.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutRangeType.h diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutSpec+Subclasses.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutSpec+Subclasses.h diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutSpec+Subclasses.mm similarity index 97% rename from submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutSpec+Subclasses.mm index 3ab1e7bfd2..17ff53e6c5 100644 --- a/submodules/AsyncDisplayKit/Source/ASLayoutSpec+Subclasses.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutSpec+Subclasses.mm @@ -10,7 +10,7 @@ #import #import -#import "Private/ASLayoutSpecPrivate.h" +#import #pragma mark - ASNullLayoutSpec diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutSpec.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutSpec.h similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASLayoutSpec.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutSpec.h index c7aa01c386..545f95e2d5 100644 --- a/submodules/AsyncDisplayKit/Source/ASLayoutSpec.h +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutSpec.h @@ -8,7 +8,6 @@ // #import -#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutSpec.mm similarity index 93% rename from submodules/AsyncDisplayKit/Source/ASLayoutSpec.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutSpec.mm index fda39c118f..7cf5e198ba 100644 --- a/submodules/AsyncDisplayKit/Source/ASLayoutSpec.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutSpec.mm @@ -7,8 +7,8 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASLayoutSpec.h" -#import "Private/ASLayoutSpecPrivate.h" +#import +#import #import @@ -16,7 +16,7 @@ #import #import #import -#import "Private/ASInternalHelpers.h" +#import #import #import @@ -317,17 +317,7 @@ ASSynthesizeLockingMethodsWithMutex(__instanceLock__) + (NSString *)asciiArtStringForChildren:(NSArray *)children parentName:(NSString *)parentName direction:(ASStackLayoutDirection)direction { - NSMutableArray *childStrings = [NSMutableArray array]; - for (id layoutChild in children) { - NSString *childString = [layoutChild asciiArtString]; - if (childString) { - [childStrings addObject:childString]; - } - } - if (direction == ASStackLayoutDirectionHorizontal) { - return [ASAsciiArtBoxCreator horizontalBoxStringForChildren:childStrings parent:parentName]; - } - return [ASAsciiArtBoxCreator verticalBoxStringForChildren:childStrings parent:parentName]; + return @""; } + (NSString *)asciiArtStringForChildren:(NSArray *)children parentName:(NSString *)parentName diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutSpecPrivate.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutSpecPrivate.h similarity index 95% rename from submodules/AsyncDisplayKit/Source/ASLayoutSpecPrivate.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutSpecPrivate.h index 2a143dc674..930232096c 100644 --- a/submodules/AsyncDisplayKit/Source/ASLayoutSpecPrivate.h +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutSpecPrivate.h @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "Private/ASInternalHelpers.h" +#import #import #if DEBUG diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutSpecUtilities.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutSpecUtilities.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASLayoutSpecUtilities.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutSpecUtilities.h diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutTransition.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutTransition.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASLayoutTransition.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutTransition.h diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutTransition.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutTransition.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASLayoutTransition.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutTransition.mm index 5366f9582c..2fea2b8ad0 100644 --- a/submodules/AsyncDisplayKit/Source/ASLayoutTransition.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLayoutTransition.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASLayoutTransition.h" +#import #import diff --git a/submodules/AsyncDisplayKit/Source/ASLog.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLog.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASLog.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLog.h diff --git a/submodules/AsyncDisplayKit/Source/ASLog.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLog.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASLog.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASLog.mm diff --git a/submodules/AsyncDisplayKit/Source/ASMainSerialQueue.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASMainSerialQueue.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASMainSerialQueue.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASMainSerialQueue.h diff --git a/submodules/AsyncDisplayKit/Source/ASMainSerialQueue.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASMainSerialQueue.mm similarity index 96% rename from submodules/AsyncDisplayKit/Source/ASMainSerialQueue.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASMainSerialQueue.mm index 8dc6218ea3..06cb7c3b9b 100644 --- a/submodules/AsyncDisplayKit/Source/ASMainSerialQueue.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASMainSerialQueue.mm @@ -10,7 +10,7 @@ #import #import -#import "Private/ASInternalHelpers.h" +#import @interface ASMainSerialQueue () { diff --git a/submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASMainThreadDeallocation.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASMainThreadDeallocation.h diff --git a/submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASMainThreadDeallocation.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASMainThreadDeallocation.mm index 66f6a481a1..1cf90ada05 100644 --- a/submodules/AsyncDisplayKit/Source/ASMainThreadDeallocation.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASMainThreadDeallocation.mm @@ -10,7 +10,7 @@ #import #import -#import "Private/ASInternalHelpers.h" +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASMutableAttributedStringBuilder.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASMutableAttributedStringBuilder.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASMutableAttributedStringBuilder.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASMutableAttributedStringBuilder.h diff --git a/submodules/AsyncDisplayKit/Source/ASMutableAttributedStringBuilder.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASMutableAttributedStringBuilder.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASMutableAttributedStringBuilder.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASMutableAttributedStringBuilder.mm diff --git a/submodules/AsyncDisplayKit/Source/ASMutableElementMap.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASMutableElementMap.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASMutableElementMap.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASMutableElementMap.h diff --git a/submodules/AsyncDisplayKit/Source/ASMutableElementMap.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASMutableElementMap.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASMutableElementMap.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASMutableElementMap.mm diff --git a/submodules/AsyncDisplayKit/Source/ASNodeController+Beta.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASNodeController+Beta.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASNodeController+Beta.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASNodeController+Beta.h diff --git a/submodules/AsyncDisplayKit/Source/ASNodeController+Beta.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASNodeController+Beta.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASNodeController+Beta.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASNodeController+Beta.mm diff --git a/submodules/AsyncDisplayKit/Source/ASObjectDescriptionHelpers.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASObjectDescriptionHelpers.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASObjectDescriptionHelpers.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASObjectDescriptionHelpers.mm index 2b5d94492c..cbd6be0963 100644 --- a/submodules/AsyncDisplayKit/Source/ASObjectDescriptionHelpers.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASObjectDescriptionHelpers.mm @@ -11,7 +11,7 @@ #import -#import "NSIndexSet+ASHelpers.h" +#import NSString *ASGetDescriptionValueString(id object) { diff --git a/submodules/AsyncDisplayKit/Source/ASOverlayLayoutSpec.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASOverlayLayoutSpec.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASOverlayLayoutSpec.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASOverlayLayoutSpec.h diff --git a/submodules/AsyncDisplayKit/Source/ASOverlayLayoutSpec.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASOverlayLayoutSpec.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASOverlayLayoutSpec.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASOverlayLayoutSpec.mm diff --git a/submodules/AsyncDisplayKit/Source/ASPendingStateController.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASPendingStateController.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASPendingStateController.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASPendingStateController.h diff --git a/submodules/AsyncDisplayKit/Source/ASPendingStateController.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASPendingStateController.mm similarity index 91% rename from submodules/AsyncDisplayKit/Source/ASPendingStateController.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASPendingStateController.mm index e7ca4a71de..269b37e948 100644 --- a/submodules/AsyncDisplayKit/Source/ASPendingStateController.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASPendingStateController.mm @@ -7,10 +7,10 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASPendingStateController.h" +#import #import #import -#import "ASDisplayNodeInternal.h" // Required for -applyPendingViewState; consider moving this to +FrameworkPrivate +#import // Required for -applyPendingViewState; consider moving this to +FrameworkPrivate @interface ASPendingStateController() { diff --git a/submodules/AsyncDisplayKit/Source/ASRecursiveUnfairLock.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASRecursiveUnfairLock.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASRecursiveUnfairLock.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASRecursiveUnfairLock.h diff --git a/submodules/AsyncDisplayKit/Source/ASRecursiveUnfairLock.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASRecursiveUnfairLock.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASRecursiveUnfairLock.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASRecursiveUnfairLock.mm index 9e4a29d47a..e44eec76d1 100644 --- a/submodules/AsyncDisplayKit/Source/ASRecursiveUnfairLock.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASRecursiveUnfairLock.mm @@ -6,7 +6,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASRecursiveUnfairLock.h" +#import #import diff --git a/submodules/AsyncDisplayKit/Source/ASResponderChainEnumerator.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASResponderChainEnumerator.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASResponderChainEnumerator.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASResponderChainEnumerator.h diff --git a/submodules/AsyncDisplayKit/Source/ASResponderChainEnumerator.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASResponderChainEnumerator.mm similarity index 94% rename from submodules/AsyncDisplayKit/Source/ASResponderChainEnumerator.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASResponderChainEnumerator.mm index 2d94c99945..bb16e0fc57 100644 --- a/submodules/AsyncDisplayKit/Source/ASResponderChainEnumerator.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASResponderChainEnumerator.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASResponderChainEnumerator.h" +#import #import @implementation ASResponderChainEnumerator { diff --git a/submodules/AsyncDisplayKit/Source/ASRunLoopQueue.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASRunLoopQueue.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASRunLoopQueue.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASRunLoopQueue.h diff --git a/submodules/AsyncDisplayKit/Source/ASRunLoopQueue.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASRunLoopQueue.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASRunLoopQueue.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASRunLoopQueue.mm diff --git a/submodules/AsyncDisplayKit/Source/ASScrollDirection.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASScrollDirection.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASScrollDirection.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASScrollDirection.h diff --git a/submodules/AsyncDisplayKit/Source/ASScrollDirection.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASScrollDirection.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASScrollDirection.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASScrollDirection.mm index ed0a6b7900..3dff6ba9b8 100644 --- a/submodules/AsyncDisplayKit/Source/ASScrollDirection.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASScrollDirection.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASScrollDirection.h" +#import const ASScrollDirection ASScrollDirectionHorizontalDirections = ASScrollDirectionLeft | ASScrollDirectionRight; const ASScrollDirection ASScrollDirectionVerticalDirections = ASScrollDirectionUp | ASScrollDirectionDown; diff --git a/submodules/AsyncDisplayKit/Source/ASScrollNode.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASScrollNode.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASScrollNode.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASScrollNode.h diff --git a/submodules/AsyncDisplayKit/Source/ASScrollNode.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASScrollNode.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASScrollNode.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASScrollNode.mm index 4dc0652820..c686b5f8ac 100644 --- a/submodules/AsyncDisplayKit/Source/ASScrollNode.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASScrollNode.mm @@ -15,7 +15,6 @@ #import #import #import -#import @interface ASScrollView : UIScrollView @end @@ -88,7 +87,7 @@ restrictedToSize:(ASLayoutElementSize)size relativeToParentSize:(CGSize)parentSize { - ASScopedLockSelfOrToRoot(); + ASLockScopeSelf(); ASSizeRange contentConstrainedSize = constrainedSize; if (ASScrollDirectionContainsVerticalDirection(_scrollableDirections)) { diff --git a/submodules/AsyncDisplayKit/Source/ASSignpost.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASSignpost.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASSignpost.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASSignpost.h diff --git a/submodules/AsyncDisplayKit/Source/ASSupplementaryNodeSource.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASSupplementaryNodeSource.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASSupplementaryNodeSource.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASSupplementaryNodeSource.h diff --git a/submodules/AsyncDisplayKit/Source/ASTextAttribute.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextAttribute.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASTextAttribute.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextAttribute.h diff --git a/submodules/AsyncDisplayKit/Source/ASTextAttribute.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextAttribute.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASTextAttribute.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextAttribute.mm index d1abadbe1f..abbcc8ddf1 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextAttribute.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextAttribute.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASTextAttribute.h" +#import #import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASTextDebugOption.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextDebugOption.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASTextDebugOption.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextDebugOption.h diff --git a/submodules/AsyncDisplayKit/Source/ASTextDebugOption.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextDebugOption.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASTextDebugOption.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextDebugOption.mm index 2565b903c9..9887e1ad74 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextDebugOption.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextDebugOption.mm @@ -6,7 +6,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASTextDebugOption.h" +#import #import static pthread_mutex_t _sharedDebugLock; diff --git a/submodules/AsyncDisplayKit/Source/ASTextInput.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextInput.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASTextInput.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextInput.h diff --git a/submodules/AsyncDisplayKit/Source/ASTextInput.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextInput.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASTextInput.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextInput.mm index b71562ef1c..1cdfe73858 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextInput.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextInput.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASTextInput.h" +#import #import diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitAttributes.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitAttributes.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASTextKitAttributes.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitAttributes.h diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitAttributes.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitAttributes.mm similarity index 96% rename from submodules/AsyncDisplayKit/Source/ASTextKitAttributes.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitAttributes.mm index d2dd4969bb..400ef437bb 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextKitAttributes.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitAttributes.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASTextKitAttributes.h" +#import #if AS_ENABLE_TEXTNODE diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitComponents.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitComponents.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASTextKitComponents.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitComponents.h diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitComponents.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitComponents.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASTextKitComponents.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitComponents.mm diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitContext.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitContext.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASTextKitContext.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitContext.h diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitContext.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitContext.mm similarity index 96% rename from submodules/AsyncDisplayKit/Source/ASTextKitContext.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitContext.mm index 42f4a1a45c..8883f3175f 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextKitContext.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitContext.mm @@ -7,11 +7,11 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASTextKitContext.h" +#import #if AS_ENABLE_TEXTNODE -#import "ASLayoutManager.h" +#import #import #include diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitCoreTextAdditions.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitCoreTextAdditions.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASTextKitCoreTextAdditions.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitCoreTextAdditions.h diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitCoreTextAdditions.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitCoreTextAdditions.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASTextKitCoreTextAdditions.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitCoreTextAdditions.mm index 342a14da38..8ae370de14 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextKitCoreTextAdditions.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitCoreTextAdditions.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASTextKitCoreTextAdditions.h" +#import #if AS_ENABLE_TEXTNODE diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitEntityAttribute.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitEntityAttribute.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASTextKitEntityAttribute.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitEntityAttribute.h diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitEntityAttribute.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitEntityAttribute.mm similarity index 93% rename from submodules/AsyncDisplayKit/Source/ASTextKitEntityAttribute.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitEntityAttribute.mm index 2992107b67..fb87e9bd3a 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextKitEntityAttribute.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitEntityAttribute.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASTextKitEntityAttribute.h" +#import #if AS_ENABLE_TEXTNODE diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitFontSizeAdjuster.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitFontSizeAdjuster.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASTextKitFontSizeAdjuster.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitFontSizeAdjuster.h diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitFontSizeAdjuster.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitFontSizeAdjuster.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASTextKitFontSizeAdjuster.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitFontSizeAdjuster.mm index 4deab16afe..aeea44d7cc 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextKitFontSizeAdjuster.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitFontSizeAdjuster.mm @@ -8,7 +8,7 @@ // -#import "ASTextKitFontSizeAdjuster.h" +#import #if AS_ENABLE_TEXTNODE diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitRenderer+Positioning.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitRenderer+Positioning.h similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASTextKitRenderer+Positioning.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitRenderer+Positioning.h index 82d206c78b..c887282568 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextKitRenderer+Positioning.h +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitRenderer+Positioning.h @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASTextKitRenderer.h" +#import #if AS_ENABLE_TEXTNODE diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitRenderer+Positioning.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitRenderer+Positioning.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASTextKitRenderer+Positioning.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitRenderer+Positioning.mm index 45b4573bd6..9dc770e1d9 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextKitRenderer+Positioning.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitRenderer+Positioning.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASTextKitRenderer+Positioning.h" +#import #if AS_ENABLE_TEXTNODE diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitRenderer+TextChecking.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitRenderer+TextChecking.h similarity index 95% rename from submodules/AsyncDisplayKit/Source/ASTextKitRenderer+TextChecking.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitRenderer+TextChecking.h index b5ea9fca06..d4ba74fd32 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextKitRenderer+TextChecking.h +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitRenderer+TextChecking.h @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASTextKitRenderer.h" +#import #if AS_ENABLE_TEXTNODE diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitRenderer+TextChecking.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitRenderer+TextChecking.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASTextKitRenderer+TextChecking.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitRenderer+TextChecking.mm index 205330aee6..e556393572 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextKitRenderer+TextChecking.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitRenderer+TextChecking.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASTextKitRenderer+TextChecking.h" +#import #if AS_ENABLE_TEXTNODE diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitRenderer.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitRenderer.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASTextKitRenderer.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitRenderer.h diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitRenderer.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitRenderer.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASTextKitRenderer.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitRenderer.mm index e96d7db7ea..b724d70747 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextKitRenderer.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitRenderer.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASTextKitRenderer.h" +#import #if AS_ENABLE_TEXTNODE @@ -17,7 +17,7 @@ #import #import #import -#import "Private/ASInternalHelpers.h" +#import #import //#define LOG(...) NSLog(__VA_ARGS__) diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitShadower.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitShadower.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASTextKitShadower.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitShadower.h diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitShadower.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitShadower.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASTextKitShadower.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitShadower.mm index 3c4096757f..a2f37f7e06 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextKitShadower.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitShadower.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASTextKitShadower.h" +#import #if AS_ENABLE_TEXTNODE diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitTailTruncater.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitTailTruncater.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASTextKitTailTruncater.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitTailTruncater.h diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitTailTruncater.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitTailTruncater.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASTextKitTailTruncater.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitTailTruncater.mm index a8be67ba13..b81e27be05 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextKitTailTruncater.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitTailTruncater.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASTextKitTailTruncater.h" +#import #if AS_ENABLE_TEXTNODE diff --git a/submodules/AsyncDisplayKit/Source/ASTextKitTruncating.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitTruncating.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASTextKitTruncating.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextKitTruncating.h diff --git a/submodules/AsyncDisplayKit/Source/ASTextNodeTypes.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextNodeTypes.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASTextNodeTypes.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextNodeTypes.h diff --git a/submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextNodeWordKerner.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextNodeWordKerner.h diff --git a/submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextNodeWordKerner.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextNodeWordKerner.mm index e1d0c73c0e..67e640557c 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextNodeWordKerner.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextNodeWordKerner.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASTextNodeWordKerner.h" +#import #import diff --git a/submodules/AsyncDisplayKit/Source/ASTextRunDelegate.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextRunDelegate.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASTextRunDelegate.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextRunDelegate.h diff --git a/submodules/AsyncDisplayKit/Source/ASTextRunDelegate.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextRunDelegate.mm similarity index 97% rename from submodules/AsyncDisplayKit/Source/ASTextRunDelegate.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextRunDelegate.mm index f1b1f0106e..1c179b1fea 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextRunDelegate.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextRunDelegate.mm @@ -6,7 +6,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASTextRunDelegate.h" +#import static void DeallocCallback(void *ref) { ASTextRunDelegate *self = (__bridge_transfer ASTextRunDelegate *)(ref); diff --git a/submodules/AsyncDisplayKit/Source/ASTextUtilities.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextUtilities.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASTextUtilities.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextUtilities.h index 50b47a01c0..f21a931ba8 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextUtilities.h +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextUtilities.h @@ -10,7 +10,7 @@ #import #import #import -#import "Private/ASInternalHelpers.h" +#import #ifndef ASTEXT_CLAMP // return the clamped value diff --git a/submodules/AsyncDisplayKit/Source/ASTextUtilities.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextUtilities.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASTextUtilities.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextUtilities.mm index 8d0137718b..e1b8c6d71c 100644 --- a/submodules/AsyncDisplayKit/Source/ASTextUtilities.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTextUtilities.mm @@ -6,7 +6,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASTextUtilities.h" +#import #import NSCharacterSet *ASTextVerticalFormRotateCharacterSet() { diff --git a/submodules/AsyncDisplayKit/Source/ASThread.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASThread.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASThread.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASThread.h diff --git a/submodules/AsyncDisplayKit/Source/ASTraceEvent.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTraceEvent.h similarity index 95% rename from submodules/AsyncDisplayKit/Source/ASTraceEvent.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTraceEvent.h index 8ca7b32fa6..72249faea9 100644 --- a/submodules/AsyncDisplayKit/Source/ASTraceEvent.h +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTraceEvent.h @@ -8,7 +8,7 @@ // #import -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/submodules/AsyncDisplayKit/Source/ASTraceEvent.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTraceEvent.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASTraceEvent.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTraceEvent.mm index b530624fc0..c809865591 100644 --- a/submodules/AsyncDisplayKit/Source/ASTraceEvent.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTraceEvent.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASTraceEvent.h" +#import #import static NSString *const ASTraceEventThreadDescriptionKey = @"ASThreadTraceEventDescription"; diff --git a/submodules/AsyncDisplayKit/Source/ASTraitCollection.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTraitCollection.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASTraitCollection.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTraitCollection.mm index efabee7257..e885239211 100644 --- a/submodules/AsyncDisplayKit/Source/ASTraitCollection.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTraitCollection.mm @@ -7,7 +7,6 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import #import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASTwoDimensionalArrayUtils.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTwoDimensionalArrayUtils.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASTwoDimensionalArrayUtils.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTwoDimensionalArrayUtils.h diff --git a/submodules/AsyncDisplayKit/Source/ASTwoDimensionalArrayUtils.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTwoDimensionalArrayUtils.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASTwoDimensionalArrayUtils.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTwoDimensionalArrayUtils.mm index a5140f91fc..0527580df1 100644 --- a/submodules/AsyncDisplayKit/Source/ASTwoDimensionalArrayUtils.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASTwoDimensionalArrayUtils.mm @@ -8,9 +8,9 @@ // #import -#import -#import "Private/ASInternalHelpers.h" +#import #import +#import #import diff --git a/submodules/AsyncDisplayKit/Source/ASVisibilityProtocols.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASVisibilityProtocols.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASVisibilityProtocols.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASVisibilityProtocols.h diff --git a/submodules/AsyncDisplayKit/Source/ASVisibilityProtocols.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASVisibilityProtocols.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASVisibilityProtocols.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASVisibilityProtocols.mm diff --git a/submodules/AsyncDisplayKit/Source/ASWeakMap.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASWeakMap.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASWeakMap.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASWeakMap.h diff --git a/submodules/AsyncDisplayKit/Source/ASWeakMap.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASWeakMap.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASWeakMap.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASWeakMap.mm index 1267d8ff69..3110b13d37 100644 --- a/submodules/AsyncDisplayKit/Source/ASWeakMap.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASWeakMap.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASWeakMap.h" +#import @interface ASWeakMapEntry () @property (nonatomic, readonly) id key; diff --git a/submodules/AsyncDisplayKit/Source/ASWeakProxy.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASWeakProxy.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASWeakProxy.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASWeakProxy.h diff --git a/submodules/AsyncDisplayKit/Source/ASWeakProxy.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASWeakProxy.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/ASWeakProxy.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASWeakProxy.mm index 4a3b5c8a2a..4a73408dd5 100644 --- a/submodules/AsyncDisplayKit/Source/ASWeakProxy.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASWeakProxy.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "ASWeakProxy.h" +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASWeakSet.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASWeakSet.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASWeakSet.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASWeakSet.h diff --git a/submodules/AsyncDisplayKit/Source/ASWeakSet.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASWeakSet.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASWeakSet.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/ASWeakSet.mm diff --git a/submodules/AsyncDisplayKit/Source/CoreGraphics+ASConvenience.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/CoreGraphics+ASConvenience.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/CoreGraphics+ASConvenience.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/CoreGraphics+ASConvenience.h diff --git a/submodules/AsyncDisplayKit/Source/NSArray+Diffing.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSArray+Diffing.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/NSArray+Diffing.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSArray+Diffing.h diff --git a/submodules/AsyncDisplayKit/Source/NSArray+Diffing.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSArray+Diffing.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/NSArray+Diffing.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSArray+Diffing.mm index 51d564a0d9..83d32fea68 100644 --- a/submodules/AsyncDisplayKit/Source/NSArray+Diffing.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSArray+Diffing.mm @@ -7,9 +7,9 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "NSArray+Diffing.h" +#import #import -#import "ASAssert.h" +#import #import @implementation NSArray (Diffing) diff --git a/submodules/AsyncDisplayKit/Source/NSAttributedString+ASText.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSAttributedString+ASText.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/NSAttributedString+ASText.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSAttributedString+ASText.h diff --git a/submodules/AsyncDisplayKit/Source/NSAttributedString+ASText.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSAttributedString+ASText.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/NSAttributedString+ASText.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSAttributedString+ASText.mm index 205441e633..39f628151c 100644 --- a/submodules/AsyncDisplayKit/Source/NSAttributedString+ASText.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSAttributedString+ASText.mm @@ -6,7 +6,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "NSAttributedString+ASText.h" +#import #import #import #import diff --git a/submodules/AsyncDisplayKit/Source/NSIndexSet+ASHelpers.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSIndexSet+ASHelpers.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/NSIndexSet+ASHelpers.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSIndexSet+ASHelpers.h diff --git a/submodules/AsyncDisplayKit/Source/NSIndexSet+ASHelpers.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSIndexSet+ASHelpers.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/NSIndexSet+ASHelpers.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSIndexSet+ASHelpers.mm diff --git a/submodules/AsyncDisplayKit/Source/NSMutableAttributedString+TextKitAdditions.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSMutableAttributedString+TextKitAdditions.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/NSMutableAttributedString+TextKitAdditions.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSMutableAttributedString+TextKitAdditions.h diff --git a/submodules/AsyncDisplayKit/Source/NSMutableAttributedString+TextKitAdditions.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSMutableAttributedString+TextKitAdditions.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/NSMutableAttributedString+TextKitAdditions.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSMutableAttributedString+TextKitAdditions.mm diff --git a/submodules/AsyncDisplayKit/Source/NSParagraphStyle+ASText.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSParagraphStyle+ASText.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/NSParagraphStyle+ASText.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSParagraphStyle+ASText.h diff --git a/submodules/AsyncDisplayKit/Source/NSParagraphStyle+ASText.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSParagraphStyle+ASText.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/NSParagraphStyle+ASText.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSParagraphStyle+ASText.mm index 80ce6305cb..bc19fd234c 100644 --- a/submodules/AsyncDisplayKit/Source/NSParagraphStyle+ASText.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/NSParagraphStyle+ASText.mm @@ -6,8 +6,8 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "NSParagraphStyle+ASText.h" -#import "Private/TextExperiment/String/ASTextAttribute.h" +#import +#import #import // Dummy class for category diff --git a/submodules/AsyncDisplayKit/Source/UICollectionViewLayout+ASConvenience.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/UICollectionViewLayout+ASConvenience.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/UICollectionViewLayout+ASConvenience.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/UICollectionViewLayout+ASConvenience.h diff --git a/submodules/AsyncDisplayKit/Source/UICollectionViewLayout+ASConvenience.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/UICollectionViewLayout+ASConvenience.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/UICollectionViewLayout+ASConvenience.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/UICollectionViewLayout+ASConvenience.mm diff --git a/submodules/AsyncDisplayKit/Source/UIResponder+AsyncDisplayKit.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/UIResponder+AsyncDisplayKit.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/UIResponder+AsyncDisplayKit.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/UIResponder+AsyncDisplayKit.h diff --git a/submodules/AsyncDisplayKit/Source/UIResponder+AsyncDisplayKit.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/UIResponder+AsyncDisplayKit.mm similarity index 86% rename from submodules/AsyncDisplayKit/Source/UIResponder+AsyncDisplayKit.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/UIResponder+AsyncDisplayKit.mm index ee26560877..8c012723ad 100644 --- a/submodules/AsyncDisplayKit/Source/UIResponder+AsyncDisplayKit.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/UIResponder+AsyncDisplayKit.mm @@ -7,11 +7,11 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "UIResponder+AsyncDisplayKit.h" +#import #import #import -#import "Private/ASResponderChainEnumerator.h" +#import @implementation UIResponder (AsyncDisplayKit) diff --git a/submodules/AsyncDisplayKit/Source/UIView+ASConvenience.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/UIView+ASConvenience.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/UIView+ASConvenience.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/UIView+ASConvenience.h diff --git a/submodules/AsyncDisplayKit/Source/_ASAsyncTransaction.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASAsyncTransaction.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/_ASAsyncTransaction.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASAsyncTransaction.h diff --git a/submodules/AsyncDisplayKit/Source/_ASAsyncTransaction.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASAsyncTransaction.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/_ASAsyncTransaction.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASAsyncTransaction.mm diff --git a/submodules/AsyncDisplayKit/Source/_ASAsyncTransactionContainer+Private.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASAsyncTransactionContainer+Private.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/_ASAsyncTransactionContainer+Private.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASAsyncTransactionContainer+Private.h diff --git a/submodules/AsyncDisplayKit/Source/_ASAsyncTransactionContainer.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASAsyncTransactionContainer.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/_ASAsyncTransactionContainer.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASAsyncTransactionContainer.mm diff --git a/submodules/AsyncDisplayKit/Source/_ASAsyncTransactionGroup.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASAsyncTransactionGroup.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/_ASAsyncTransactionGroup.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASAsyncTransactionGroup.h diff --git a/submodules/AsyncDisplayKit/Source/_ASAsyncTransactionGroup.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASAsyncTransactionGroup.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/_ASAsyncTransactionGroup.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASAsyncTransactionGroup.mm diff --git a/submodules/AsyncDisplayKit/Source/_ASCoreAnimationExtras.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASCoreAnimationExtras.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/_ASCoreAnimationExtras.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASCoreAnimationExtras.h diff --git a/submodules/AsyncDisplayKit/Source/_ASCoreAnimationExtras.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASCoreAnimationExtras.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/_ASCoreAnimationExtras.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASCoreAnimationExtras.mm index a8a0a9fe89..b55bd6442f 100644 --- a/submodules/AsyncDisplayKit/Source/_ASCoreAnimationExtras.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASCoreAnimationExtras.mm @@ -7,7 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "_ASCoreAnimationExtras.h" +#import #import #import diff --git a/submodules/AsyncDisplayKit/Source/_ASDisplayLayer.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASDisplayLayer.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/_ASDisplayLayer.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASDisplayLayer.h diff --git a/submodules/AsyncDisplayKit/Source/_ASDisplayLayer.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASDisplayLayer.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/_ASDisplayLayer.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASDisplayLayer.mm index 51676fc6e0..a7478f1883 100644 --- a/submodules/AsyncDisplayKit/Source/_ASDisplayLayer.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASDisplayLayer.mm @@ -14,7 +14,7 @@ #import #import #import -#import "Private/ASDisplayNodeInternal.h" +#import #import #import #import diff --git a/submodules/AsyncDisplayKit/Source/_ASDisplayView.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASDisplayView.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/_ASDisplayView.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASDisplayView.h diff --git a/submodules/AsyncDisplayKit/Source/_ASDisplayView.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASDisplayView.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/_ASDisplayView.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASDisplayView.mm index a0792bc2b2..d25f54c589 100644 --- a/submodules/AsyncDisplayKit/Source/_ASDisplayView.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASDisplayView.mm @@ -10,16 +10,15 @@ #import #import -#import "Private/_ASCoreAnimationExtras.h" +#import #import #import #import #import #import -#import "Private/ASInternalHelpers.h" +#import #import #import -#import #pragma mark - _ASDisplayViewMethodOverrides diff --git a/submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASDisplayViewAccessiblity.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASDisplayViewAccessiblity.h diff --git a/submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASDisplayViewAccessiblity.mm similarity index 98% rename from submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASDisplayViewAccessiblity.mm index 9fe817d09b..321c1af742 100644 --- a/submodules/AsyncDisplayKit/Source/_ASDisplayViewAccessiblity.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASDisplayViewAccessiblity.mm @@ -11,11 +11,9 @@ #import #import -#import #import -#import "ASDisplayNode+FrameworkPrivate.h" +#import #import -#import #import @@ -296,9 +294,7 @@ static void CollectAccessibilityElementsForView(UIView *view, NSMutableArray *el if (viewNode == nil) { return @[]; } - if (true || _accessibilityElements == nil) { - _accessibilityElements = [viewNode accessibilityElements]; - } + _accessibilityElements = [viewNode accessibilityElements]; return _accessibilityElements; } diff --git a/submodules/AsyncDisplayKit/Source/_ASHierarchyChangeSet.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASHierarchyChangeSet.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/_ASHierarchyChangeSet.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASHierarchyChangeSet.h diff --git a/submodules/AsyncDisplayKit/Source/_ASHierarchyChangeSet.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASHierarchyChangeSet.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/_ASHierarchyChangeSet.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASHierarchyChangeSet.mm index 89d0af9de9..77d4d1af3f 100644 --- a/submodules/AsyncDisplayKit/Source/_ASHierarchyChangeSet.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASHierarchyChangeSet.mm @@ -10,7 +10,7 @@ #ifndef MINIMAL_ASDK #import -#import "Private/ASInternalHelpers.h" +#import #import #import #import diff --git a/submodules/AsyncDisplayKit/Source/_ASPendingState.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASPendingState.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/_ASPendingState.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASPendingState.h diff --git a/submodules/AsyncDisplayKit/Source/_ASPendingState.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASPendingState.mm similarity index 99% rename from submodules/AsyncDisplayKit/Source/_ASPendingState.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASPendingState.mm index 0b1fe54d54..9e9778cf04 100644 --- a/submodules/AsyncDisplayKit/Source/_ASPendingState.mm +++ b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASPendingState.mm @@ -7,14 +7,14 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // -#import "_ASPendingState.h" +#import -#import "_ASCoreAnimationExtras.h" +#import #import #import #import #import -#import "Private/ASInternalHelpers.h" +#import #define __shouldSetNeedsDisplay(layer) (flags.needsDisplay \ || (flags.setOpaque && opaque != (layer).opaque)\ diff --git a/submodules/AsyncDisplayKit/Source/_ASScopeTimer.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASScopeTimer.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/_ASScopeTimer.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASScopeTimer.h diff --git a/submodules/AsyncDisplayKit/Source/_ASTransitionContext.h b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASTransitionContext.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/_ASTransitionContext.h rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASTransitionContext.h diff --git a/submodules/AsyncDisplayKit/Source/_ASTransitionContext.mm b/submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASTransitionContext.mm similarity index 100% rename from submodules/AsyncDisplayKit/Source/_ASTransitionContext.mm rename to submodules/AsyncDisplayKit/Source/Implementation/AsyncDisplayKit/_ASTransitionContext.mm diff --git a/submodules/AsyncDisplayKit/Source/Info.plist b/submodules/AsyncDisplayKit/Source/Info.plist deleted file mode 100644 index fbe1e6b314..0000000000 --- a/submodules/AsyncDisplayKit/Source/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSPrincipalClass - - - diff --git a/submodules/AsyncDisplayKit/Source/ASAbsoluteLayoutElement.h b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASAbsoluteLayoutElement.h similarity index 96% rename from submodules/AsyncDisplayKit/Source/ASAbsoluteLayoutElement.h rename to submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASAbsoluteLayoutElement.h index 109816de20..2ae11bd2d6 100644 --- a/submodules/AsyncDisplayKit/Source/ASAbsoluteLayoutElement.h +++ b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASAbsoluteLayoutElement.h @@ -7,6 +7,7 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // +#import #import NS_ASSUME_NONNULL_BEGIN diff --git a/submodules/AsyncDisplayKit/Source/ASAssert.h b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASAssert.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASAssert.h rename to submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASAssert.h diff --git a/submodules/AsyncDisplayKit/Source/ASAvailability.h b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASAvailability.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASAvailability.h rename to submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASAvailability.h diff --git a/submodules/AsyncDisplayKit/Source/ASBaseDefines.h b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASBaseDefines.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASBaseDefines.h rename to submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASBaseDefines.h diff --git a/submodules/AsyncDisplayKit/Source/ASBlockTypes.h b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASBlockTypes.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASBlockTypes.h rename to submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASBlockTypes.h diff --git a/submodules/AsyncDisplayKit/Source/ASDimension.h b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASDimension.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDimension.h rename to submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASDimension.h diff --git a/submodules/AsyncDisplayKit/Source/ASDimensionInternal.h b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASDimensionInternal.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDimensionInternal.h rename to submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASDimensionInternal.h diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode+InterfaceState.h b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASDisplayNode+InterfaceState.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode+InterfaceState.h rename to submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASDisplayNode+InterfaceState.h diff --git a/submodules/AsyncDisplayKit/Source/ASDisplayNode.h b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASDisplayNode.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASDisplayNode.h rename to submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASDisplayNode.h index a3475892df..479d1acfe5 100644 --- a/submodules/AsyncDisplayKit/Source/ASDisplayNode.h +++ b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASDisplayNode.h @@ -15,7 +15,6 @@ #import #import #import -#import #import #import #import diff --git a/submodules/AsyncDisplayKit/Source/ASEditableTextNode.h b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASEditableTextNode.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASEditableTextNode.h rename to submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASEditableTextNode.h diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutElement.h b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASLayoutElement.h similarity index 99% rename from submodules/AsyncDisplayKit/Source/ASLayoutElement.h rename to submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASLayoutElement.h index c4ba689cfc..bd871730aa 100644 --- a/submodules/AsyncDisplayKit/Source/ASLayoutElement.h +++ b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASLayoutElement.h @@ -13,7 +13,6 @@ #import #import #import -#import #import @class ASLayout; @@ -54,7 +53,7 @@ typedef NS_ENUM(NSUInteger, ASLayoutElementType) { * access to the options via convenience properties. If you are creating custom layout spec, then you can * extend the backing layout options class to accommodate any new layout options. */ -@protocol ASLayoutElement +@protocol ASLayoutElement #pragma mark - Getter diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutElementExtensibility.h b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASLayoutElementExtensibility.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASLayoutElementExtensibility.h rename to submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASLayoutElementExtensibility.h diff --git a/submodules/AsyncDisplayKit/Source/ASLayoutElementPrivate.h b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASLayoutElementPrivate.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASLayoutElementPrivate.h rename to submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASLayoutElementPrivate.h diff --git a/submodules/AsyncDisplayKit/Source/ASLocking.h b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASLocking.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASLocking.h rename to submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASLocking.h diff --git a/submodules/AsyncDisplayKit/Source/ASObjectDescriptionHelpers.h b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASObjectDescriptionHelpers.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASObjectDescriptionHelpers.h rename to submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASObjectDescriptionHelpers.h diff --git a/submodules/AsyncDisplayKit/Source/ASStackLayoutDefines.h b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASStackLayoutDefines.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASStackLayoutDefines.h rename to submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASStackLayoutDefines.h diff --git a/submodules/AsyncDisplayKit/Source/ASStackLayoutElement.h b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASStackLayoutElement.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASStackLayoutElement.h rename to submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASStackLayoutElement.h diff --git a/submodules/AsyncDisplayKit/Source/ASTraitCollection.h b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASTraitCollection.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/ASTraitCollection.h rename to submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/ASTraitCollection.h diff --git a/submodules/AsyncDisplayKit/Source/_ASAsyncTransactionContainer.h b/submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/_ASAsyncTransactionContainer.h similarity index 100% rename from submodules/AsyncDisplayKit/Source/_ASAsyncTransactionContainer.h rename to submodules/AsyncDisplayKit/Source/Public/AsyncDisplayKit/_ASAsyncTransactionContainer.h diff --git a/submodules/AsyncDisplayKit/Source/UIImage+ASConvenience.h b/submodules/AsyncDisplayKit/Source/UIImage+ASConvenience.h deleted file mode 100644 index b24c97d1c5..0000000000 --- a/submodules/AsyncDisplayKit/Source/UIImage+ASConvenience.h +++ /dev/null @@ -1,109 +0,0 @@ -// -// UIImage+ASConvenience.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 -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * Dramatically faster version of +[UIImage imageNamed:]. Although it is believed that imageNamed: - * has a cache and is fast, it actually performs expensive asset catalog lookups and is often a - * performance bottleneck (verified on iOS 7 through iOS 10). - * - * Use [UIImage as_imageNamed:] anywhere in your app, even if you aren't using other parts of ASDK. - * Although not the best choice for extremely large assets that are only used once, it is the ideal - * choice for any assets used in tab bars, nav bars, buttons, table or collection cells, etc. - */ - -@interface UIImage (ASDKFastImageNamed) - -/** - * A version of imageNamed that caches results because loading an image is expensive. - * Calling with the same name value will usually return the same object. A UIImage, - * after creation, is immutable and thread-safe so it's fine to share these objects across multiple threads. - * - * @param imageName The name of the image to load - * @return The loaded image or nil - */ -+ (nullable UIImage *)as_imageNamed:(NSString *)imageName NS_RETURNS_RETAINED; - -/** - * A version of imageNamed that caches results because loading an image is expensive. - * Calling with the same name value will usually return the same object. A UIImage, - * after creation, is immutable and thread-safe so it's fine to share these objects across multiple threads. - * - * @param imageName The name of the image to load - * @param traitCollection The traits associated with the intended environment for the image. - * @return The loaded image or nil - */ -+ (nullable UIImage *)as_imageNamed:(NSString *)imageName compatibleWithTraitCollection:(nullable UITraitCollection *)traitCollection NS_RETURNS_RETAINED; - -@end - -/** - * High-performance flat-colored, rounded-corner resizable images - * - * For "Baked-in Opaque" corners, set cornerColor equal to the color behind the rounded image object, - * i.e. the background color. - * For "Baked-in Alpha" corners, set cornerColor = [UIColor clearColor] - * - * See http://asyncdisplaykit.org/docs/corner-rounding.html for an explanation. - */ - -@interface UIImage (ASDKResizableRoundedRects) - -/** - * This generates a flat-color, rounded-corner resizeable image - * - * @param cornerRadius The radius of the rounded-corner - * @param cornerColor The fill color of the corners (For Alpha corners use clearColor) - * @param fillColor The fill color of the rounded-corner image - */ -+ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius - cornerColor:(nullable UIColor *)cornerColor - fillColor:(UIColor *)fillColor NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -/** - * This generates a flat-color, rounded-corner resizeable image with a border - * - * @param cornerRadius The radius of the rounded-corner - * @param cornerColor The fill color of the corners (For Alpha corners use clearColor) - * @param fillColor The fill color of the rounded-corner image - * @param borderColor The border color. Set to nil for no border. - * @param borderWidth The border width. Dummy value if borderColor = nil. - */ -+ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius - cornerColor:(UIColor *)cornerColor - fillColor:(UIColor *)fillColor - borderColor:(nullable UIColor *)borderColor - borderWidth:(CGFloat)borderWidth NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -/** - * This generates a flat-color, rounded-corner resizeable image with a border - * - * @param cornerRadius The radius of the rounded-corner - * @param cornerColor The fill color of the corners (For Alpha corners use clearColor) - * @param fillColor The fill color of the rounded-corner image - * @param borderColor The border color. Set to nil for no border. - * @param borderWidth The border width. Dummy value if borderColor = nil. - * @param roundedCorners Select individual or multiple corners to round. Set to UIRectCornerAllCorners to round all 4 corners. - * @param scale The number of pixels per point. Provide 0.0 to use the screen scale. - */ -+ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius - cornerColor:(nullable UIColor *)cornerColor - fillColor:(UIColor *)fillColor - borderColor:(nullable UIColor *)borderColor - borderWidth:(CGFloat)borderWidth - roundedCorners:(UIRectCorner)roundedCorners - scale:(CGFloat)scale NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/AsyncDisplayKit/Source/UIImage+ASConvenience.mm b/submodules/AsyncDisplayKit/Source/UIImage+ASConvenience.mm deleted file mode 100644 index f3493093f9..0000000000 --- a/submodules/AsyncDisplayKit/Source/UIImage+ASConvenience.mm +++ /dev/null @@ -1,172 +0,0 @@ -// -// UIImage+ASConvenience.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 -#import -#import "Private/ASInternalHelpers.h" -#import - -#pragma mark - ASDKFastImageNamed - -@implementation UIImage (ASDKFastImageNamed) - -UIImage *cachedImageNamed(NSString *imageName, UITraitCollection *traitCollection) NS_RETURNS_RETAINED -{ - static NSCache *imageCache = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // Because NSCache responds to memory warnings, we do not need an explicit limit. - // all of these objects contain compressed image data and are relatively small - // compared to the backing stores of text and image views. - imageCache = [[NSCache alloc] init]; - }); - - UIImage *image = nil; - if ([imageName length] > 0) { - NSString *imageKey = imageName; - if (traitCollection) { - char imageKeyBuffer[256]; - snprintf(imageKeyBuffer, sizeof(imageKeyBuffer), "%s|%ld|%ld", imageName.UTF8String, (long)traitCollection.horizontalSizeClass, (long)traitCollection.verticalSizeClass); - imageKey = [NSString stringWithUTF8String:imageKeyBuffer]; - } - - image = [imageCache objectForKey:imageKey]; - if (!image) { - image = [UIImage imageNamed:imageName inBundle:nil compatibleWithTraitCollection:traitCollection]; - if (image) { - [imageCache setObject:image forKey:imageKey]; - } - } - } - return image; -} - -+ (UIImage *)as_imageNamed:(NSString *)imageName NS_RETURNS_RETAINED -{ - return cachedImageNamed(imageName, nil); -} - -+ (UIImage *)as_imageNamed:(NSString *)imageName compatibleWithTraitCollection:(UITraitCollection *)traitCollection NS_RETURNS_RETAINED -{ - return cachedImageNamed(imageName, traitCollection); -} - -@end - -#pragma mark - ASDKResizableRoundedRects - -@implementation UIImage (ASDKResizableRoundedRects) - -+ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius - cornerColor:(UIColor *)cornerColor - fillColor:(UIColor *)fillColor NS_RETURNS_RETAINED -{ - return [self as_resizableRoundedImageWithCornerRadius:cornerRadius - cornerColor:cornerColor - fillColor:fillColor - borderColor:nil - borderWidth:1.0 - roundedCorners:UIRectCornerAllCorners - scale:0.0]; -} - -+ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius - cornerColor:(UIColor *)cornerColor - fillColor:(UIColor *)fillColor - borderColor:(UIColor *)borderColor - borderWidth:(CGFloat)borderWidth NS_RETURNS_RETAINED -{ - return [self as_resizableRoundedImageWithCornerRadius:cornerRadius - cornerColor:cornerColor - fillColor:fillColor - borderColor:borderColor - borderWidth:borderWidth - roundedCorners:UIRectCornerAllCorners - scale:0.0]; -} - -+ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius - cornerColor:(UIColor *)cornerColor - fillColor:(UIColor *)fillColor - borderColor:(UIColor *)borderColor - borderWidth:(CGFloat)borderWidth - roundedCorners:(UIRectCorner)roundedCorners - scale:(CGFloat)scale NS_RETURNS_RETAINED -{ - static NSCache *__pathCache = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - __pathCache = [[NSCache alloc] init]; - // UIBezierPath objects are fairly small and these are equally sized. 20 should be plenty for many different parameters. - __pathCache.countLimit = 20; - }); - - // Treat clear background color as no background color - if ([cornerColor isEqual:[UIColor clearColor]]) { - cornerColor = nil; - } - - CGFloat dimension = (cornerRadius * 2) + 1; - CGRect bounds = CGRectMake(0, 0, dimension, dimension); - - typedef struct { - UIRectCorner corners; - CGFloat radius; - } PathKey; - PathKey key = { roundedCorners, cornerRadius }; - NSValue *pathKeyObject = [[NSValue alloc] initWithBytes:&key objCType:@encode(PathKey)]; - - CGSize cornerRadii = CGSizeMake(cornerRadius, cornerRadius); - UIBezierPath *path = [__pathCache objectForKey:pathKeyObject]; - if (path == nil) { - path = [UIBezierPath bezierPathWithRoundedRect:bounds byRoundingCorners:roundedCorners cornerRadii:cornerRadii]; - [__pathCache setObject:path forKey:pathKeyObject]; - } - - // We should probably check if the background color has any alpha component but that - // might be expensive due to needing to check mulitple color spaces. - ASGraphicsBeginImageContextWithOptions(bounds.size, cornerColor != nil, scale); - - BOOL contextIsClean = YES; - if (cornerColor) { - contextIsClean = NO; - [cornerColor setFill]; - // Copy "blend" mode is extra fast because it disregards any value currently in the buffer and overrides directly. - UIRectFillUsingBlendMode(bounds, kCGBlendModeCopy); - } - - BOOL canUseCopy = contextIsClean || (CGColorGetAlpha(fillColor.CGColor) == 1); - [fillColor setFill]; - [path fillWithBlendMode:(canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal) alpha:1]; - - if (borderColor) { - [borderColor setStroke]; - - // Inset border fully inside filled path (not halfway on each side of path) - CGRect strokeRect = CGRectInset(bounds, borderWidth / 2.0, borderWidth / 2.0); - - // It is rarer to have a stroke path, and our cache key only handles rounded rects for the exact-stretchable - // size calculated by cornerRadius, so we won't bother caching this path. Profiling validates this decision. - UIBezierPath *strokePath = [UIBezierPath bezierPathWithRoundedRect:strokeRect - byRoundingCorners:roundedCorners - cornerRadii:cornerRadii]; - [strokePath setLineWidth:borderWidth]; - BOOL canUseCopy = (CGColorGetAlpha(borderColor.CGColor) == 1); - [strokePath strokeWithBlendMode:(canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal) alpha:1]; - } - - UIImage *result = ASGraphicsGetImageAndEndCurrentContext(); - - UIEdgeInsets capInsets = UIEdgeInsetsMake(cornerRadius, cornerRadius, cornerRadius, cornerRadius); - result = [result resizableImageWithCapInsets:capInsets resizingMode:UIImageResizingModeStretch]; - - return result; -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/_ASCollectionGalleryLayoutInfo.h b/submodules/AsyncDisplayKit/Source/_ASCollectionGalleryLayoutInfo.h deleted file mode 100644 index 6d7c91e5e5..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASCollectionGalleryLayoutInfo.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// _ASCollectionGalleryLayoutInfo.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@interface _ASCollectionGalleryLayoutInfo : NSObject - -// Read-only properties -@property (nonatomic, readonly) CGSize itemSize; -@property (nonatomic, readonly) CGFloat minimumLineSpacing; -@property (nonatomic, readonly) CGFloat minimumInteritemSpacing; -@property (nonatomic, readonly) UIEdgeInsets sectionInset; - -- (instancetype)initWithItemSize:(CGSize)itemSize - minimumLineSpacing:(CGFloat)minimumLineSpacing - minimumInteritemSpacing:(CGFloat)minimumInteritemSpacing - sectionInset:(UIEdgeInsets)sectionInset NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; - -@end diff --git a/submodules/AsyncDisplayKit/Source/_ASCollectionGalleryLayoutInfo.mm b/submodules/AsyncDisplayKit/Source/_ASCollectionGalleryLayoutInfo.mm deleted file mode 100644 index 91a0c98411..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASCollectionGalleryLayoutInfo.mm +++ /dev/null @@ -1,68 +0,0 @@ -// -// _ASCollectionGalleryLayoutInfo.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "_ASCollectionGalleryLayoutInfo.h" -#import - -@implementation _ASCollectionGalleryLayoutInfo - -- (instancetype)initWithItemSize:(CGSize)itemSize - minimumLineSpacing:(CGFloat)minimumLineSpacing - minimumInteritemSpacing:(CGFloat)minimumInteritemSpacing - sectionInset:(UIEdgeInsets)sectionInset -{ - self = [super init]; - if (self) { - _itemSize = itemSize; - _minimumLineSpacing = minimumLineSpacing; - _minimumInteritemSpacing = minimumInteritemSpacing; - _sectionInset = sectionInset; - } - return self; -} - -- (BOOL)isEqualToInfo:(_ASCollectionGalleryLayoutInfo *)info -{ - if (info == nil) { - return NO; - } - - return CGSizeEqualToSize(_itemSize, info.itemSize) - && _minimumLineSpacing == info.minimumLineSpacing - && _minimumInteritemSpacing == info.minimumInteritemSpacing - && UIEdgeInsetsEqualToEdgeInsets(_sectionInset, info.sectionInset); -} - -- (BOOL)isEqual:(id)other -{ - if (self == other) { - return YES; - } - if (! [other isKindOfClass:[_ASCollectionGalleryLayoutInfo class]]) { - return NO; - } - return [self isEqualToInfo:other]; -} - -- (NSUInteger)hash -{ - struct { - CGSize itemSize; - CGFloat minimumLineSpacing; - CGFloat minimumInteritemSpacing; - UIEdgeInsets sectionInset; - } data = { - _itemSize, - _minimumLineSpacing, - _minimumInteritemSpacing, - _sectionInset, - }; - return ASHashBytes(&data, sizeof(data)); -} - -@end diff --git a/submodules/AsyncDisplayKit/Source/_ASCollectionGalleryLayoutItem.h b/submodules/AsyncDisplayKit/Source/_ASCollectionGalleryLayoutItem.h deleted file mode 100644 index 21df3bcdc0..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASCollectionGalleryLayoutItem.h +++ /dev/null @@ -1,38 +0,0 @@ -// -// _ASCollectionGalleryLayoutItem.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import -#import -#import - -@class ASCollectionElement; - -NS_ASSUME_NONNULL_BEGIN - -/** - * A dummy item that represents a collection element to participate in the collection layout calculation process - * without triggering measurement on the actual node of the collection element. - * - * This item always has a fixed size that is the item size passed to it. - */ -AS_SUBCLASSING_RESTRICTED -@interface _ASGalleryLayoutItem : NSObject - -@property (nonatomic, readonly) CGSize itemSize; -@property (nonatomic, weak, readonly) ASCollectionElement *collectionElement; - -- (instancetype)initWithItemSize:(CGSize)itemSize collectionElement:(ASCollectionElement *)collectionElement; -- (instancetype)init __unavailable; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/_ASCollectionGalleryLayoutItem.mm b/submodules/AsyncDisplayKit/Source/_ASCollectionGalleryLayoutItem.mm deleted file mode 100644 index 582d7983cc..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASCollectionGalleryLayoutItem.mm +++ /dev/null @@ -1,85 +0,0 @@ -// -// _ASCollectionGalleryLayoutItem.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#ifndef MINIMAL_ASDK - -#import - -#import -#import -#import -#import -#import - -@implementation _ASGalleryLayoutItem { - std::atomic _primitiveTraitCollection; -} - -@synthesize style; - -- (instancetype)initWithItemSize:(CGSize)itemSize collectionElement:(ASCollectionElement *)collectionElement -{ - self = [super init]; - if (self) { - ASDisplayNodeAssert(! CGSizeEqualToSize(CGSizeZero, itemSize), @"Item size should not be zero"); - ASDisplayNodeAssertNotNil(collectionElement, @"Collection element should not be nil"); - _itemSize = itemSize; - _collectionElement = collectionElement; - } - return self; -} - -ASLayoutElementStyleExtensibilityForwarding -ASPrimitiveTraitCollectionDefaults - -- (ASTraitCollection *)asyncTraitCollection -{ - ASDisplayNodeAssertNotSupported(); - return nil; -} - -- (ASLayoutElementType)layoutElementType -{ - return ASLayoutElementTypeLayoutSpec; -} - -- (NSArray> *)sublayoutElements -{ - ASDisplayNodeAssertNotSupported(); - return nil; -} - -- (BOOL)implementsLayoutMethod -{ - return YES; -} - -ASLayoutElementLayoutCalculationDefaults - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - ASDisplayNodeAssert(CGSizeEqualToSize(_itemSize, ASSizeRangeClamp(constrainedSize, _itemSize)), - @"Item size %@ can't fit within the bounds of constrained size %@", NSStringFromCGSize(_itemSize), NSStringFromASSizeRange(constrainedSize)); - return [ASLayout layoutWithLayoutElement:self size:_itemSize]; -} - -#pragma mark - ASLayoutElementAsciiArtProtocol - -- (NSString *)asciiArtString -{ - return [ASLayoutSpec asciiArtStringForChildren:@[] parentName:[self asciiArtName]]; -} - -- (NSString *)asciiArtName -{ - return [NSMutableString stringWithCString:object_getClassName(self) encoding:NSASCIIStringEncoding]; -} - -@end - -#endif diff --git a/submodules/AsyncDisplayKit/Source/_ASCollectionReusableView.h b/submodules/AsyncDisplayKit/Source/_ASCollectionReusableView.h deleted file mode 100644 index bb38bb25bf..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASCollectionReusableView.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// _ASCollectionReusableView.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 -#import - -@class ASCellNode, ASCollectionElement; - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED // Note: ASDynamicCastStrict is used on instances of this class based on this restriction. -@interface _ASCollectionReusableView : UICollectionReusableView - -@property (nullable, nonatomic, readonly) ASCellNode *node; -@property (nullable, nonatomic) ASCollectionElement *element; -@property (nullable, nonatomic) UICollectionViewLayoutAttributes *layoutAttributes; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/_ASCollectionReusableView.mm b/submodules/AsyncDisplayKit/Source/_ASCollectionReusableView.mm deleted file mode 100644 index 6f5445654f..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASCollectionReusableView.mm +++ /dev/null @@ -1,93 +0,0 @@ -// -// _ASCollectionReusableView.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 - -#import -#import - -@implementation _ASCollectionReusableView - -- (ASCellNode *)node -{ - return self.element.node; -} - -- (void)setElement:(ASCollectionElement *)element -{ - ASDisplayNodeAssertMainThread(); - element.node.layoutAttributes = _layoutAttributes; - _element = element; -} - -- (void)setLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes -{ - _layoutAttributes = layoutAttributes; - self.node.layoutAttributes = layoutAttributes; -} - -- (void)prepareForReuse -{ - self.layoutAttributes = nil; - - // Need to clear element before UIKit calls setSelected:NO / setHighlighted:NO on its cells - self.element = nil; - [super prepareForReuse]; -} - -/** - * In the initial case, this is called by UICollectionView during cell dequeueing, before - * we get a chance to assign a node to it, so we must be sure to set these layout attributes - * on our node when one is next assigned to us in @c setNode: . Since there may be cases when we _do_ already - * have our node assigned e.g. during a layout update for existing cells, we also attempt - * to update it now. - */ -- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes -{ - self.layoutAttributes = layoutAttributes; -} - -/** - * Keep our node filling our content view. - */ -- (void)layoutSubviews -{ - [super layoutSubviews]; - self.node.frame = self.bounds; -} - -@end - -/** - * A category that makes _ASCollectionReusableView conform to IGListBindable. - * - * We don't need to do anything to bind the view model – the cell node - * serves the same purpose. - */ -#if __has_include() - -#import - -@interface _ASCollectionReusableView (IGListBindable) -@end - -@implementation _ASCollectionReusableView (IGListBindable) - -- (void)bindViewModel:(id)viewModel -{ - // nop -} - -@end - -#endif - -#endif diff --git a/submodules/AsyncDisplayKit/Source/_ASCollectionViewCell.h b/submodules/AsyncDisplayKit/Source/_ASCollectionViewCell.h deleted file mode 100644 index cf08d0d508..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASCollectionViewCell.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// _ASCollectionViewCell.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 -#import -#import - -@class ASCollectionElement; - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED // Note: ASDynamicCastStrict is used on instances of this class based on this restriction. -@interface _ASCollectionViewCell : UICollectionViewCell - -@property (nonatomic, nullable) ASCollectionElement *element; -@property (nullable, nonatomic, readonly) ASCellNode *node; -@property (nonatomic, nullable) UICollectionViewLayoutAttributes *layoutAttributes; - -/** - * Whether or not this cell is interested in cell node visibility events. - * -cellNodeVisibilityEvent:inScrollView: should be called only if this property is YES. - */ -@property (nonatomic, readonly) BOOL consumesCellNodeVisibilityEvents; - -- (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(UIScrollView *)scrollView; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/submodules/AsyncDisplayKit/Source/_ASCollectionViewCell.mm b/submodules/AsyncDisplayKit/Source/_ASCollectionViewCell.mm deleted file mode 100644 index 6051c5be50..0000000000 --- a/submodules/AsyncDisplayKit/Source/_ASCollectionViewCell.mm +++ /dev/null @@ -1,149 +0,0 @@ -// -// _ASCollectionViewCell.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 -#import - -#import -#import -#import "Private/ASInternalHelpers.h" - -@implementation _ASCollectionViewCell - -- (ASCellNode *)node -{ - return self.element.node; -} - -- (void)setElement:(ASCollectionElement *)element -{ - ASDisplayNodeAssertMainThread(); - ASCellNode *node = element.node; - node.layoutAttributes = _layoutAttributes; - _element = element; - - [node __setSelectedFromUIKit:self.selected]; - [node __setHighlightedFromUIKit:self.highlighted]; -} - -- (BOOL)consumesCellNodeVisibilityEvents -{ - ASCellNode *node = self.node; - if (node == nil) { - return NO; - } - return ASSubclassOverridesSelector([ASCellNode class], [node class], @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:)); -} - -- (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(UIScrollView *)scrollView -{ - [self.node cellNodeVisibilityEvent:event inScrollView:scrollView withCellFrame:self.frame]; -} - -- (void)setSelected:(BOOL)selected -{ - [super setSelected:selected]; - [self.node __setSelectedFromUIKit:selected]; -} - -- (void)setHighlighted:(BOOL)highlighted -{ - [super setHighlighted:highlighted]; - [self.node __setHighlightedFromUIKit:highlighted]; -} - -- (void)setLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes -{ - _layoutAttributes = layoutAttributes; - self.node.layoutAttributes = layoutAttributes; -} - -- (void)prepareForReuse -{ - self.layoutAttributes = nil; - - // Need to clear element before UIKit calls setSelected:NO / setHighlighted:NO on its cells - self.element = nil; - [super prepareForReuse]; -} - -/** - * In the initial case, this is called by UICollectionView during cell dequeueing, before - * we get a chance to assign a node to it, so we must be sure to set these layout attributes - * on our node when one is next assigned to us in @c setNode: . Since there may be cases when we _do_ already - * have our node assigned e.g. during a layout update for existing cells, we also attempt - * to update it now. - */ -- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes -{ - [super applyLayoutAttributes:layoutAttributes]; - self.layoutAttributes = layoutAttributes; -} - -/** - * Keep our node filling our content view. - */ -- (void)layoutSubviews -{ - [super layoutSubviews]; - self.node.frame = self.contentView.bounds; -} - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event -{ - /** - * The documentation for hitTest:withEvent: on an UIView explicitly states the fact that: - * it ignores view objects that are hidden, that have disabled user interactions, or have an - * alpha level less than 0.01. - * To be able to determine if the collection view cell should skip going further down the tree - * based on the states above we use a valid point within the cells bounds and check the - * superclass hitTest:withEvent: implementation. If this returns a valid value we can go on with - * checking the node as it's expected to not be in one of these states. - */ - if (![super hitTest:self.bounds.origin withEvent:event]) { - return nil; - } - - CGPoint pointOnNode = [self.node.view convertPoint:point fromView:self]; - return [self.node hitTest:pointOnNode withEvent:event]; -} - -- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event -{ - CGPoint pointOnNode = [self.node.view convertPoint:point fromView:self]; - return [self.node pointInside:pointOnNode withEvent:event]; -} - -@end - -/** - * A category that makes _ASCollectionViewCell conform to IGListBindable. - * - * We don't need to do anything to bind the view model – the cell node - * serves the same purpose. - */ -#if __has_include() - -#import - -@interface _ASCollectionViewCell (IGListBindable) -@end - -@implementation _ASCollectionViewCell (IGListBindable) - -- (void)bindViewModel:(id)viewModel -{ - // nop -} - -@end - -#endif -#endif diff --git a/submodules/AsyncDisplayKit/Source/asdkmodule.modulemap b/submodules/AsyncDisplayKit/Source/asdkmodule.modulemap deleted file mode 100644 index fd7d49e620..0000000000 --- a/submodules/AsyncDisplayKit/Source/asdkmodule.modulemap +++ /dev/null @@ -1,19 +0,0 @@ -framework module AsyncDisplayKit { - umbrella header "AsyncDisplayKit.h" - - export * - module * { - export * - } - - explicit module ASControlNode_Subclasses { - header "ASControlNode+Subclasses.h" - export * - } - - explicit module ASDisplayNode_Subclasses { - header "ASDisplayNode+Subclasses.h" - export * - } - -} diff --git a/submodules/Display/Display/CASeeThroughTracingLayer.h b/submodules/Display/Display/CASeeThroughTracingLayer.h deleted file mode 100644 index 8c6ff3de8e..0000000000 --- a/submodules/Display/Display/CASeeThroughTracingLayer.h +++ /dev/null @@ -1,9 +0,0 @@ -#import - -@interface CASeeThroughTracingLayer : CALayer - -@end - -@interface CASeeThroughTracingView : UIView - -@end diff --git a/submodules/Display/Display/CASeeThroughTracingLayer.m b/submodules/Display/Display/CASeeThroughTracingLayer.m deleted file mode 100644 index 79ff8d3631..0000000000 --- a/submodules/Display/Display/CASeeThroughTracingLayer.m +++ /dev/null @@ -1,57 +0,0 @@ -#import "CASeeThroughTracingLayer.h" - -@interface CASeeThroughTracingLayer () { - CGPoint _parentOffset; -} - -@end - -@implementation CASeeThroughTracingLayer - -- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key { - [super addAnimation:anim forKey:key]; -} - -- (void)setFrame:(CGRect)frame { - [super setFrame:frame]; - - [self _mirrorTransformToSublayers]; -} - -- (void)setBounds:(CGRect)bounds { - [super setBounds:bounds]; - - [self _mirrorTransformToSublayers]; -} - -- (void)setPosition:(CGPoint)position { - [super setPosition:position]; - - [self _mirrorTransformToSublayers]; -} - -- (void)_mirrorTransformToSublayers { - CGRect bounds = self.bounds; - CGPoint position = self.position; - - CGPoint sublayerParentOffset = _parentOffset; - sublayerParentOffset.x += position.x - (bounds.size.width) / 2.0f; - sublayerParentOffset.y += position.y - (bounds.size.width) / 2.0f; - - for (CALayer *sublayer in self.sublayers) { - if ([sublayer isKindOfClass:[CASeeThroughTracingLayer class]]) { - ((CASeeThroughTracingLayer *)sublayer)->_parentOffset = sublayerParentOffset; - [(CASeeThroughTracingLayer *)sublayer _mirrorTransformToSublayers]; - } - } -} - -@end - -@implementation CASeeThroughTracingView - -+ (Class)layerClass { - return [CASeeThroughTracingLayer class]; -} - -@end diff --git a/submodules/Display/Display/CATracingLayer.h b/submodules/Display/Display/CATracingLayer.h deleted file mode 100644 index 22037b19a4..0000000000 --- a/submodules/Display/Display/CATracingLayer.h +++ /dev/null @@ -1,34 +0,0 @@ -#import - -@interface CATracingLayer : CALayer - -@end - -@interface CATracingLayerInfo : NSObject - -@property (nonatomic, readonly) bool shouldBeAdjustedToInverseTransform; -@property (nonatomic, weak, readonly) id _Nullable userData; -@property (nonatomic, readonly) int32_t tracingTag; -@property (nonatomic, readonly) int32_t disableChildrenTracingTags; - -- (instancetype _Nonnull)initWithShouldBeAdjustedToInverseTransform:(bool)shouldBeAdjustedToInverseTransform userData:(id _Nullable)userData tracingTag:(int32_t)tracingTag disableChildrenTracingTags:(int32_t)disableChildrenTracingTags; - -@end - -@interface CALayer (Tracing) - -- (CATracingLayerInfo * _Nullable)traceableInfo; -- (void)setTraceableInfo:(CATracingLayerInfo * _Nullable)info; - -- (bool)hasPositionOrOpacityAnimations; -- (bool)hasPositionAnimations; - -- (void)setInvalidateTracingSublayers:(void (^_Nullable)())block; -- (NSArray *> * _Nonnull)traceableLayerSurfacesWithTag:(int32_t)tracingTag; -- (void)adjustTraceableLayerTransforms:(CGSize)offset; - -- (void)setPositionAnimationMirrorTarget:(CALayer * _Nullable)animationMirrorTarget; - -- (void)invalidateUpTheTree; - -@end diff --git a/submodules/Display/Display/CATracingLayer.m b/submodules/Display/Display/CATracingLayer.m deleted file mode 100644 index dd463b5b56..0000000000 --- a/submodules/Display/Display/CATracingLayer.m +++ /dev/null @@ -1,364 +0,0 @@ -#import "CATracingLayer.h" - -#import "RuntimeUtils.h" - -static void *CATracingLayerInvalidatedKey = &CATracingLayerInvalidatedKey; -static void *CATracingLayerIsInvalidatedBlock = &CATracingLayerIsInvalidatedBlock; -static void *CATracingLayerTraceableInfoKey = &CATracingLayerTraceableInfoKey; -static void *CATracingLayerPositionAnimationMirrorTarget = &CATracingLayerPositionAnimationMirrorTarget; - -@implementation CALayer (Tracing) - -- (void)setInvalidateTracingSublayers:(void (^_Nullable)())block { - [self setAssociatedObject:[block copy] forKey:CATracingLayerIsInvalidatedBlock]; -} - -- (void (^_Nullable)())invalidateTracingSublayers { - return [self associatedObjectForKey:CATracingLayerIsInvalidatedBlock]; -} - -- (bool)isTraceable { - return [self associatedObjectForKey:CATracingLayerTraceableInfoKey] != nil || [self isKindOfClass:[CATracingLayer class]]; -} - -- (CATracingLayerInfo * _Nullable)traceableInfo { - return [self associatedObjectForKey:CATracingLayerTraceableInfoKey]; -} - -- (void)setTraceableInfo:(CATracingLayerInfo * _Nullable)info { - [self setAssociatedObject:info forKey:CATracingLayerTraceableInfoKey]; -} - -- (bool)hasPositionOrOpacityAnimations { - return [self animationForKey:@"position"] != nil || [self animationForKey:@"bounds"] != nil || [self animationForKey:@"sublayerTransform"] != nil || [self animationForKey:@"opacity"] != nil; -} - -- (bool)hasPositionAnimations { - return [self animationForKey:@"position"] != nil || [self animationForKey:@"bounds"] != nil; -} - -static void traceLayerSurfaces(int32_t tracingTag, int depth, CALayer * _Nonnull layer, NSMutableDictionary *> *layersByDepth, bool skipIfNoTraceableSublayers) { - bool hadTraceableSublayers = false; - for (CALayer *sublayer in layer.sublayers.reverseObjectEnumerator) { - CATracingLayerInfo *sublayerTraceableInfo = [sublayer traceableInfo]; - if (sublayerTraceableInfo != nil && sublayerTraceableInfo.tracingTag == tracingTag) { - NSMutableArray *array = layersByDepth[@(depth)]; - if (array == nil) { - array = [[NSMutableArray alloc] init]; - layersByDepth[@(depth)] = array; - } - [array addObject:sublayer]; - hadTraceableSublayers = true; - } - if (sublayerTraceableInfo.disableChildrenTracingTags & tracingTag) { - return; - } - } - - if (!skipIfNoTraceableSublayers || !hadTraceableSublayers) { - for (CALayer *sublayer in layer.sublayers.reverseObjectEnumerator) { - if ([sublayer isKindOfClass:[CATracingLayer class]]) { - traceLayerSurfaces(tracingTag, depth + 1, sublayer, layersByDepth, hadTraceableSublayers); - } - } - } -} - -- (NSArray *> * _Nonnull)traceableLayerSurfacesWithTag:(int32_t)tracingTag { - NSMutableDictionary *> *layersByDepth = [[NSMutableDictionary alloc] init]; - - traceLayerSurfaces(tracingTag, 0, self, layersByDepth, false); - - NSMutableArray *> *result = [[NSMutableArray alloc] init]; - - for (id key in [[layersByDepth allKeys] sortedArrayUsingSelector:@selector(compare:)]) { - [result addObject:layersByDepth[key]]; - } - - return result; -} - -- (void)adjustTraceableLayerTransforms:(CGSize)offset { - CGRect frame = self.frame; - CGSize sublayerOffset = CGSizeMake(frame.origin.x + offset.width, frame.origin.y + offset.height); - for (CALayer *sublayer in self.sublayers) { - CATracingLayerInfo *sublayerTraceableInfo = [sublayer traceableInfo]; - if (sublayerTraceableInfo != nil && sublayerTraceableInfo.shouldBeAdjustedToInverseTransform) { - sublayer.sublayerTransform = CATransform3DMakeTranslation(-sublayerOffset.width, -sublayerOffset.height, 0.0f); - } else if ([sublayer isKindOfClass:[CATracingLayer class]]) { - [(CATracingLayer *)sublayer adjustTraceableLayerTransforms:sublayerOffset]; - } - } -} - -- (CALayer * _Nullable)animationMirrorTarget { - return [self associatedObjectForKey:CATracingLayerPositionAnimationMirrorTarget]; -} - -- (void)setPositionAnimationMirrorTarget:(CALayer * _Nullable)animationMirrorTarget { - [self setAssociatedObject:animationMirrorTarget forKey:CATracingLayerPositionAnimationMirrorTarget associationPolicy:NSObjectAssociationPolicyRetain]; -} - -- (void)invalidateUpTheTree { - CALayer *superlayer = self; - while (true) { - if (superlayer == nil) { - break; - } - - void (^block)() = [superlayer invalidateTracingSublayers]; - if (block != nil) { - block(); - } - - superlayer = superlayer.superlayer; - } -} - -@end - -@interface CATracingLayerAnimationDelegate : NSObject { - id _delegate; - void (^_animationStopped)(); -} - -@end - -@implementation CATracingLayerAnimationDelegate - -- (instancetype)initWithDelegate:(id)delegate animationStopped:(void (^_Nonnull)())animationStopped { - _delegate = delegate; - _animationStopped = [animationStopped copy]; - return self; -} - -- (void)animationDidStart:(CAAnimation *)anim { - if ([_delegate respondsToSelector:@selector(animationDidStart:)]) { - [(id)_delegate animationDidStart:anim]; - } -} - -- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { - if ([_delegate respondsToSelector:@selector(animationDidStop:finished:)]) { - [(id)_delegate animationDidStop:anim finished:flag]; - } - - if (_animationStopped) { - _animationStopped(); - } -} - -@end - -@interface CATracingLayer () - -@property (nonatomic) bool isInvalidated; - -@end - -@implementation CATracingLayer - -- (void)setNeedsDisplay { -} - -- (void)displayIfNeeded { -} - -- (bool)isInvalidated { - return [[self associatedObjectForKey:CATracingLayerInvalidatedKey] intValue] != 0; -} - -- (void)setIsInvalidated:(bool)isInvalidated { - [self setAssociatedObject: isInvalidated ? @1 : @0 forKey:CATracingLayerInvalidatedKey]; -} - -- (void)setPosition:(CGPoint)position { - [super setPosition:position]; - - [self invalidateUpTheTree]; -} - -- (void)setOpacity:(float)opacity { - [super setOpacity:opacity]; - - [self invalidateUpTheTree]; -} - -- (void)addSublayer:(CALayer *)layer { - [super addSublayer:layer]; - - if ([layer isTraceable] || [layer isKindOfClass:[CATracingLayer class]]) { - [self invalidateUpTheTree]; - } -} - -- (void)insertSublayer:(CALayer *)layer atIndex:(unsigned)idx { - [super insertSublayer:layer atIndex:idx]; - - if ([layer isTraceable] || [layer isKindOfClass:[CATracingLayer class]]) { - [self invalidateUpTheTree]; - } -} - -- (void)insertSublayer:(CALayer *)layer below:(nullable CALayer *)sibling { - [super insertSublayer:layer below:sibling]; - - if ([layer isTraceable] || [layer isKindOfClass:[CATracingLayer class]]) { - [self invalidateUpTheTree]; - } -} - -- (void)insertSublayer:(CALayer *)layer above:(nullable CALayer *)sibling { - [super insertSublayer:layer above:sibling]; - - if ([layer isTraceable] || [layer isKindOfClass:[CATracingLayer class]]) { - [self invalidateUpTheTree]; - } -} - -- (void)replaceSublayer:(CALayer *)layer with:(CALayer *)layer2 { - [super replaceSublayer:layer with:layer2]; - - if ([layer isTraceable] || [layer2 isTraceable]) { - [self invalidateUpTheTree]; - } -} - -- (void)removeFromSuperlayer { - if ([self isTraceable]) { - [self invalidateUpTheTree]; - } - - [super removeFromSuperlayer]; -} - -- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key { - if ([anim isKindOfClass:[CABasicAnimation class]]) { - if (false && [key isEqualToString:@"bounds.origin.y"]) { - CABasicAnimation *animCopy = [anim copy]; - CGFloat from = [animCopy.fromValue floatValue]; - CGFloat to = [animCopy.toValue floatValue]; - - animCopy.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0.0, to - from, 0.0f)]; - animCopy.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; - animCopy.keyPath = @"sublayerTransform"; - - __weak CATracingLayer *weakSelf = self; - anim.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{ - __strong CATracingLayer *strongSelf = weakSelf; - if (strongSelf != nil) { - [strongSelf invalidateUpTheTree]; - } - }]; - - [super addAnimation:anim forKey:key]; - - CABasicAnimation *positionAnimCopy = [animCopy copy]; - positionAnimCopy.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0.0, 0.0, 0.0f)]; - positionAnimCopy.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; - positionAnimCopy.additive = true; - positionAnimCopy.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{ - __strong CATracingLayer *strongSelf = weakSelf; - if (strongSelf != nil) { - [strongSelf invalidateUpTheTree]; - } - }]; - - [self invalidateUpTheTree]; - - [self mirrorAnimationDownTheTree:animCopy key:@"sublayerTransform"]; - [self mirrorPositionAnimationDownTheTree:positionAnimCopy key:@"sublayerTransform"]; - } else if ([key isEqualToString:@"position"]) { - CABasicAnimation *animCopy = [anim copy]; - CGPoint from = [animCopy.fromValue CGPointValue]; - CGPoint to = [animCopy.toValue CGPointValue]; - - animCopy.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(to.x - from.x, to.y - from.y, 0.0f)]; - animCopy.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; - animCopy.keyPath = @"sublayerTransform"; - - __weak CATracingLayer *weakSelf = self; - anim.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{ - __strong CATracingLayer *strongSelf = weakSelf; - if (strongSelf != nil) { - [strongSelf invalidateUpTheTree]; - } - }]; - - [super addAnimation:anim forKey:key]; - - CABasicAnimation *positionAnimCopy = [animCopy copy]; - positionAnimCopy.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(-to.x + from.x, 0.0, 0.0f)]; - positionAnimCopy.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; - positionAnimCopy.additive = true; - positionAnimCopy.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{ - __strong CATracingLayer *strongSelf = weakSelf; - if (strongSelf != nil) { - [strongSelf invalidateUpTheTree]; - } - }]; - - [self invalidateUpTheTree]; - - [self mirrorAnimationDownTheTree:animCopy key:@"sublayerTransform"]; - [self mirrorPositionAnimationDownTheTree:positionAnimCopy key:@"sublayerTransform"]; - } else if ([key isEqualToString:@"opacity"]) { - __weak CATracingLayer *weakSelf = self; - anim.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{ - __strong CATracingLayer *strongSelf = weakSelf; - if (strongSelf != nil) { - [strongSelf invalidateUpTheTree]; - } - }]; - - [super addAnimation:anim forKey:key]; - - [self invalidateUpTheTree]; - } else { - [super addAnimation:anim forKey:key]; - } - } else { - [super addAnimation:anim forKey:key]; - } -} - -- (void)mirrorPositionAnimationDownTheTree:(CAAnimation *)animation key:(NSString *)key { - if ([animation isKindOfClass:[CABasicAnimation class]]) { - if ([((CABasicAnimation *)animation).keyPath isEqualToString:@"sublayerTransform"]) { - CALayer *positionAnimationMirrorTarget = [self animationMirrorTarget]; - if (positionAnimationMirrorTarget != nil) { - [positionAnimationMirrorTarget addAnimation:[animation copy] forKey:key]; - } - } - } -} - -- (void)mirrorAnimationDownTheTree:(CAAnimation *)animation key:(NSString *)key { - for (CALayer *sublayer in self.sublayers) { - CATracingLayerInfo *traceableInfo = [sublayer traceableInfo]; - if (traceableInfo != nil && traceableInfo.shouldBeAdjustedToInverseTransform) { - [sublayer addAnimation:[animation copy] forKey:key]; - } - - if ([sublayer isKindOfClass:[CATracingLayer class]]) { - [(CATracingLayer *)sublayer mirrorAnimationDownTheTree:animation key:key]; - } - } -} - -@end - -@implementation CATracingLayerInfo - -- (instancetype _Nonnull)initWithShouldBeAdjustedToInverseTransform:(bool)shouldBeAdjustedToInverseTransform userData:(id _Nullable)userData tracingTag:(int32_t)tracingTag disableChildrenTracingTags:(int32_t)disableChildrenTracingTags { - self = [super init]; - if (self != nil) { - _shouldBeAdjustedToInverseTransform = shouldBeAdjustedToInverseTransform; - _userData = userData; - _tracingTag = tracingTag; - _disableChildrenTracingTags = disableChildrenTracingTags; - } - return self; -} - -@end diff --git a/submodules/Display/Display/Display.h b/submodules/Display/Display/Display.h deleted file mode 100644 index e013c37f97..0000000000 --- a/submodules/Display/Display/Display.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// Display.h -// Display -// -// Created by Peter on 29/07/15. -// Copyright © 2015 Telegram. All rights reserved. -// - -#import - -//! Project version number for Display. -FOUNDATION_EXPORT double DisplayVersionNumber; - -//! Project version string for Display. -FOUNDATION_EXPORT const unsigned char DisplayVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import diff --git a/submodules/Display/Display/Info.plist b/submodules/Display/Display/Info.plist deleted file mode 100644 index d3de8eefb6..0000000000 --- a/submodules/Display/Display/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSPrincipalClass - - - diff --git a/submodules/Display/Display/NavigationBarProxy.h b/submodules/Display/Display/NavigationBarProxy.h deleted file mode 100644 index 9936b4726a..0000000000 --- a/submodules/Display/Display/NavigationBarProxy.h +++ /dev/null @@ -1,7 +0,0 @@ -#import - -@interface NavigationBarProxy : UINavigationBar - -@property (nonatomic, copy) void (^setItemsProxy)(NSArray *, NSArray *, bool); - -@end diff --git a/submodules/Display/Display/NavigationBarProxy.m b/submodules/Display/Display/NavigationBarProxy.m deleted file mode 100644 index 9f5700c13b..0000000000 --- a/submodules/Display/Display/NavigationBarProxy.m +++ /dev/null @@ -1,67 +0,0 @@ -#import "NavigationBarProxy.h" - -@interface NavigationBarProxy () -{ - NSArray *_items; -} - -@end - -@implementation NavigationBarProxy - -- (instancetype)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self != nil) - { - } - return self; -} - -- (void)pushNavigationItem:(UINavigationItem *)item animated:(BOOL)animated -{ - [self setItems:[[self items] arrayByAddingObject:item] animated:animated]; -} - -- (UINavigationItem *)popNavigationItemAnimated:(BOOL)animated -{ - NSMutableArray *items = [[NSMutableArray alloc] initWithArray:[self items]]; - UINavigationItem *lastItem = [items lastObject]; - [items removeLastObject]; - [self setItems:items animated:animated]; - return lastItem; -} - -- (UINavigationItem *)topItem -{ - return [[self items] lastObject]; -} - -- (UINavigationItem *)backItem -{ - NSLog(@"backItem"); - return nil; -} - -- (NSArray *)items -{ - if (_items == nil) - return @[]; - return _items; -} - -- (void)setItems:(NSArray *)items -{ - [self setItems:items animated:false]; -} - -- (void)setItems:(NSArray *)items animated:(BOOL)animated -{ - NSArray *previousItems = _items; - _items = items; - - if (_setItemsProxy) - _setItemsProxy(previousItems, items, animated); -} - -@end diff --git a/submodules/Display/Display/NavigationControllerProxy.h b/submodules/Display/Display/NavigationControllerProxy.h deleted file mode 100644 index 02e4fb6208..0000000000 --- a/submodules/Display/Display/NavigationControllerProxy.h +++ /dev/null @@ -1,7 +0,0 @@ -#import - -@interface NavigationControllerProxy : UINavigationController - -- (instancetype)init; - -@end diff --git a/submodules/Display/Display/NavigationControllerProxy.m b/submodules/Display/Display/NavigationControllerProxy.m deleted file mode 100644 index 6a86a5062f..0000000000 --- a/submodules/Display/Display/NavigationControllerProxy.m +++ /dev/null @@ -1,15 +0,0 @@ -#import "NavigationControllerProxy.h" - -#import "NavigationBarProxy.h" - -@implementation NavigationControllerProxy - -- (instancetype)init -{ - self = [super initWithNavigationBarClass:[NavigationBarProxy class] toolbarClass:[UIToolbar class]]; - if (self != nil) { - } - return self; -} - -@end diff --git a/submodules/SSignalKit/BUILD b/submodules/SSignalKit/BUILD new file mode 100644 index 0000000000..e69de29bb2 diff --git a/submodules/SSignalKit/SSignalKit/BUILD b/submodules/SSignalKit/SSignalKit/BUILD new file mode 100644 index 0000000000..5a980e8242 --- /dev/null +++ b/submodules/SSignalKit/SSignalKit/BUILD @@ -0,0 +1,21 @@ + +objc_library( + name = "SSignalKit", + enable_modules = True, + module_name = "SSignalKit", + srcs = glob([ + "Source/SSignalKit/*.m", + ]), + hdrs = glob([ + "Source/SSignalKit/*.h", + ]), + includes = [ + "Source", + ], + sdk_frameworks = [ + "Foundation", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/SSignalKit/SSignalKit/SAtomic.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SAtomic.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SAtomic.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SAtomic.h diff --git a/submodules/SSignalKit/SSignalKit/SAtomic.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SAtomic.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SAtomic.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SAtomic.m diff --git a/submodules/SSignalKit/SSignalKit/SBag.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SBag.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SBag.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SBag.h diff --git a/submodules/SSignalKit/SSignalKit/SBag.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SBag.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SBag.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SBag.m diff --git a/submodules/SSignalKit/SSignalKit/SBlockDisposable.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SBlockDisposable.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SBlockDisposable.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SBlockDisposable.h diff --git a/submodules/SSignalKit/SSignalKit/SBlockDisposable.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SBlockDisposable.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SBlockDisposable.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SBlockDisposable.m diff --git a/submodules/SSignalKit/SSignalKit/SDisposable.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SDisposable.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SDisposable.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SDisposable.h diff --git a/submodules/SSignalKit/SSignalKit/SDisposableSet.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SDisposableSet.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SDisposableSet.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SDisposableSet.h diff --git a/submodules/SSignalKit/SSignalKit/SDisposableSet.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SDisposableSet.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SDisposableSet.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SDisposableSet.m diff --git a/submodules/SSignalKit/SSignalKit/SMetaDisposable.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SMetaDisposable.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SMetaDisposable.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SMetaDisposable.h diff --git a/submodules/SSignalKit/SSignalKit/SMetaDisposable.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SMetaDisposable.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SMetaDisposable.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SMetaDisposable.m diff --git a/submodules/SSignalKit/SSignalKit/SMulticastSignalManager.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SMulticastSignalManager.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SMulticastSignalManager.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SMulticastSignalManager.h diff --git a/submodules/SSignalKit/SSignalKit/SMulticastSignalManager.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SMulticastSignalManager.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SMulticastSignalManager.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SMulticastSignalManager.m diff --git a/submodules/SSignalKit/SSignalKit/SQueue.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SQueue.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SQueue.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SQueue.h diff --git a/submodules/SSignalKit/SSignalKit/SQueue.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SQueue.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SQueue.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SQueue.m diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Accumulate.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Accumulate.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Accumulate.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Accumulate.h diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Accumulate.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Accumulate.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Accumulate.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Accumulate.m diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Catch.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Catch.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Catch.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Catch.h diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Catch.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Catch.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Catch.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Catch.m diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Combine.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Combine.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Combine.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Combine.h diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Combine.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Combine.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Combine.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Combine.m diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Dispatch.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Dispatch.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Dispatch.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Dispatch.h diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Dispatch.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Dispatch.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Dispatch.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Dispatch.m diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Mapping.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Mapping.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Mapping.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Mapping.h diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Mapping.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Mapping.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Mapping.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Mapping.m diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Meta.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Meta.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Meta.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Meta.h diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Meta.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Meta.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Meta.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Meta.m diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Multicast.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Multicast.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Multicast.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Multicast.h diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Multicast.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Multicast.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Multicast.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Multicast.m diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Pipe.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Pipe.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Pipe.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Pipe.h diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Pipe.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Pipe.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Pipe.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Pipe.m diff --git a/submodules/SSignalKit/SSignalKit/SSignal+SideEffects.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+SideEffects.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+SideEffects.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+SideEffects.h diff --git a/submodules/SSignalKit/SSignalKit/SSignal+SideEffects.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+SideEffects.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+SideEffects.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+SideEffects.m diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Single.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Single.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Single.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Single.h diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Single.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Single.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Single.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Single.m diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Take.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Take.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Take.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Take.h diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Take.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Take.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Take.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Take.m diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Timing.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Timing.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Timing.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Timing.h diff --git a/submodules/SSignalKit/SSignalKit/SSignal+Timing.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Timing.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal+Timing.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal+Timing.m diff --git a/submodules/SSignalKit/SSignalKit/SSignal.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal.h diff --git a/submodules/SSignalKit/SSignalKit/SSignal.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignal.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignal.m diff --git a/submodules/SSignalKit/SSignalKit/SSignalKit.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignalKit.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSignalKit.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSignalKit.h diff --git a/submodules/SSignalKit/SSignalKit/SSubscriber.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSubscriber.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSubscriber.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSubscriber.h diff --git a/submodules/SSignalKit/SSignalKit/SSubscriber.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSubscriber.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SSubscriber.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SSubscriber.m diff --git a/submodules/SSignalKit/SSignalKit/SThreadPool.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SThreadPool.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SThreadPool.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SThreadPool.h diff --git a/submodules/SSignalKit/SSignalKit/SThreadPool.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SThreadPool.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SThreadPool.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SThreadPool.m diff --git a/submodules/SSignalKit/SSignalKit/SThreadPoolQueue.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SThreadPoolQueue.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SThreadPoolQueue.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SThreadPoolQueue.h diff --git a/submodules/SSignalKit/SSignalKit/SThreadPoolQueue.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SThreadPoolQueue.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SThreadPoolQueue.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SThreadPoolQueue.m diff --git a/submodules/SSignalKit/SSignalKit/SThreadPoolTask.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SThreadPoolTask.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SThreadPoolTask.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SThreadPoolTask.h diff --git a/submodules/SSignalKit/SSignalKit/SThreadPoolTask.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SThreadPoolTask.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SThreadPoolTask.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SThreadPoolTask.m diff --git a/submodules/SSignalKit/SSignalKit/STimer.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/STimer.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/STimer.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/STimer.h diff --git a/submodules/SSignalKit/SSignalKit/STimer.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/STimer.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/STimer.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/STimer.m diff --git a/submodules/SSignalKit/SSignalKit/SVariable.h b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SVariable.h similarity index 100% rename from submodules/SSignalKit/SSignalKit/SVariable.h rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SVariable.h diff --git a/submodules/SSignalKit/SSignalKit/SVariable.m b/submodules/SSignalKit/SSignalKit/Source/SSignalKit/SVariable.m similarity index 100% rename from submodules/SSignalKit/SSignalKit/SVariable.m rename to submodules/SSignalKit/SSignalKit/Source/SSignalKit/SVariable.m diff --git a/submodules/SSignalKit/SSignalKitTests/DeallocatingObject.h b/submodules/SSignalKit/SSignalKitTests/DeallocatingObject.h deleted file mode 100644 index 00f3c9cc8b..0000000000 --- a/submodules/SSignalKit/SSignalKitTests/DeallocatingObject.h +++ /dev/null @@ -1,7 +0,0 @@ -#import - -@interface DeallocatingObject : NSObject - -- (instancetype)initWithDeallocated:(bool *)deallocated; - -@end diff --git a/submodules/SSignalKit/SSignalKitTests/DeallocatingObject.m b/submodules/SSignalKit/SSignalKitTests/DeallocatingObject.m deleted file mode 100644 index 38cff658e6..0000000000 --- a/submodules/SSignalKit/SSignalKitTests/DeallocatingObject.m +++ /dev/null @@ -1,27 +0,0 @@ -#import "DeallocatingObject.h" - -@interface DeallocatingObject () -{ - bool *_deallocated; -} - -@end - -@implementation DeallocatingObject - -- (instancetype)initWithDeallocated:(bool *)deallocated -{ - self = [super init]; - if (self != nil) - { - _deallocated = deallocated; - } - return self; -} - -- (void)dealloc -{ - *_deallocated = true; -} - -@end diff --git a/submodules/SSignalKit/SSignalKitTests/Info.plist b/submodules/SSignalKit/SSignalKitTests/Info.plist deleted file mode 100644 index ba72822e87..0000000000 --- a/submodules/SSignalKit/SSignalKitTests/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - - diff --git a/submodules/SSignalKit/SSignalKitTests/SDisposableTests.m b/submodules/SSignalKit/SSignalKitTests/SDisposableTests.m deleted file mode 100644 index 3c71105e4e..0000000000 --- a/submodules/SSignalKit/SSignalKitTests/SDisposableTests.m +++ /dev/null @@ -1,318 +0,0 @@ -#if __IPHONE_OS_VERSION_MIN_REQUIRED -#import -#else -#import -#endif -#import - -#import - -@import SSignalKit; - -#import "DeallocatingObject.h" - -@interface SDisposableTests : XCTestCase - -@end - -@implementation SDisposableTests - -- (void)setUp -{ - [super setUp]; -} - -- (void)tearDown -{ - [super tearDown]; -} - -- (void)testBlockDisposableDisposed -{ - bool deallocated = false; - __block bool disposed = false; - { - DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated]; - dispatch_block_t block = ^{ - [object description]; - disposed = true; - }; - SBlockDisposable *disposable = [[SBlockDisposable alloc] initWithBlock:[block copy]]; - object = nil; - block = nil; - [disposable dispose]; - } - - XCTAssertTrue(deallocated); - XCTAssertTrue(disposed); -} - -- (void)testBlockDisposableNotDisposed -{ - bool deallocated = false; - __block bool disposed = false; - { - DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated]; - dispatch_block_t block = ^{ - [object description]; - disposed = true; - }; - SBlockDisposable *disposable = [[SBlockDisposable alloc] initWithBlock:[block copy]]; - [disposable description]; - } - - XCTAssertTrue(deallocated); - XCTAssertFalse(disposed); -} - -- (void)testMetaDisposableDisposed -{ - bool deallocated = false; - __block bool disposed = false; - { - DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated]; - dispatch_block_t block = ^{ - [object description]; - disposed = true; - }; - SBlockDisposable *blockDisposable = [[SBlockDisposable alloc] initWithBlock:[block copy]]; - - SMetaDisposable *metaDisposable = [[SMetaDisposable alloc] init]; - [metaDisposable setDisposable:blockDisposable]; - [metaDisposable dispose]; - } - - XCTAssertTrue(deallocated); - XCTAssertTrue(disposed); -} - -- (void)testMetaDisposableDisposedMultipleTimes -{ - bool deallocated1 = false; - __block bool disposed1 = false; - bool deallocated2 = false; - __block bool disposed2 = false; - { - DeallocatingObject *object1 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated1]; - dispatch_block_t block1 = ^{ - [object1 description]; - disposed1 = true; - }; - SBlockDisposable *blockDisposable1 = [[SBlockDisposable alloc] initWithBlock:[block1 copy]]; - - DeallocatingObject *object2 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated2]; - dispatch_block_t block2 = ^{ - [object2 description]; - disposed2 = true; - }; - SBlockDisposable *blockDisposable2 = [[SBlockDisposable alloc] initWithBlock:[block2 copy]]; - - SMetaDisposable *metaDisposable = [[SMetaDisposable alloc] init]; - [metaDisposable setDisposable:blockDisposable1]; - [metaDisposable setDisposable:blockDisposable2]; - [metaDisposable dispose]; - } - - XCTAssertTrue(deallocated1); - XCTAssertTrue(disposed1); - XCTAssertTrue(deallocated2); - XCTAssertTrue(disposed2); -} - -- (void)testMetaDisposableNotDisposed -{ - bool deallocated = false; - __block bool disposed = false; - { - DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated]; - dispatch_block_t block = ^{ - [object description]; - disposed = true; - }; - SBlockDisposable *blockDisposable = [[SBlockDisposable alloc] initWithBlock:[block copy]]; - - SMetaDisposable *metaDisposable = [[SMetaDisposable alloc] init]; - [metaDisposable setDisposable:blockDisposable]; - } - - XCTAssertTrue(deallocated); - XCTAssertFalse(disposed); -} - -- (void)testDisposableSetSingleDisposed -{ - bool deallocated = false; - __block bool disposed = false; - { - DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated]; - dispatch_block_t block = ^{ - [object description]; - disposed = true; - }; - SBlockDisposable *blockDisposable = [[SBlockDisposable alloc] initWithBlock:[block copy]]; - - SDisposableSet *disposableSet = [[SDisposableSet alloc] init]; - [disposableSet add:blockDisposable]; - [disposableSet dispose]; - } - - XCTAssertTrue(deallocated); - XCTAssertTrue(disposed); -} - -- (void)testDisposableSetMultipleDisposed -{ - bool deallocated1 = false; - __block bool disposed1 = false; - bool deallocated2 = false; - __block bool disposed2 = false; - { - DeallocatingObject *object1 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated1]; - dispatch_block_t block1 = ^{ - [object1 description]; - disposed1 = true; - }; - SBlockDisposable *blockDisposable1 = [[SBlockDisposable alloc] initWithBlock:[block1 copy]]; - - DeallocatingObject *object2 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated2]; - dispatch_block_t block2 = ^{ - [object2 description]; - disposed2 = true; - }; - SBlockDisposable *blockDisposable2 = [[SBlockDisposable alloc] initWithBlock:[block2 copy]]; - - SDisposableSet *disposableSet = [[SDisposableSet alloc] init]; - [disposableSet add:blockDisposable1]; - [disposableSet add:blockDisposable2]; - [disposableSet dispose]; - } - - XCTAssertTrue(deallocated1); - XCTAssertTrue(disposed1); - XCTAssertTrue(deallocated2); - XCTAssertTrue(disposed2); -} - -- (void)testDisposableSetSingleNotDisposed -{ - bool deallocated = false; - __block bool disposed = false; - { - DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated]; - dispatch_block_t block = ^{ - [object description]; - disposed = true; - }; - SBlockDisposable *blockDisposable = [[SBlockDisposable alloc] initWithBlock:[block copy]]; - - SDisposableSet *disposableSet = [[SDisposableSet alloc] init]; - [disposableSet add:blockDisposable]; - } - - XCTAssertTrue(deallocated); - XCTAssertFalse(disposed); -} - -- (void)testDisposableSetMultipleNotDisposed -{ - bool deallocated1 = false; - __block bool disposed1 = false; - bool deallocated2 = false; - __block bool disposed2 = false; - { - DeallocatingObject *object1 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated1]; - dispatch_block_t block1 = ^{ - [object1 description]; - disposed1 = true; - }; - SBlockDisposable *blockDisposable1 = [[SBlockDisposable alloc] initWithBlock:[block1 copy]]; - - DeallocatingObject *object2 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated2]; - dispatch_block_t block2 = ^{ - [object2 description]; - disposed2 = true; - }; - SBlockDisposable *blockDisposable2 = [[SBlockDisposable alloc] initWithBlock:[block2 copy]]; - - SDisposableSet *disposableSet = [[SDisposableSet alloc] init]; - [disposableSet add:blockDisposable1]; - [disposableSet add:blockDisposable2]; - } - - XCTAssertTrue(deallocated1); - XCTAssertFalse(disposed1); - XCTAssertTrue(deallocated2); - XCTAssertFalse(disposed2); -} - -- (void)testMetaDisposableAlreadyDisposed -{ - bool deallocated1 = false; - __block bool disposed1 = false; - bool deallocated2 = false; - __block bool disposed2 = false; - - @autoreleasepool - { - DeallocatingObject *object1 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated1]; - dispatch_block_t block1 = ^{ - [object1 description]; - disposed1 = true; - }; - SBlockDisposable *blockDisposable1 = [[SBlockDisposable alloc] initWithBlock:[block1 copy]]; - - DeallocatingObject *object2 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated2]; - dispatch_block_t block2 = ^{ - [object2 description]; - disposed2 = true; - }; - SBlockDisposable *blockDisposable2 = [[SBlockDisposable alloc] initWithBlock:[block2 copy]]; - - SMetaDisposable *metaDisposable = [[SMetaDisposable alloc] init]; - [metaDisposable setDisposable:blockDisposable1]; - [metaDisposable dispose]; - [metaDisposable setDisposable:blockDisposable2]; - } - - XCTAssertTrue(deallocated1); - XCTAssertTrue(disposed1); - XCTAssertTrue(deallocated2); - XCTAssertTrue(disposed2); -} - -- (void)testDisposableSetAlreadyDisposed -{ - bool deallocated1 = false; - __block bool disposed1 = false; - bool deallocated2 = false; - __block bool disposed2 = false; - - @autoreleasepool - { - DeallocatingObject *object1 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated1]; - dispatch_block_t block1 = ^{ - [object1 description]; - disposed1 = true; - }; - SBlockDisposable *blockDisposable1 = [[SBlockDisposable alloc] initWithBlock:[block1 copy]]; - - DeallocatingObject *object2 = [[DeallocatingObject alloc] initWithDeallocated:&deallocated2]; - dispatch_block_t block2 = ^{ - [object2 description]; - disposed2 = true; - }; - SBlockDisposable *blockDisposable2 = [[SBlockDisposable alloc] initWithBlock:[block2 copy]]; - - SMetaDisposable *metaDisposable = [[SMetaDisposable alloc] init]; - [metaDisposable setDisposable:blockDisposable1]; - [metaDisposable dispose]; - [metaDisposable setDisposable:blockDisposable2]; - } - - XCTAssertTrue(deallocated1); - XCTAssertTrue(disposed1); - XCTAssertTrue(deallocated2); - XCTAssertTrue(disposed2); -} - -@end diff --git a/submodules/SSignalKit/SSignalKitTests/SSignalBasicTests.m b/submodules/SSignalKit/SSignalKitTests/SSignalBasicTests.m deleted file mode 100644 index 56ca98337c..0000000000 --- a/submodules/SSignalKit/SSignalKitTests/SSignalBasicTests.m +++ /dev/null @@ -1,764 +0,0 @@ -#if __IPHONE_OS_VERSION_MIN_REQUIRED -#import -#else -#import -#endif -#import - -@import SSignalKit; - -#import "DeallocatingObject.h" - -@interface DisposableHolder : NSObject { -} - -@property (nonatomic, strong) id disposable; - -@end - -@implementation DisposableHolder - -- (instancetype)init { - self = [super init]; - if (self != nil) { - _disposable = [[[SSignal single:nil] delay:1.0 onQueue:[SQueue concurrentDefaultQueue]] startWithNext:^(__unused id next){ - [self description]; - }]; - } - return self; -} - -- (void)dealloc { - [_disposable dispose]; -} - -@end - -@interface SSignalBasicTests : XCTestCase - -@end - -@implementation SSignalBasicTests - -- (void)setUp -{ - [super setUp]; -} - -- (void)tearDown -{ - [super tearDown]; -} - -- (void)testSignalGenerated -{ - __block bool deallocated = false; - __block bool disposed = false; - __block bool generated = false; - - { - DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated]; - SSignal *signal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - [subscriber putNext:@1]; - [object description]; - - return [[SBlockDisposable alloc] initWithBlock:^ - { - [object description]; - disposed = true; - }]; - }]; - id disposable = [signal startWithNext:^(__unused id next) - { - generated = true; - [object description]; - } error:nil completed:nil]; - [disposable dispose]; - } - - XCTAssertTrue(deallocated); - XCTAssertTrue(disposed); - XCTAssertTrue(generated); -} - -- (void)testSignalGeneratedCompleted -{ - __block bool deallocated = false; - __block bool disposed = false; - __block bool generated = false; - __block bool completed = false; - - { - DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated]; - SSignal *signal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - [subscriber putNext:@1]; - [subscriber putCompletion]; - [object description]; - - return [[SBlockDisposable alloc] initWithBlock:^ - { - [object description]; - disposed = true; - }]; - }]; - id disposable = [signal startWithNext:^(__unused id next) - { - [object description]; - generated = true; - } error:nil completed:^ - { - [object description]; - completed = true; - }]; - [disposable dispose]; - } - - XCTAssertTrue(deallocated); - XCTAssertTrue(disposed); - XCTAssertTrue(generated); - XCTAssertTrue(completed); -} - -- (void)testSignalGeneratedError -{ - __block bool deallocated = false; - __block bool disposed = false; - __block bool generated = false; - __block bool error = false; - - { - DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated]; - SSignal *signal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - [subscriber putNext:@1]; - [subscriber putError:@1]; - [object description]; - - return [[SBlockDisposable alloc] initWithBlock:^ - { - [object description]; - disposed = true; - }]; - }]; - id disposable = [signal startWithNext:^(__unused id next) - { - generated = true; - } error:^(__unused id value) - { - error = true; - } completed:nil]; - [disposable dispose]; - } - - XCTAssertTrue(deallocated); - XCTAssertTrue(disposed); - XCTAssertTrue(generated); - XCTAssertTrue(error); -} - -- (void)testMap -{ - bool deallocated = false; - __block bool disposed = false; - __block bool generated = false; - - { - @autoreleasepool - { - DeallocatingObject *object = [[DeallocatingObject alloc] initWithDeallocated:&deallocated]; - SSignal *signal = [[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - [subscriber putNext:@1]; - [object description]; - return [[SBlockDisposable alloc] initWithBlock:^ - { - [object description]; - disposed = true; - }]; - }] map:^id(id value) - { - [object description]; - return @([value intValue] * 2); - }]; - - id disposable = [signal startWithNext:^(id value) - { - generated = [value isEqual:@2]; - } error:nil completed:nil]; - [disposable dispose]; - } - } - - XCTAssertTrue(deallocated); - XCTAssertTrue(disposed); - XCTAssertTrue(generated); -} - -- (void)testSubscriberDisposal -{ - __block bool disposed = false; - __block bool generated = false; - - dispatch_queue_t queue = dispatch_queue_create(NULL, 0); - - @autoreleasepool - { - SSignal *signal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - dispatch_async(queue, ^ - { - usleep(200); - [subscriber putNext:@1]; - }); - - return [[SBlockDisposable alloc] initWithBlock:^ - { - disposed = true; - }]; - }]; - - id disposable = [signal startWithNext:^(id value) - { - generated = true; - } error:nil completed:nil]; - NSLog(@"dispose"); - [disposable dispose]; - } - - dispatch_barrier_sync(queue, ^ - { - }); - - XCTAssertTrue(disposed); - XCTAssertFalse(generated); -} - -- (void)testThen -{ - __block bool generatedFirst = false; - __block bool disposedFirst = false; - __block bool generatedSecond = false; - __block bool disposedSecond = false; - __block int result = 0; - - SSignal *signal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - generatedFirst = true; - [subscriber putNext:@(1)]; - [subscriber putCompletion]; - return [[SBlockDisposable alloc] initWithBlock:^ - { - disposedFirst = true; - }]; - }]; - - signal = [signal then:[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - generatedSecond = true; - [subscriber putNext:@(2)]; - [subscriber putCompletion]; - return [[SBlockDisposable alloc] initWithBlock:^ - { - disposedSecond = true; - }]; - }]]; - - [signal startWithNext:^(id next) - { - result += [next intValue]; - }]; - - XCTAssertTrue(generatedFirst); - XCTAssertTrue(disposedFirst); - XCTAssertTrue(generatedSecond); - XCTAssertTrue(disposedSecond); - XCTAssert(result == 3); -} - -- (void)testSwitchToLatest -{ - __block int result = 0; - __block bool disposedOne = false; - __block bool disposedTwo = false; - __block bool disposedThree = false; - __block bool completedAll = false; - - bool deallocatedOne = false; - bool deallocatedTwo = false; - bool deallocatedThree = false; - - @autoreleasepool - { - DeallocatingObject *objectOne = [[DeallocatingObject alloc] initWithDeallocated:&deallocatedOne]; - DeallocatingObject *objectTwo = [[DeallocatingObject alloc] initWithDeallocated:&deallocatedTwo]; - DeallocatingObject *objectThree = [[DeallocatingObject alloc] initWithDeallocated:&deallocatedThree]; - - SSignal *one = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - [subscriber putNext:@(1)]; - [subscriber putCompletion]; - __unused id a0 = [objectOne description]; - return [[SBlockDisposable alloc] initWithBlock:^ - { - __unused id a0 = [objectOne description]; - disposedOne = true; - }]; - }]; - SSignal *two = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - [subscriber putNext:@(2)]; - [subscriber putCompletion]; - __unused id a1 = [objectTwo description]; - return [[SBlockDisposable alloc] initWithBlock:^ - { - __unused id a1 = [objectOne description]; - disposedTwo = true; - }]; - }]; - SSignal *three = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - [subscriber putNext:@(3)]; - [subscriber putCompletion]; - __unused id a0 = [objectThree description]; - return [[SBlockDisposable alloc] initWithBlock:^ - { - __unused id a1 = [objectOne description]; - disposedThree = true; - }]; - }]; - - SSignal *signal = [[[[SSignal single:one] then:[SSignal single:two]] then:[SSignal single:three]] switchToLatest]; - [signal startWithNext:^(id next) - { - result += [next intValue]; - } error:nil completed:^ - { - completedAll = true; - }]; - } - - XCTAssert(result == 6); - XCTAssertTrue(disposedOne); - XCTAssertTrue(disposedTwo); - XCTAssertTrue(disposedThree); - XCTAssertTrue(deallocatedOne); - XCTAssertTrue(deallocatedTwo); - XCTAssertTrue(deallocatedThree); - XCTAssertTrue(completedAll); -} - -- (void)testSwitchToLatestError -{ - __block bool errorGenerated = false; - - SSignal *one = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - [subscriber putError:nil]; - return nil; - }]; - - [one startWithNext:^(__unused id next) - { - - } error:^(__unused id error) - { - errorGenerated = true; - } completed:^ - { - - }]; - - XCTAssertTrue(errorGenerated); -} - -- (void)testSwitchToLatestCompleted -{ - __block bool completedAll = false; - - SSignal *one = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - [subscriber putCompletion]; - return nil; - }]; - - [one startWithNext:^(__unused id next) - { - - } error:^(__unused id error) - { - } completed:^ - { - completedAll = true; - }]; - - XCTAssertTrue(completedAll); -} - -- (void)testQueue -{ - dispatch_queue_t queue = dispatch_queue_create(NULL, 0); - - __block bool disposedFirst = false; - __block bool disposedSecond = false; - __block bool disposedThird = false; - __block int result = 0; - - SSignal *firstSignal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - dispatch_async(queue, ^ - { - usleep(100); - [subscriber putNext:@1]; - [subscriber putCompletion]; - }); - - return [[SBlockDisposable alloc] initWithBlock:^ - { - disposedFirst = true; - }]; - }]; - - SSignal *secondSignal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - dispatch_async(queue, ^ - { - usleep(100); - [subscriber putNext:@2]; - [subscriber putCompletion]; - }); - - return [[SBlockDisposable alloc] initWithBlock:^ - { - disposedSecond = true; - }]; - }]; - - SSignal *thirdSignal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - dispatch_async(queue, ^ - { - usleep(100); - [subscriber putNext:@3]; - [subscriber putCompletion]; - }); - - return [[SBlockDisposable alloc] initWithBlock:^ - { - disposedThird = true; - }]; - }]; - - SSignal *signal = [[[[SSignal single:firstSignal] then:[SSignal single:secondSignal]] then:[SSignal single:thirdSignal]] queue]; - [signal startWithNext:^(id next) - { - result += [next intValue]; - }]; - - usleep(1000); - - XCTAssertEqual(result, 6); - XCTAssertTrue(disposedFirst); - XCTAssertTrue(disposedSecond); - XCTAssertTrue(disposedThird); -} - -- (void)testQueueInterrupted -{ - dispatch_queue_t queue = dispatch_queue_create(NULL, 0); - - __block bool disposedFirst = false; - __block bool disposedSecond = false; - __block bool disposedThird = false; - __block bool startedThird = false; - __block int result = 0; - - SSignal *firstSignal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - dispatch_async(queue, ^ - { - usleep(100); - [subscriber putNext:@1]; - [subscriber putCompletion]; - }); - - return [[SBlockDisposable alloc] initWithBlock:^ - { - disposedFirst = true; - }]; - }]; - - SSignal *secondSignal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - dispatch_async(queue, ^ - { - usleep(100); - [subscriber putNext:@2]; - [subscriber putError:nil]; - }); - - return [[SBlockDisposable alloc] initWithBlock:^ - { - disposedSecond = true; - }]; - }]; - - SSignal *thirdSignal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - startedThird = true; - - dispatch_async(queue, ^ - { - usleep(100); - [subscriber putNext:@3]; - [subscriber putCompletion]; - }); - - return [[SBlockDisposable alloc] initWithBlock:^ - { - disposedThird = true; - }]; - }]; - - SSignal *signal = [[[[SSignal single:firstSignal] then:[SSignal single:secondSignal]] then:[SSignal single:thirdSignal]] queue]; - [signal startWithNext:^(id next) - { - result += [next intValue]; - }]; - - usleep(1000); - - XCTAssertEqual(result, 3); - XCTAssertTrue(disposedFirst); - XCTAssertTrue(disposedSecond); - XCTAssertFalse(startedThird); - XCTAssertFalse(disposedThird); -} - -- (void)testQueueDisposed -{ - dispatch_queue_t queue = dispatch_queue_create(NULL, 0); - - __block bool disposedFirst = false; - __block bool disposedSecond = false; - __block bool disposedThird = false; - __block bool startedFirst = false; - __block bool startedSecond = false; - __block bool startedThird = false; - __block int result = 0; - - SSignal *firstSignal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - startedFirst = true; - - __block bool cancelled = false; - dispatch_async(queue, ^ - { - if (!cancelled) - { - usleep(100); - [subscriber putNext:@1]; - [subscriber putCompletion]; - } - }); - - return [[SBlockDisposable alloc] initWithBlock:^ - { - cancelled = true; - disposedFirst = true; - }]; - }]; - - SSignal *secondSignal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - startedSecond = true; - - __block bool cancelled = false; - dispatch_async(queue, ^ - { - if (!cancelled) - { - usleep(100); - [subscriber putNext:@2]; - [subscriber putError:nil]; - } - }); - - return [[SBlockDisposable alloc] initWithBlock:^ - { - cancelled = true; - disposedSecond = true; - }]; - }]; - - SSignal *thirdSignal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - startedThird = true; - - dispatch_async(queue, ^ - { - usleep(100); - [subscriber putNext:@3]; - [subscriber putCompletion]; - }); - - return [[SBlockDisposable alloc] initWithBlock:^ - { - disposedThird = true; - }]; - }]; - - SSignal *signal = [[[[SSignal single:firstSignal] then:[SSignal single:secondSignal]] then:[SSignal single:thirdSignal]] queue]; - [[signal startWithNext:^(id next) - { - result += [next intValue]; - }] dispose]; - - usleep(1000); - - XCTAssertEqual(result, 0); - XCTAssertTrue(disposedFirst); - XCTAssertFalse(disposedSecond); - XCTAssertFalse(disposedThird); - - XCTAssertTrue(startedFirst); - XCTAssertFalse(startedSecond); - XCTAssertFalse(startedThird); -} - -- (void)testRestart -{ - SSignal *signal = [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - [[SQueue concurrentDefaultQueue] dispatch:^ - { - [subscriber putNext:@1]; - [subscriber putCompletion]; - }]; - - return [[SBlockDisposable alloc] initWithBlock:^ - { - }]; - }]; - - __block int result = 0; - - [[[signal restart] take:3] startWithNext:^(id next) - { - result += [next intValue]; - } error:^(id error) { - - } completed:^{ - - }]; - - usleep(100 * 1000); - - XCTAssertEqual(result, 3); -} - -- (void)testPipe -{ - SPipe *pipe = [[SPipe alloc] init]; - - __block int result1 = 0; - id disposable1 = [pipe.signalProducer() startWithNext:^(id next) - { - result1 += [next intValue]; - }]; - - __block int result2 = 0; - id disposable2 = [pipe.signalProducer() startWithNext:^(id next) - { - result2 += [next intValue]; - }]; - - pipe.sink(@1); - - XCTAssertEqual(result1, 1); - XCTAssertEqual(result2, 1); - - [disposable1 dispose]; - - pipe.sink(@1); - - XCTAssertEqual(result1, 1); - XCTAssertEqual(result2, 2); - - [disposable2 dispose]; - - pipe.sink(@1); - - XCTAssertEqual(result1, 1); - XCTAssertEqual(result2, 2); -} - -- (void)testDisposableDeadlock { - @autoreleasepool { - DisposableHolder *holder = [[DisposableHolder alloc] init]; - holder = nil; - sleep(10); - } -} - -- (void)testRetryIfNoError { - SSignal *s = [[SSignal single:@1] retryIf:^bool(__unused id error) { - return true; - }]; - [s startWithNext:^(id next) { - XCTAssertEqual(next, @1); - }]; -} - -- (void)testRetryErrorNoMatch { - SSignal *s = [[SSignal fail:@false] retryIf:^bool(id error) { - return false; - }]; -} - -- (void)testRetryErrorMatch { - __block counter = 1; - SSignal *s = [[[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber) { - if (counter == 1) { - counter++; - [subscriber putError:@true]; - } else { - [subscriber putNext:@(counter)]; - } - return nil; - }] retryIf:^bool(id error) { - return [error boolValue]; - }]; - - __block int value = 0; - [s startWithNext:^(id next) { - value = [next intValue]; - }]; - - XCTAssertEqual(value, 2); -} - -- (void)testRetryErrorFailNoMatch { - __block counter = 1; - SSignal *s = [[[SSignal alloc] initWithGenerator:^id (SSubscriber *subscriber) { - if (counter == 1) { - counter++; - [subscriber putError:@true]; - } else { - [subscriber putError:@false]; - } - return nil; - }] retryIf:^bool(id error) { - return [error boolValue]; - }]; - - __block bool errorMatches = false; - [s startWithNext:nil error:^(id error) { - errorMatches = ![error boolValue]; - } completed:nil]; - - XCTAssert(errorMatches); -} - -@end diff --git a/submodules/SSignalKit/SSignalKitTests/SSignalPerformanceTests.m b/submodules/SSignalKit/SSignalKitTests/SSignalPerformanceTests.m deleted file mode 100644 index f5384d75bd..0000000000 --- a/submodules/SSignalKit/SSignalKitTests/SSignalPerformanceTests.m +++ /dev/null @@ -1,50 +0,0 @@ -#if __IPHONE_OS_VERSION_MIN_REQUIRED -#import -#else -#import -#endif -#import - -@import SSignalKit; - -@interface SSignalPerformanceTests : XCTestCase - -@end - -@implementation SSignalPerformanceTests - -- (void)setUp -{ - [super setUp]; -} - -- (void)tearDown -{ - [super tearDown]; -} - -- (void)testMap -{ - [self measureBlock:^ - { - SSignal *signal = [[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) - { - [subscriber putNext:@1]; - [subscriber putCompletion]; - return nil; - }] map:^id (id value) - { - return value; - }]; - - for (int i = 0; i < 100000; i++) - { - [signal startWithNext:^(__unused id next) - { - - }]; - } - }]; -} - -@end diff --git a/submodules/SSignalKit/SwiftSignalKit/BUILD b/submodules/SSignalKit/SwiftSignalKit/BUILD new file mode 100644 index 0000000000..16720552fe --- /dev/null +++ b/submodules/SSignalKit/SwiftSignalKit/BUILD @@ -0,0 +1,12 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "SwiftSignalKit", + module_name = "SwiftSignalKit", + srcs = glob([ + "Source/**/*.swift", + ]), + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/SSignalKit/SwiftSignalKit/Info.plist b/submodules/SSignalKit/SwiftSignalKit/Info.plist deleted file mode 100644 index d3de8eefb6..0000000000 --- a/submodules/SSignalKit/SwiftSignalKit/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSPrincipalClass - - - diff --git a/submodules/SSignalKit/SwiftSignalKit/Atomic.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Atomic.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Atomic.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Atomic.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Bag.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Bag.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Bag.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Bag.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Disposable.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Disposable.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Disposable.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Disposable.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Lock.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Lock.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Lock.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Lock.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Multicast.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Multicast.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Multicast.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Multicast.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Promise.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Promise.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Promise.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Promise.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Queue.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Queue.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Queue.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Queue.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/QueueLocalObject.swift b/submodules/SSignalKit/SwiftSignalKit/Source/QueueLocalObject.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/QueueLocalObject.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/QueueLocalObject.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Signal.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Signal.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Signal.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Signal_Catch.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Catch.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Signal_Catch.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Signal_Catch.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Signal_Combine.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Signal_Combine.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Signal_Combine.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Signal_Dispatch.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Dispatch.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Signal_Dispatch.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Signal_Dispatch.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Signal_Loop.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Loop.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Signal_Loop.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Signal_Loop.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Signal_Mapping.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Mapping.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Signal_Mapping.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Signal_Mapping.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Signal_Materialize.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Materialize.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Signal_Materialize.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Signal_Materialize.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Signal_Merge.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Merge.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Signal_Merge.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Signal_Merge.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Signal_Meta.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Meta.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Signal_Meta.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Signal_Meta.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Signal_Reduce.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Reduce.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Signal_Reduce.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Signal_Reduce.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Signal_SideEffects.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_SideEffects.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Signal_SideEffects.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Signal_SideEffects.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Signal_Single.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Single.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Signal_Single.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Signal_Single.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Signal_Take.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Take.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Signal_Take.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Signal_Take.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Signal_Timing.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Signal_Timing.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Signal_Timing.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Signal_Timing.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Subscriber.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Subscriber.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Subscriber.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Subscriber.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/ThreadPool.swift b/submodules/SSignalKit/SwiftSignalKit/Source/ThreadPool.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/ThreadPool.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/ThreadPool.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/Timer.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Timer.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/Timer.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/Timer.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/ValuePipe.swift b/submodules/SSignalKit/SwiftSignalKit/Source/ValuePipe.swift similarity index 100% rename from submodules/SSignalKit/SwiftSignalKit/ValuePipe.swift rename to submodules/SSignalKit/SwiftSignalKit/Source/ValuePipe.swift diff --git a/submodules/SSignalKit/SwiftSignalKit/SwiftSignalKit.h b/submodules/SSignalKit/SwiftSignalKit/SwiftSignalKit.h deleted file mode 100644 index d30da27eb3..0000000000 --- a/submodules/SSignalKit/SwiftSignalKit/SwiftSignalKit.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// SwiftSignalKit.h -// SwiftSignalKit -// -// Created by Peter on 10/06/15. -// Copyright (c) 2015 Telegram. All rights reserved. -// - -#if __IPHONE_OS_VERSION_MIN_REQUIRED -#import -#else -#import -#endif - -//! Project version number for SwiftSignalKit. -FOUNDATION_EXPORT double SwiftSignalKitVersionNumber; - -//! Project version string for SwiftSignalKit. -FOUNDATION_EXPORT const unsigned char SwiftSignalKitVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/submodules/SSignalKit/SwiftSignalKitTests/DeallocatingObject.swift b/submodules/SSignalKit/SwiftSignalKitTests/DeallocatingObject.swift deleted file mode 100644 index 840ca1075b..0000000000 --- a/submodules/SSignalKit/SwiftSignalKitTests/DeallocatingObject.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Foundation - -internal class DeallocatingObject : CustomStringConvertible { - private var deallocated: UnsafeMutablePointer - - init(deallocated: UnsafeMutablePointer) { - self.deallocated = deallocated - } - - deinit { - self.deallocated.pointee = true - } - - var description: String { - get { - return "" - } - } -} diff --git a/submodules/SSignalKit/SwiftSignalKitTests/Info.plist b/submodules/SSignalKit/SwiftSignalKitTests/Info.plist deleted file mode 100644 index ba72822e87..0000000000 --- a/submodules/SSignalKit/SwiftSignalKitTests/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - - diff --git a/submodules/SSignalKit/SwiftSignalKitTests/PerformanceTests.swift b/submodules/SSignalKit/SwiftSignalKitTests/PerformanceTests.swift deleted file mode 100644 index 824ffcca7c..0000000000 --- a/submodules/SSignalKit/SwiftSignalKitTests/PerformanceTests.swift +++ /dev/null @@ -1,167 +0,0 @@ -import UIKit -import XCTest -import SwiftSignalKit -import Foundation - -/*final class DisposableLock { - private var action: (() -> Void)? - private var lock = pthread_mutex_t() - - init(action: @escaping () -> Void) { - self.action = action - pthread_mutex_init(&self.lock, nil) - } - - func dispose() { - var action: (() -> Void)? - pthread_mutex_lock(&self.lock) - action = self.action - self.action = nil - pthread_mutex_unlock(&self.lock) - if let action = action { - action() - } - } -} - -final class DisposableSpinLock { - private var action: (() -> Void)? - private var lock = OSSpinLock() - - init(action: @escaping () -> Void) { - self.action = action - } - - func dispose() { - var action: (() -> Void)? - OSSpinLockLock(&self.lock) - action = self.action - self.action = nil - OSSpinLockUnlock(&self.lock) - if let action = action { - action() - } - } -} - -final class DisposableNoLock { - private var action: (() -> Void)? - - init(action: @escaping () -> Void) { - self.action = action - } - - func dispose() { - var action: (() -> Void)? - action = self.action - self.action = nil - if let action = action { - action() - } - } -} - -final class DisposableAtomic { - private var action: () -> Void - private var disposed: Int32 = 0 - - init(action: @escaping () -> Void) { - self.action = action - } - - func dispose() { - if OSAtomicCompareAndSwap32(0, 1, &self.disposed) { - self.action() - } - } -} - -class PerformanceTests: XCTestCase { - override func setUp() { - super.setUp() - } - - override func tearDown() { - super.tearDown() - } - - func testMeasureLock() { - measure { - for _ in 0 ..< 1000000 { - let disposable = DisposableLock(action: {}) - disposable.dispose() - } - } - } - - func testMeasureSpinlock() { - measure { - for _ in 0 ..< 1000000 { - let disposable = DisposableSpinLock(action: {}) - disposable.dispose() - } - } - } - - func testMeasureAtomic() { - measure { - for _ in 0 ..< 1000000 { - let disposable = DisposableAtomic(action: {}) - disposable.dispose() - } - } - } - - func read(_ idxin: Int, _ size: Int, _ tree: inout [Int: Int], _ reads: inout Set) -> Int { - var idx = idxin - var sum = 0 - while idx <= size { - print("read at \(idx)") - if let value = tree[idx] { - sum += value - } - reads.insert(idx) - idx += (idx & -idx) - } - return sum - } - - func update(_ idxin: Int, _ val: Int, _ tree: inout [Int: Int], _ updates: inout Set) { - var idx = idxin - while (idx > 0) { - if let value = tree[idx] { - tree[idx] = value + val - } else { - tree[idx] = val - } - //print("write at \(idx)") - updates.insert(idx) - idx -= (idx & -idx) - } - } - - func testTree() { - let size = 2_000_000 - var dict: [Int: Int] = [:] - - var updates = Set() - var index = 0 - for _ in 1 ..< 100_000 { - //update(Int(1 + arc4random_uniform(UInt32(size))), 1, &dict, &updates) - update(index, 1, &dict, &updates) - index += Int(1 + arc4random_uniform(100)) - } - update(size - 1, 1, &dict, &updates) - print("update ops = \(updates.count), tree = \(dict.count) items") - - var reads = Set() - let sum = read(1, size, &dict, &reads) - print("read = \(sum) ops = \(reads.count)") - - update(99, -2, &dict, &updates) - reads.removeAll() - let sum2 = read(1, size, &dict, &reads) - print("read2 = \(sum2) ops = \(reads.count)") - } -} -*/ diff --git a/submodules/SSignalKit/SwiftSignalKitTests/SwiftSignalKitBasicTests.swift b/submodules/SSignalKit/SwiftSignalKitTests/SwiftSignalKitBasicTests.swift deleted file mode 100644 index 9ff9df469d..0000000000 --- a/submodules/SSignalKit/SwiftSignalKitTests/SwiftSignalKitBasicTests.swift +++ /dev/null @@ -1,300 +0,0 @@ -import UIKit -import XCTest -import SwiftSignalKit - -class SwiftSignalKitTests: XCTestCase { - override func setUp() { - super.setUp() - } - - override func tearDown() { - super.tearDown() - } - - func testActionDisposableDisposed() { - var deallocated = false - var disposed = false - if true { - var object: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated) - let disposable = ActionDisposable(action: { [object] () -> Void in - let _ = object.debugDescription - disposed = true - }) - object = nil - XCTAssertFalse(deallocated, "deallocated != false") - disposable.dispose() - } - - XCTAssertTrue(deallocated, "deallocated != true") - XCTAssertTrue(disposed, "disposed != true") - } - - func testActionDisposableNotDisposed() { - var deallocated = false - var disposed = false - if true { - let object: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated) - let _ = ActionDisposable(action: { [object] () -> Void in - let _ = object.debugDescription - disposed = true - }) - } - XCTAssertTrue(deallocated, "deallocated != true") - XCTAssertFalse(disposed, "disposed != false") - } - - func testMetaDisposableDisposed() { - var deallocated = false - var disposed = false - if true { - let object: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated) - let disposable = ActionDisposable(action: { [object] () -> Void in - let _ = object.debugDescription - disposed = true - }) - - let metaDisposable = MetaDisposable() - metaDisposable.set(disposable) - metaDisposable.dispose() - } - XCTAssertTrue(deallocated, "deallocated != true") - XCTAssertTrue(disposed, "disposed != true") - } - - func testMetaDisposableDisposedMultipleTimes() { - var deallocated1 = false - var disposed1 = false - var deallocated2 = false - var disposed2 = false - if true { - let object1: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated1) - let actionDisposable1 = ActionDisposable(action: { [object1] () -> Void in - let _ = object1.debugDescription - disposed1 = true - }) - - let object2: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated2) - let actionDisposable2 = ActionDisposable(action: { [object2] () -> Void in - let _ = object2.debugDescription - disposed2 = true - }) - - let metaDisposable = MetaDisposable() - metaDisposable.set(actionDisposable1) - metaDisposable.set(actionDisposable2) - metaDisposable.dispose() - } - XCTAssertTrue(deallocated1, "deallocated1 != true") - XCTAssertTrue(disposed1, "disposed1 != true") - XCTAssertTrue(deallocated2, "deallocated2 != true") - XCTAssertTrue(disposed2, "disposed2 != true") - } - - func testMetaDisposableNotDisposed() { - var deallocated = false - var disposed = false - if true { - let object: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated) - let disposable = ActionDisposable(action: { [object] () -> Void in - let _ = object.debugDescription - disposed = true - }) - - let metaDisposable = MetaDisposable() - metaDisposable.set(disposable) - } - XCTAssertTrue(deallocated, "deallocated != true") - XCTAssertFalse(disposed, "disposed != false") - } - - func testDisposableSetSingleDisposed() { - var deallocated = false - var disposed = false - if true { - let object: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated) - let disposable = ActionDisposable(action: { [object] () -> Void in - let _ = object.debugDescription - disposed = true - }) - - let disposableSet = DisposableSet() - disposableSet.add(disposable) - disposableSet.dispose() - } - XCTAssertTrue(deallocated, "deallocated != true") - XCTAssertTrue(disposed, "disposed != true") - } - - func testDisposableSetMultipleDisposed() { - var deallocated1 = false - var disposed1 = false - var deallocated2 = false - var disposed2 = false - if true { - let object1: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated1) - let actionDisposable1 = ActionDisposable(action: { [object1] () -> Void in - let _ = object1.debugDescription - disposed1 = true - }) - - let object2: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated2) - let actionDisposable2 = ActionDisposable(action: { [object2] () -> Void in - let _ = object2.debugDescription - disposed2 = true - }) - - let disposableSet = DisposableSet() - disposableSet.add(actionDisposable1) - disposableSet.add(actionDisposable2) - disposableSet.dispose() - } - XCTAssertTrue(deallocated1, "deallocated1 != true") - XCTAssertTrue(disposed1, "disposed1 != true") - XCTAssertTrue(deallocated2, "deallocated2 != true") - XCTAssertTrue(disposed2, "disposed2 != true") - } - - func testDisposableSetSingleNotDisposed() { - var deallocated = false - var disposed = false - if true { - let object: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated) - let disposable = ActionDisposable(action: { [object] () -> Void in - let _ = object.debugDescription - disposed = true - }) - - let disposableSet = DisposableSet() - disposableSet.add(disposable) - } - XCTAssertTrue(deallocated, "deallocated != true") - XCTAssertFalse(disposed, "disposed != false") - } - - func testDisposableSetMultipleNotDisposed() { - var deallocated1 = false - var disposed1 = false - var deallocated2 = false - var disposed2 = false - if true { - let object1: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated1) - let actionDisposable1 = ActionDisposable(action: { [object1] () -> Void in - let _ = object1.debugDescription - disposed1 = true - }) - - let object2: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated2) - let actionDisposable2 = ActionDisposable(action: { [object2] () -> Void in - let _ = object2.debugDescription - disposed2 = true - }) - - let disposableSet = DisposableSet() - disposableSet.add(actionDisposable1) - disposableSet.add(actionDisposable2) - } - XCTAssertTrue(deallocated1, "deallocated1 != true") - XCTAssertFalse(disposed1, "disposed1 != false") - XCTAssertTrue(deallocated2, "deallocated2 != true") - XCTAssertFalse(disposed2, "disposed2 != false") - } - - func testMetaDisposableAlreadyDisposed() { - var deallocated1 = false - var disposed1 = false - var deallocated2 = false - var disposed2 = false - if true { - let object1: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated1) - let actionDisposable1 = ActionDisposable(action: { [object1] () -> Void in - let _ = object1.debugDescription - disposed1 = true - }) - - let object2: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated2) - let actionDisposable2 = ActionDisposable(action: { [object2] () -> Void in - let _ = object2.debugDescription - disposed2 = true - }) - - let metaDisposable = MetaDisposable() - metaDisposable.set(actionDisposable1) - metaDisposable.dispose() - metaDisposable.set(actionDisposable2) - } - XCTAssertTrue(deallocated1, "deallocated1 != true") - XCTAssertTrue(disposed1, "disposed1 != true") - XCTAssertTrue(deallocated2, "deallocated2 != true") - XCTAssertTrue(disposed2, "disposed2 != true") - } - - func testDisposableSetAlreadyDisposed() { - var deallocated1 = false - var disposed1 = false - var deallocated2 = false - var disposed2 = false - if true { - let object1: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated1) - let actionDisposable1 = ActionDisposable(action: { [object1] () -> Void in - let _ = object1.debugDescription - disposed1 = true - }) - - let object2: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated2) - let actionDisposable2 = ActionDisposable(action: { [object2] () -> Void in - let _ = object2.debugDescription - disposed2 = true - }) - - let disposableSet = DisposableSet() - disposableSet.add(actionDisposable1) - disposableSet.dispose() - disposableSet.add(actionDisposable2) - } - XCTAssertTrue(deallocated1, "deallocated1 != true") - XCTAssertTrue(disposed1, "disposed1 != true") - XCTAssertTrue(deallocated2, "deallocated2 != true") - XCTAssertTrue(disposed2, "disposed2 != true") - } - - func testDelayed1() { - var flag = false - let signal = Signal, NoError> { subscriber in - Queue.concurrentDefaultQueue().after(0.1, { - subscriber.putNext(Signal { susbcriber2 in - return ActionDisposable { - flag = true - } - }) - }) - - return EmptyDisposable - } |> switchToLatest - - let disposable = signal.start() - disposable.dispose() - - usleep(1000000 * 20) - - XCTAssert(flag == true) - } - - func testSingleDeallocation() { - do { - let signal: Signal<(Bool, WrapData?, Int), NoError> = .single((true, WrapData(data: Data(count: 1000)), 123)) - let _ = signal.start() - } - } -} - -final class WrapData { - let data: Data? - - init(data: Data?) { - self.data = data - } - - deinit { - print("deinit") - } -} diff --git a/submodules/SSignalKit/SwiftSignalKitTests/SwiftSignalKitFunctionsTests.swift b/submodules/SSignalKit/SwiftSignalKitTests/SwiftSignalKitFunctionsTests.swift deleted file mode 100644 index a7c418579d..0000000000 --- a/submodules/SSignalKit/SwiftSignalKitTests/SwiftSignalKitFunctionsTests.swift +++ /dev/null @@ -1,733 +0,0 @@ -import UIKit -import XCTest -import SwiftSignalKit - -func singleSignalInt(_ value: Signal) -> Signal, Void> { - return Signal { subscriber in - subscriber.putNext(value) - subscriber.putCompletion() - return EmptyDisposable - } -} - -class SwiftSignalKitFunctionsTests: XCTestCase { - - override func setUp() { - super.setUp() - } - - override func tearDown() { - super.tearDown() - } - - func testSignalGenerated() { - var deallocated = false - var disposed = false - var generated = false - - if true { - var object: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated) - let signal = Signal { [object] subscriber in - subscriber.putNext(1) - return ActionDisposable { - let _ = object?.description - disposed = true - } - } - - let disposable = signal.start(next: { [object] next in - generated = true - let _ = object?.description - }) - - object = nil - - XCTAssertFalse(deallocated, "deallocated != false") - - disposable.dispose() - } - - XCTAssertTrue(deallocated, "deallocated != true") - XCTAssertTrue(disposed, "disposed != true") - XCTAssertTrue(generated, "generated != true") - } - - func testSignalGeneratedCompleted() { - var deallocated = false - var disposed = false - var generated = false - var completed = false - - if true { - var object: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated) - let signal = Signal { [object] subscriber in - subscriber.putNext(1) - subscriber.putCompletion() - - return ActionDisposable { - let _ = object?.description - disposed = true - } - } - - let disposable = signal.start(next: { [object] next in - generated = true - let _ = object?.description - }, completed: { [object] - completed = true - let _ = object?.description - }) - - object = nil - - XCTAssertFalse(deallocated, "deallocated != false") - - disposable.dispose() - } - - XCTAssertTrue(deallocated, "deallocated != true") - XCTAssertTrue(disposed, "disposed != true") - XCTAssertTrue(generated, "generated != true") - XCTAssertTrue(completed, "completed != true") - } - - func testSignalGeneratedError() { - var deallocated = false - var disposed = false - var generated = false - var completed = false - var error = false - - if true { - var object: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated) - let signal = Signal { [object] subscriber in - subscriber.putError() - subscriber.putNext(1) - - return ActionDisposable { - let _ = object?.description - disposed = true - } - } - - let disposable = signal.start(next: { [object] next in - generated = true - let _ = object?.description - }, error: { [object] _ in - error = true - let _ = object?.description - }, - completed: { [object] - completed = true - let _ = object?.description - }) - - object = nil - - XCTAssertFalse(deallocated, "deallocated != false") - - disposable.dispose() - } - - XCTAssertTrue(deallocated, "deallocated != true") - XCTAssertTrue(disposed, "disposed != true") - XCTAssertFalse(generated, "generated != false") - XCTAssertFalse(completed, "completed != false") - XCTAssertTrue(error, "error != true") - } - - func testMap() { - var deallocated = false - var disposed = false - var generated = false - - if true { - var object: DeallocatingObject? = DeallocatingObject(deallocated: &deallocated) - var signal = Signal { [object] subscriber in - subscriber.putNext(1) - - return ActionDisposable { - let _ = object?.description - disposed = true - } - } - signal = signal |> map { $0 * 2} - - let disposable = signal.start(next: { [object] next in - generated = next == 2 - let _ = object?.description - }) - - object = nil - - XCTAssertFalse(deallocated, "deallocated != false") - - disposable.dispose() - } - - XCTAssertTrue(deallocated, "deallocated != true") - XCTAssertTrue(disposed, "disposed != true") - XCTAssertTrue(generated, "generated != true") - } - - func testCatch() { - let failingSignal = Signal { subscriber in - subscriber.putNext(1) - subscriber.putError(1) - return EmptyDisposable - } - - let catchSignal = failingSignal |> `catch` { error in - return Signal { subscriber in - subscriber.putNext(error * 2) - return EmptyDisposable - } - } - - var result = 0 - let _ = catchSignal.start(next: { next in - result += next - }) - - XCTAssertTrue(result == 3, "result != 2") - } - - func testSubscriberDisposal() { - var disposed = false - var generated = false - let queue = DispatchQueue(label: "") - - if true { - let signal = Signal { subscriber in - queue.async { - usleep(200) - subscriber.putNext(1) - } - return ActionDisposable { - disposed = true - } - } - - let disposable = signal.start(next: { next in - generated = true - }) - disposable.dispose() - - queue.sync(flags: [.barrier], execute: {}) - - XCTAssertTrue(disposed, "disposed != true") - XCTAssertFalse(generated, "generated != false") - } - } - - func testThen() { - var generatedFirst = false - var disposedFirst = false - var generatedSecond = false - var disposedSecond = false - var result = 0 - - var signal = Signal { subscriber in - generatedFirst = true - subscriber.putNext(1) - subscriber.putCompletion() - return ActionDisposable { - disposedFirst = true - } - } - - signal = signal |> then (Signal { subscriber in - generatedSecond = true - subscriber.putNext(2) - subscriber.putCompletion() - return ActionDisposable { - disposedSecond = true - } - }) - - let _ = signal.start(next: { next in - result += next - }) - - XCTAssertTrue(generatedFirst, "generatedFirst != true"); - XCTAssertTrue(disposedFirst, "disposedFirst != true"); - XCTAssertTrue(generatedSecond, "generatedSecond !+ true"); - XCTAssertTrue(disposedSecond, "disposedSecond != true"); - XCTAssertTrue(result == 3, "result != 3"); - } - - func testCombineLatest2() { - let s1 = Signal { subscriber in - subscriber.putNext(1) - subscriber.putCompletion() - return EmptyDisposable - } - let s2 = Signal { subscriber in - subscriber.putNext(2) - subscriber.putCompletion() - return EmptyDisposable - } - - let signal = combineLatest(s1, s2) - - var completed = false - let _ = signal.start(next: { next in - XCTAssert(next.0 == 1 && next.1 == 2, "next != (1, 2)") - return - }, completed: { - completed = true - }) - XCTAssert(completed == true, "completed != true") - } - - func testCombineLatest3() { - let s1 = Signal { subscriber in - subscriber.putNext(1) - subscriber.putCompletion() - return EmptyDisposable - } - let s2 = Signal { subscriber in - subscriber.putNext(2) - subscriber.putCompletion() - return EmptyDisposable - } - let s3 = Signal { subscriber in - subscriber.putNext(3) - subscriber.putCompletion() - return EmptyDisposable - } - - let signal = combineLatest(s1, s2, s3) - - var completed = false - let _ = signal.start(next: { next in - XCTAssert(next.0 == 1 && next.1 == 2 && next.2 == 3, "next != (1, 2, 3)") - return - }, completed: { - completed = true - }) - XCTAssert(completed == true, "completed != true") - } - - func testSingle() { - let s1 = single(1, Void.self) - let s2 = fail(Int.self, Void()) - let s3 = complete(Int.self, Void.self) - - var singleEmitted = false - let _ = s1.start(next: { next in - singleEmitted = next == 1 - }) - XCTAssert(singleEmitted == true, "singleEmitted != true") - - var errorEmitted = false - let _ = s2.start(error: { error in - errorEmitted = true - }) - XCTAssert(errorEmitted == true, "errorEmitted != true") - - var completedEmitted = false - let _ = s3.start(completed: { - completedEmitted = true - }) - XCTAssert(completedEmitted == true, "errorEmitted != true") - } - - func testSwitchToLatest() { - var result: [Int] = [] - var disposedOne = false - var disposedTwo = false - var disposedThree = false - var completedAll = false - - var deallocatedOne = false - var deallocatedTwo = false - var deallocatedThree = false - - if true { - let objectOne: DeallocatingObject? = DeallocatingObject(deallocated: &deallocatedOne) - let objectTwo: DeallocatingObject? = DeallocatingObject(deallocated: &deallocatedTwo) - let objectThree: DeallocatingObject? = DeallocatingObject(deallocated: &deallocatedThree) - - let one = Signal { subscriber in - subscriber.putNext(1) - subscriber.putCompletion() - return ActionDisposable { [objectOne] in - let _ = objectOne?.description - disposedOne = true - } - } - - let two = Signal { subscriber in - subscriber.putNext(2) - subscriber.putCompletion() - return ActionDisposable { [objectTwo] in - let _ = objectTwo?.description - disposedTwo = true - } - } - - let three = Signal { subscriber in - subscriber.putNext(3) - subscriber.putCompletion() - return ActionDisposable { [objectThree] in - let _ = objectThree?.description - disposedThree = true - } - } - - let signal = singleSignalInt(one) |> then(singleSignalInt(two)) |> then(singleSignalInt(three)) |> switchToLatest - - let _ = signal.start(next: { next in - result.append(next) - }, completed: { - completedAll = true - }) - } - - XCTAssert(result.count == 3 && result[0] == 1 && result[1] == 2 && result[2] == 3, "result != [1, 2, 3]"); - XCTAssert(disposedOne == true, "disposedOne != true"); - XCTAssert(disposedTwo == true, "disposedTwo != true"); - XCTAssert(disposedThree == true, "disposedThree != true"); - XCTAssert(deallocatedOne == true, "deallocatedOne != true"); - XCTAssert(deallocatedTwo == true, "deallocatedTwo != true"); - XCTAssert(deallocatedThree == true, "deallocatedThree != true"); - XCTAssert(completedAll == true, "completedAll != true"); - } - - func testSwitchToLatestError() { - var errorGenerated = false - - let one = Signal { subscriber in - subscriber.putError(Void()) - return EmptyDisposable - } - - let signal = singleSignalInt(one) |> switchToLatest - - let _ = signal.start(error: { error in - errorGenerated = true - }) - - XCTAssert(errorGenerated == true, "errorGenerated != true") - } - - func testQueue() { - let q = DispatchQueue(label: "") - - var disposedOne = false - var disposedTwo = false - var disposedThree = false - var completedAll = false - var result: [Int] = [] - - let one = Signal { subscriber in - q.async { - subscriber.putNext(1) - subscriber.putCompletion() - } - return ActionDisposable { - disposedOne = true - } - } - - let two = Signal { subscriber in - q.async { - subscriber.putNext(2) - subscriber.putCompletion() - } - return ActionDisposable { - disposedTwo = true - } - } - - let three = Signal { subscriber in - q.async { - subscriber.putNext(3) - subscriber.putCompletion() - } - return ActionDisposable { - disposedThree = true - } - } - - let signal = singleSignalInt(one) |> then(singleSignalInt(two)) |> then(singleSignalInt(three)) |> queue - - let _ = signal.start(next: { next in - print("next: \(next)") - result.append(next) - }, completed: { - completedAll = true - }) - - usleep(1000 * 200) - - XCTAssert(result.count == 3 && result[0] == 1 && result[1] == 2 && result[2] == 3, "result != [1, 2, 3]"); - XCTAssert(disposedOne == true, "disposedOne != true"); - XCTAssert(disposedTwo == true, "disposedTwo != true"); - XCTAssert(disposedThree == true, "disposedThree != true"); - XCTAssert(completedAll == true, "completedAll != true"); - } - - func testQueueInterrupted() { - let q = DispatchQueue(label: "") - - var disposedOne = false - var disposedTwo = false - var disposedThree = false - var startedThird = false - var completedAll = false - var result: [Int] = [] - - let one = Signal { subscriber in - q.async { - subscriber.putNext(1) - subscriber.putCompletion() - } - return ActionDisposable { - disposedOne = true - } - } - - let two = Signal { subscriber in - q.async { - subscriber.putNext(2) - subscriber.putError(Void()) - } - return ActionDisposable { - disposedTwo = true - } - } - - let three = Signal { subscriber in - startedThird = true - q.async { - subscriber.putNext(3) - subscriber.putCompletion() - } - return ActionDisposable { - disposedThree = true - } - } - - let signal = singleSignalInt(one) |> then(singleSignalInt(two)) |> then(singleSignalInt(three)) |> queue - - let _ = signal.start(next: { next in - result.append(next) - }, completed: { - completedAll = true - }) - - usleep(1000 * 200) - - XCTAssert(result.count == 2 && result[0] == 1 && result[1] == 2, "result != [1, 2]"); - XCTAssert(disposedOne == true, "disposedOne != true"); - XCTAssert(disposedTwo == true, "disposedTwo != true"); - XCTAssert(disposedThree == false, "disposedThree != false"); - XCTAssert(startedThird == false, "startedThird != false"); - XCTAssert(completedAll == false, "completedAll != false"); - } - - func testQueueDisposed() { - let q = DispatchQueue(label: "") - - var disposedOne = false - var disposedTwo = false - var disposedThree = false - var startedFirst = false - var startedSecond = false - var startedThird = false - var result: [Int] = [] - - let one = Signal { subscriber in - startedFirst = true - var cancelled = false - q.async { - if !cancelled { - usleep(100 * 1000) - subscriber.putNext(1) - subscriber.putCompletion() - } - } - return ActionDisposable { - cancelled = true - disposedOne = true - } - } - - let two = Signal { subscriber in - startedSecond = true - var cancelled = false - q.async { - if !cancelled { - usleep(100 * 1000) - subscriber.putNext(2) - subscriber.putError(Void()) - } - } - return ActionDisposable { - cancelled = true - disposedTwo = true - } - } - - let three = Signal { subscriber in - startedThird = true - var cancelled = false - q.async { - if !cancelled { - usleep(100 * 1000) - subscriber.putNext(3) - subscriber.putCompletion() - } - } - return ActionDisposable { - cancelled = true - disposedThree = true - } - } - - let signal = singleSignalInt(one) |> then(singleSignalInt(two)) |> then(singleSignalInt(three)) |> queue - - signal.start(next: { next in - result.append(next) - }).dispose() - - usleep(1000 * 200) - - XCTAssert(result.count == 0, "result != []"); - XCTAssert(disposedOne == true, "disposedOne != true"); - XCTAssert(disposedTwo == false, "disposedTwo != false"); - XCTAssert(disposedThree == false, "disposedThree != false"); - XCTAssert(startedFirst == true, "startedFirst != false"); - XCTAssert(startedSecond == false, "startedSecond != false"); - XCTAssert(startedThird == false, "startedThird != false"); - } - - func testRestart() { - let q = DispatchQueue(label: "", attributes: [.concurrent]) - let signal = Signal { subscriber in - q.async { - subscriber.putNext(1) - subscriber.putCompletion() - } - return EmptyDisposable - } - - var result = 0 - - let _ = (signal |> restart |> take(3)).start(next: { next in - result += next - }) - - usleep(100 * 1000) - - XCTAssert(result == 3, "result != 3") - } - - func testPipe() { - let pipe = ValuePipe() - - var result1 = 0 - let disposable1 = pipe.signal().start(next: { next in - result1 += next - }) - - var result2 = 0 - let disposable2 = pipe.signal().start(next: { next in - result2 += next - }) - - pipe.putNext(1) - - XCTAssert(result1 == 1, "result1 != 1") - XCTAssert(result2 == 1, "result2 != 1") - - disposable1.dispose() - - pipe.putNext(1) - - XCTAssert(result1 == 1, "result1 != 1") - XCTAssert(result2 == 2, "result2 != 2") - - disposable2.dispose() - - pipe.putNext(1) - - XCTAssert(result1 == 1, "result1 != 1") - XCTAssert(result2 == 2, "result2 != 2") - } - - func testQueueRecursive() { - let q = Queue() - - let signal = Signal { subscriber in - for _ in 0 ..< 1000 { - subscriber.putNext(1) - } - subscriber.putCompletion() - return EmptyDisposable - } - - let queued = signal - |> mapToQueue { _ -> Signal in - return complete(Void.self, NoError.self) |> deliverOn(q) - } - - let _ = queued.start() - } - - func testReduceSignal() { - let q = Queue() - - let signal = Signal { subscriber in - for i in 0 ..< 1000 { - subscriber.putNext(i) - } - subscriber.putCompletion() - return EmptyDisposable - } - - let reduced = signal - |> reduceLeft(0, generator: { current, next -> Signal<(Int, Passthrough), NoError> in - return Signal { subscriber in - subscriber.putNext((current + next, Passthrough.Some(current + next))) - subscriber.putCompletion() - return EmptyDisposable - } |> deliverOn(q) - }) - - var values: [Int] = [] - let _ = reduced.start(next: { next in - values.append(next) - }) - - q.sync { } - - XCTAssert(values.count == 1001, "count \(values.count) != 1001") - var previous = 0 - for i in 0 ..< 1001 { - let value: Int - if i >= 1000 { - value = previous - } else { - value = previous + i - previous = value - } - previous = value - XCTAssert(values[i] == value, "at \(i): \(values[i]) != \(value)") - } - } - - func testMainQueueReentrant() { - let q = Queue.mainQueue() - - var a = 1 - q.async { - usleep(150 * 1000) - a = 2 - } - - XCTAssert(a == 2) - } -}