mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Temp
This commit is contained in:
parent
b2b2ab9c57
commit
ca0668df3d
@ -37,6 +37,8 @@ swift_library(
|
||||
deps = [
|
||||
"//submodules/GZip:GZip",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/SSignalKit/SSignalKit:SSignalKit",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -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<Int, NoError>.single(1234).start(next: { next in
|
||||
print("from swift signal: \(next)")
|
||||
})
|
||||
disposable2.dispose()
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -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)"
|
||||
|
@ -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 <AsyncDisplayKit/ASPagerFlowLayout.h>
|
||||
|
||||
@interface ASPagerFlowLayout () {
|
||||
BOOL _didRotate;
|
||||
CGRect _cachedCollectionViewBounds;
|
||||
NSIndexPath *_currentIndexPath;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASPagerFlowLayout
|
||||
|
||||
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
|
||||
{
|
||||
NSInteger currentPage = ceil(proposedContentOffset.x / self.collectionView.bounds.size.width);
|
||||
_currentIndexPath = [NSIndexPath indexPathForItem:currentPage inSection:0];
|
||||
|
||||
return [super targetContentOffsetForProposedContentOffset:proposedContentOffset withScrollingVelocity:velocity];
|
||||
}
|
||||
|
||||
|
||||
- (void)prepareForAnimatedBoundsChange:(CGRect)oldBounds
|
||||
{
|
||||
// Cache the current page if a rotation did happen. This happens before the rotation animation
|
||||
// is occuring and the bounds changed so we use this as an opportunity to cache the current index path
|
||||
if (_cachedCollectionViewBounds.size.width != self.collectionView.bounds.size.width) {
|
||||
_cachedCollectionViewBounds = self.collectionView.bounds;
|
||||
|
||||
// Figurring out current page based on the old bounds visible space
|
||||
CGRect visibleRect = oldBounds;
|
||||
|
||||
CGFloat visibleXCenter = CGRectGetMidX(visibleRect);
|
||||
NSArray<UICollectionViewLayoutAttributes *> *layoutAttributes = [self layoutAttributesForElementsInRect:visibleRect];
|
||||
for (UICollectionViewLayoutAttributes *attributes in layoutAttributes) {
|
||||
if ([attributes representedElementCategory] == UICollectionElementCategoryCell && attributes.center.x == visibleXCenter) {
|
||||
_currentIndexPath = attributes.indexPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_didRotate = YES;
|
||||
}
|
||||
|
||||
[super prepareForAnimatedBoundsChange:oldBounds];
|
||||
}
|
||||
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
|
||||
{
|
||||
// Don't mess around if the user is interacting with the page node. Although if just a rotation happened we should
|
||||
// try to use the current index path to not end up setting the target content offset to something in between pages
|
||||
if (_didRotate || (!self.collectionView.isDecelerating && !self.collectionView.isTracking)) {
|
||||
_didRotate = NO;
|
||||
if (_currentIndexPath) {
|
||||
return [self _targetContentOffsetForItemAtIndexPath:_currentIndexPath proposedContentOffset:proposedContentOffset];
|
||||
}
|
||||
}
|
||||
|
||||
return [super targetContentOffsetForProposedContentOffset:proposedContentOffset];
|
||||
}
|
||||
|
||||
- (CGPoint)_targetContentOffsetForItemAtIndexPath:(NSIndexPath *)indexPath proposedContentOffset:(CGPoint)proposedContentOffset
|
||||
{
|
||||
if ([self _dataSourceIsEmpty]) {
|
||||
return proposedContentOffset;
|
||||
}
|
||||
|
||||
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:_currentIndexPath];
|
||||
if (attributes == nil) {
|
||||
return proposedContentOffset;
|
||||
}
|
||||
|
||||
CGFloat xOffset = (CGRectGetWidth(self.collectionView.bounds) - CGRectGetWidth(attributes.frame)) / 2.0;
|
||||
return CGPointMake(attributes.frame.origin.x - xOffset, proposedContentOffset.y);
|
||||
}
|
||||
|
||||
- (BOOL)_dataSourceIsEmpty
|
||||
{
|
||||
return ([self.collectionView numberOfSections] == 0 ||
|
||||
[self.collectionView numberOfItemsInSection:0] == 0);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
@ -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 <AsyncDisplayKit/ASCellNode.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, ASLayoutElementPropertyType) {
|
||||
ASLayoutElementPropertyFlexGrow = 0,
|
||||
ASLayoutElementPropertyFlexShrink,
|
||||
ASLayoutElementPropertyAlignSelf,
|
||||
ASLayoutElementPropertyFlexBasis,
|
||||
ASLayoutElementPropertySpacingBefore,
|
||||
ASLayoutElementPropertySpacingAfter,
|
||||
ASLayoutElementPropertyAscender,
|
||||
ASLayoutElementPropertyDescender,
|
||||
ASLayoutElementPropertyCount
|
||||
};
|
||||
|
||||
@interface ASLayoutElementInspectorCell : ASCellNode
|
||||
|
||||
- (instancetype)initWithProperty:(ASLayoutElementPropertyType)property layoutElementToEdit:(id<ASLayoutElement>)layoutable NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
@ -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 <AsyncDisplayKit/ASLayoutElementInspectorCell.h>
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, CellDataType) {
|
||||
CellDataTypeBool,
|
||||
CellDataTypeFloat,
|
||||
};
|
||||
|
||||
__weak static ASLayoutElementInspectorCell *__currentlyOpenedCell = nil;
|
||||
|
||||
@protocol InspectorCellEditingBubbleProtocol <NSObject>
|
||||
- (void)valueChangedToIndex:(NSUInteger)index;
|
||||
@end
|
||||
|
||||
@interface ASLayoutElementInspectorCellEditingBubble : ASDisplayNode
|
||||
@property (nonatomic, strong, readwrite) id<InspectorCellEditingBubbleProtocol> delegate;
|
||||
- (instancetype)initWithEnumOptions:(BOOL)yes enumStrings:(NSArray<NSString *> *)options currentOptionIndex:(NSUInteger)currentOption;
|
||||
- (instancetype)initWithSliderMinValue:(CGFloat)min maxValue:(CGFloat)max currentValue:(CGFloat)current
|
||||
;@end
|
||||
|
||||
@interface ASLayoutElementInspectorCell () <InspectorCellEditingBubbleProtocol>
|
||||
@end
|
||||
|
||||
@implementation ASLayoutElementInspectorCell
|
||||
{
|
||||
ASLayoutElementPropertyType _propertyType;
|
||||
CellDataType _dataType;
|
||||
id<ASLayoutElement> _layoutElementToEdit;
|
||||
|
||||
ASButtonNode *_buttonNode;
|
||||
ASTextNode *_textNode;
|
||||
ASTextNode *_textNode2;
|
||||
|
||||
ASLayoutElementInspectorCellEditingBubble *_textBubble;
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithProperty:(ASLayoutElementPropertyType)property layoutElementToEdit:(id<ASLayoutElement>)layoutElement
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
|
||||
_propertyType = property;
|
||||
_dataType = [ASLayoutElementInspectorCell dataTypeForProperty:property];
|
||||
_layoutElementToEdit = layoutElement;
|
||||
|
||||
self.automaticallyManagesSubnodes = YES;
|
||||
|
||||
_buttonNode = [self makeBtnNodeWithTitle:[ASLayoutElementInspectorCell propertyStringForPropertyType:property]];
|
||||
[_buttonNode addTarget:self action:@selector(buttonTapped:) forControlEvents:ASControlNodeEventTouchUpInside];
|
||||
|
||||
_textNode = [[ASTextNode alloc] init];
|
||||
_textNode.attributedText = [ASLayoutElementInspectorCell propertyValueAttributedStringForProperty:property withLayoutElement:layoutElement];
|
||||
|
||||
[self updateButtonStateForProperty:property withLayoutElement:layoutElement];
|
||||
|
||||
_textNode2 = [[ASTextNode alloc] init];
|
||||
_textNode2.attributedText = [ASLayoutElementInspectorCell propertyValueDetailAttributedStringForProperty:property withLayoutElement:layoutElement];
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateButtonStateForProperty:(ASLayoutElementPropertyType)property withLayoutElement:(id<ASLayoutElement>)layoutElement
|
||||
{
|
||||
if (property == ASLayoutElementPropertyFlexGrow) {
|
||||
_buttonNode.selected = layoutElement.style.flexGrow;
|
||||
}
|
||||
else if (property == ASLayoutElementPropertyFlexShrink) {
|
||||
_buttonNode.selected = layoutElement.style.flexShrink;
|
||||
}
|
||||
}
|
||||
|
||||
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
ASStackLayoutSpec *horizontalSpec = [ASStackLayoutSpec horizontalStackLayoutSpec];
|
||||
horizontalSpec.children = @[_buttonNode, _textNode];
|
||||
horizontalSpec.style.flexGrow = 1.0;
|
||||
horizontalSpec.alignItems = ASStackLayoutAlignItemsCenter;
|
||||
horizontalSpec.justifyContent = ASStackLayoutJustifyContentSpaceBetween;
|
||||
|
||||
ASLayoutSpec *childSpec;
|
||||
if (_textBubble) {
|
||||
ASStackLayoutSpec *verticalSpec = [ASStackLayoutSpec verticalStackLayoutSpec];
|
||||
verticalSpec.children = @[horizontalSpec, _textBubble];
|
||||
verticalSpec.spacing = 8;
|
||||
verticalSpec.style.flexGrow = 1.0;
|
||||
_textBubble.style.flexGrow = 1.0;
|
||||
childSpec = verticalSpec;
|
||||
} else {
|
||||
childSpec = horizontalSpec;
|
||||
}
|
||||
ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(2, 4, 2, 4) child:childSpec];
|
||||
insetSpec.style.flexGrow =1.0;
|
||||
|
||||
return insetSpec;
|
||||
}
|
||||
|
||||
+ (NSAttributedString *)propertyValueAttributedStringForProperty:(ASLayoutElementPropertyType)property withLayoutElement:(id<ASLayoutElement>)layoutElement
|
||||
{
|
||||
NSString *valueString;
|
||||
|
||||
switch (property) {
|
||||
case ASLayoutElementPropertyFlexGrow:
|
||||
valueString = layoutElement.style.flexGrow ? @"YES" : @"NO";
|
||||
break;
|
||||
case ASLayoutElementPropertyFlexShrink:
|
||||
valueString = layoutElement.style.flexShrink ? @"YES" : @"NO";
|
||||
break;
|
||||
case ASLayoutElementPropertyAlignSelf:
|
||||
valueString = [ASLayoutElementInspectorCell alignSelfEnumValueString:layoutElement.style.alignSelf];
|
||||
break;
|
||||
case ASLayoutElementPropertyFlexBasis:
|
||||
if (layoutElement.style.flexBasis.unit && layoutElement.style.flexBasis.value) { // ENUM TYPE
|
||||
valueString = [NSString stringWithFormat:@"%0.0f %@", layoutElement.style.flexBasis.value,
|
||||
[ASLayoutElementInspectorCell ASRelativeDimensionEnumString:layoutElement.style.alignSelf]];
|
||||
} else {
|
||||
valueString = @"0 pts";
|
||||
}
|
||||
break;
|
||||
case ASLayoutElementPropertySpacingBefore:
|
||||
valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.spacingBefore];
|
||||
break;
|
||||
case ASLayoutElementPropertySpacingAfter:
|
||||
valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.spacingAfter];
|
||||
break;
|
||||
case ASLayoutElementPropertyAscender:
|
||||
valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.ascender];
|
||||
break;
|
||||
case ASLayoutElementPropertyDescender:
|
||||
valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.descender];
|
||||
break;
|
||||
default:
|
||||
valueString = @"?";
|
||||
break;
|
||||
}
|
||||
return [ASLayoutElementInspectorCell attributedStringFromString:valueString];
|
||||
}
|
||||
|
||||
+ (NSAttributedString *)propertyValueDetailAttributedStringForProperty:(ASLayoutElementPropertyType)property withLayoutElement:(id<ASLayoutElement>)layoutElement
|
||||
{
|
||||
NSString *valueString;
|
||||
|
||||
switch (property) {
|
||||
case ASLayoutElementPropertyFlexGrow:
|
||||
case ASLayoutElementPropertyFlexShrink:
|
||||
case ASLayoutElementPropertyAlignSelf:
|
||||
case ASLayoutElementPropertyFlexBasis:
|
||||
case ASLayoutElementPropertySpacingBefore:
|
||||
case ASLayoutElementPropertySpacingAfter:
|
||||
case ASLayoutElementPropertyAscender:
|
||||
case ASLayoutElementPropertyDescender:
|
||||
default:
|
||||
return nil;
|
||||
}
|
||||
return [ASLayoutElementInspectorCell attributedStringFromString:valueString];
|
||||
}
|
||||
|
||||
- (void)endEditingValue
|
||||
{
|
||||
_textBubble = nil;
|
||||
__currentlyOpenedCell = nil;
|
||||
_buttonNode.selected = NO;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)beginEditingValue
|
||||
{
|
||||
_textBubble.delegate = self;
|
||||
__currentlyOpenedCell = self;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)valueChangedToIndex:(NSUInteger)index
|
||||
{
|
||||
switch (_propertyType) {
|
||||
|
||||
case ASLayoutElementPropertyAlignSelf:
|
||||
_layoutElementToEdit.style.alignSelf = (ASStackLayoutAlignSelf)index;
|
||||
_textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[ASLayoutElementInspectorCell alignSelfEnumValueString:index]];
|
||||
break;
|
||||
|
||||
case ASLayoutElementPropertySpacingBefore:
|
||||
_layoutElementToEdit.style.spacingBefore = (CGFloat)index;
|
||||
_textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingBefore]];
|
||||
break;
|
||||
|
||||
case ASLayoutElementPropertySpacingAfter:
|
||||
_layoutElementToEdit.style.spacingAfter = (CGFloat)index;
|
||||
_textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingAfter]];
|
||||
break;
|
||||
|
||||
case ASLayoutElementPropertyAscender:
|
||||
_layoutElementToEdit.style.ascender = (CGFloat)index;
|
||||
_textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.ascender]];
|
||||
break;
|
||||
|
||||
case ASLayoutElementPropertyDescender:
|
||||
_layoutElementToEdit.style.descender = (CGFloat)index;
|
||||
_textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.descender]];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
#pragma mark - gesture handling
|
||||
|
||||
- (void)buttonTapped:(ASButtonNode *)sender
|
||||
{
|
||||
BOOL selfIsEditing = (self == __currentlyOpenedCell);
|
||||
[__currentlyOpenedCell endEditingValue];
|
||||
if (selfIsEditing) {
|
||||
sender.selected = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
// NSUInteger currentAlignSelfValue;
|
||||
// NSUInteger nextAlignSelfValue;
|
||||
// CGFloat newValue;
|
||||
|
||||
sender.selected = !sender.selected;
|
||||
switch (_propertyType) {
|
||||
|
||||
case ASLayoutElementPropertyFlexGrow:
|
||||
_layoutElementToEdit.style.flexGrow = sender.isSelected ? 1.0 : 0.0;
|
||||
_textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:sender.selected ? @"YES" : @"NO"];
|
||||
break;
|
||||
|
||||
case ASLayoutElementPropertyFlexShrink:
|
||||
_layoutElementToEdit.style.flexShrink = sender.isSelected ? 1.0 : 0.0;
|
||||
_textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:sender.selected ? @"YES" : @"NO"];
|
||||
break;
|
||||
|
||||
case ASLayoutElementPropertyAlignSelf:
|
||||
_textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithEnumOptions:YES
|
||||
enumStrings:[ASLayoutElementInspectorCell alignSelfEnumStringArray]
|
||||
currentOptionIndex:_layoutElementToEdit.style.alignSelf];
|
||||
|
||||
[self beginEditingValue];
|
||||
// if ([self layoutSpec]) {
|
||||
// currentAlignSelfValue = [[self layoutSpec] alignSelf];
|
||||
// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0;
|
||||
// [[self layoutSpec] setAlignSelf:nextAlignSelfValue];
|
||||
//
|
||||
// } else if ([self node]) {
|
||||
// currentAlignSelfValue = [[self node] alignSelf];
|
||||
// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0;
|
||||
// [[self node] setAlignSelf:nextAlignSelfValue];
|
||||
// }
|
||||
break;
|
||||
|
||||
case ASLayoutElementPropertySpacingBefore:
|
||||
_textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.spacingBefore];
|
||||
[self beginEditingValue];
|
||||
_textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingBefore]];
|
||||
break;
|
||||
|
||||
case ASLayoutElementPropertySpacingAfter:
|
||||
_textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.spacingAfter];
|
||||
[self beginEditingValue];
|
||||
_textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingAfter]];
|
||||
break;
|
||||
|
||||
|
||||
case ASLayoutElementPropertyAscender:
|
||||
_textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.ascender];
|
||||
[self beginEditingValue];
|
||||
_textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.ascender]];
|
||||
break;
|
||||
|
||||
case ASLayoutElementPropertyDescender:
|
||||
_textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.descender];
|
||||
[self beginEditingValue];
|
||||
_textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.descender]];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
#pragma mark - cast layoutElementToEdit
|
||||
|
||||
- (ASDisplayNode *)node
|
||||
{
|
||||
if (_layoutElementToEdit.layoutElementType == ASLayoutElementTypeDisplayNode) {
|
||||
return (ASDisplayNode *)_layoutElementToEdit;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (ASLayoutSpec *)layoutSpec
|
||||
{
|
||||
if (_layoutElementToEdit.layoutElementType == ASLayoutElementTypeLayoutSpec) {
|
||||
return (ASLayoutSpec *)_layoutElementToEdit;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - data / property type helper methods
|
||||
|
||||
+ (CellDataType)dataTypeForProperty:(ASLayoutElementPropertyType)property
|
||||
{
|
||||
switch (property) {
|
||||
|
||||
case ASLayoutElementPropertyFlexGrow:
|
||||
case ASLayoutElementPropertyFlexShrink:
|
||||
return CellDataTypeBool;
|
||||
|
||||
case ASLayoutElementPropertySpacingBefore:
|
||||
case ASLayoutElementPropertySpacingAfter:
|
||||
case ASLayoutElementPropertyAscender:
|
||||
case ASLayoutElementPropertyDescender:
|
||||
return CellDataTypeFloat;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return CellDataTypeBool;
|
||||
}
|
||||
|
||||
+ (NSString *)propertyStringForPropertyType:(ASLayoutElementPropertyType)property
|
||||
{
|
||||
NSString *string;
|
||||
switch (property) {
|
||||
case ASLayoutElementPropertyFlexGrow:
|
||||
string = @"FlexGrow";
|
||||
break;
|
||||
case ASLayoutElementPropertyFlexShrink:
|
||||
string = @"FlexShrink";
|
||||
break;
|
||||
case ASLayoutElementPropertyAlignSelf:
|
||||
string = @"AlignSelf";
|
||||
break;
|
||||
case ASLayoutElementPropertyFlexBasis:
|
||||
string = @"FlexBasis";
|
||||
break;
|
||||
case ASLayoutElementPropertySpacingBefore:
|
||||
string = @"SpacingBefore";
|
||||
break;
|
||||
case ASLayoutElementPropertySpacingAfter:
|
||||
string = @"SpacingAfter";
|
||||
break;
|
||||
case ASLayoutElementPropertyAscender:
|
||||
string = @"Ascender";
|
||||
break;
|
||||
case ASLayoutElementPropertyDescender:
|
||||
string = @"Descender";
|
||||
break;
|
||||
default:
|
||||
string = @"Unknown";
|
||||
break;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
+ (NSDictionary *)alignSelfTypeNames
|
||||
{
|
||||
return @{@(ASStackLayoutAlignSelfAuto) : @"Auto",
|
||||
@(ASStackLayoutAlignSelfStart) : @"Start",
|
||||
@(ASStackLayoutAlignSelfEnd) : @"End",
|
||||
@(ASStackLayoutAlignSelfCenter) : @"Center",
|
||||
@(ASStackLayoutAlignSelfStretch) : @"Stretch"};
|
||||
}
|
||||
|
||||
+ (NSString *)alignSelfEnumValueString:(NSUInteger)type
|
||||
{
|
||||
return [[self class] alignSelfTypeNames][@(type)];
|
||||
}
|
||||
|
||||
+ (NSArray <NSString *> *)alignSelfEnumStringArray
|
||||
{
|
||||
return @[@"ASStackLayoutAlignSelfAuto",
|
||||
@"ASStackLayoutAlignSelfStart",
|
||||
@"ASStackLayoutAlignSelfEnd",
|
||||
@"ASStackLayoutAlignSelfCenter",
|
||||
@"ASStackLayoutAlignSelfStretch"];
|
||||
}
|
||||
|
||||
+ (NSDictionary *)ASRelativeDimensionTypeNames
|
||||
{
|
||||
return @{@(ASDimensionUnitPoints) : @"pts",
|
||||
@(ASDimensionUnitFraction) : @"%"};
|
||||
}
|
||||
|
||||
+ (NSString *)ASRelativeDimensionEnumString:(NSUInteger)type
|
||||
{
|
||||
return [[self class] ASRelativeDimensionTypeNames][@(type)];
|
||||
}
|
||||
|
||||
#pragma mark - formatting helper methods
|
||||
|
||||
+ (NSAttributedString *)attributedStringFromString:(NSString *)string
|
||||
{
|
||||
return [ASLayoutElementInspectorCell attributedStringFromString:string withTextColor:[UIColor whiteColor]];
|
||||
}
|
||||
|
||||
+ (NSAttributedString *)attributedStringFromString:(NSString *)string withTextColor:(nullable UIColor *)color
|
||||
{
|
||||
NSDictionary *attributes = @{NSForegroundColorAttributeName : color,
|
||||
NSFontAttributeName : [UIFont fontWithName:@"Menlo-Regular" size:12]};
|
||||
|
||||
return [[NSAttributedString alloc] initWithString:string attributes:attributes];
|
||||
}
|
||||
|
||||
- (ASButtonNode *)makeBtnNodeWithTitle:(NSString *)title
|
||||
{
|
||||
UIColor *orangeColor = [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1];
|
||||
UIImage *orangeStretchBtnImg = [ASLayoutElementInspectorCell imageForButtonWithBackgroundColor:orangeColor
|
||||
borderColor:[UIColor whiteColor]
|
||||
borderWidth:3];
|
||||
UIImage *greyStretchBtnImg = [ASLayoutElementInspectorCell imageForButtonWithBackgroundColor:[UIColor darkGrayColor]
|
||||
borderColor:[UIColor lightGrayColor]
|
||||
borderWidth:3];
|
||||
UIImage *clearStretchBtnImg = [ASLayoutElementInspectorCell imageForButtonWithBackgroundColor:[UIColor clearColor]
|
||||
borderColor:[UIColor whiteColor]
|
||||
borderWidth:3];
|
||||
ASButtonNode *btn = [[ASButtonNode alloc] init];
|
||||
btn.contentEdgeInsets = UIEdgeInsetsMake(5, 5, 5, 5);
|
||||
[btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:title] forState:ASControlStateNormal];
|
||||
[btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:title withTextColor:[UIColor lightGrayColor]] forState:ASControlStateDisabled];
|
||||
[btn setBackgroundImage:clearStretchBtnImg forState:ASControlStateNormal];
|
||||
[btn setBackgroundImage:orangeStretchBtnImg forState:ASControlStateSelected];
|
||||
[btn setBackgroundImage:greyStretchBtnImg forState:ASControlStateDisabled];
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
#define CORNER_RADIUS 3
|
||||
+ (UIImage *)imageForButtonWithBackgroundColor:(UIColor *)backgroundColor borderColor:(UIColor *)borderColor borderWidth:(CGFloat)width
|
||||
{
|
||||
CGSize unstretchedSize = CGSizeMake(2 * CORNER_RADIUS + 1, 2 * CORNER_RADIUS + 1);
|
||||
CGRect rect = (CGRect) {CGPointZero, unstretchedSize};
|
||||
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:CORNER_RADIUS];
|
||||
|
||||
// create a graphics context for the following status button
|
||||
UIGraphicsBeginImageContextWithOptions(unstretchedSize, NO, 0);
|
||||
|
||||
[path addClip];
|
||||
[backgroundColor setFill];
|
||||
[path fill];
|
||||
|
||||
path.lineWidth = width;
|
||||
[borderColor setStroke];
|
||||
[path stroke];
|
||||
|
||||
UIImage *btnImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return [btnImage stretchableImageWithLeftCapWidth:CORNER_RADIUS topCapHeight:CORNER_RADIUS];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation ASLayoutElementInspectorCellEditingBubble
|
||||
{
|
||||
NSMutableArray<ASButtonNode *> *_textNodes;
|
||||
ASDisplayNode *_slider;
|
||||
}
|
||||
|
||||
- (instancetype)initWithEnumOptions:(BOOL)yes enumStrings:(NSArray<NSString *> *)options currentOptionIndex:(NSUInteger)currentOption
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.automaticallyManagesSubnodes = YES;
|
||||
self.backgroundColor = [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1];
|
||||
|
||||
_textNodes = [[NSMutableArray alloc] init];
|
||||
int index = 0;
|
||||
for (NSString *optionStr in options) {
|
||||
ASButtonNode *btn = [[ASButtonNode alloc] init];
|
||||
[btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:optionStr] forState:ASControlStateNormal];
|
||||
[btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:optionStr withTextColor:[UIColor redColor]]
|
||||
forState:ASControlStateSelected];
|
||||
[btn addTarget:self action:@selector(enumOptionSelected:) forControlEvents:ASControlNodeEventTouchUpInside];
|
||||
btn.selected = (index == currentOption) ? YES : NO;
|
||||
[_textNodes addObject:btn];
|
||||
index++;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithSliderMinValue:(CGFloat)min maxValue:(CGFloat)max currentValue:(CGFloat)current
|
||||
{
|
||||
if (self = [super init]) {
|
||||
self.userInteractionEnabled = YES;
|
||||
self.automaticallyManagesSubnodes = YES;
|
||||
self.backgroundColor = [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1];
|
||||
|
||||
__weak id weakSelf = self;
|
||||
_slider = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull{
|
||||
UISlider *slider = [[UISlider alloc] init];
|
||||
slider.minimumValue = min;
|
||||
slider.maximumValue = max;
|
||||
slider.value = current;
|
||||
[slider addTarget:weakSelf action:@selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged];
|
||||
|
||||
return slider;
|
||||
}];
|
||||
_slider.userInteractionEnabled = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
_slider.style.preferredSize = CGSizeMake(constrainedSize.max.width, 25);
|
||||
|
||||
NSMutableArray *children = [[NSMutableArray alloc] init];
|
||||
if (_textNodes) {
|
||||
ASStackLayoutSpec *textStack = [ASStackLayoutSpec verticalStackLayoutSpec];
|
||||
textStack.children = _textNodes;
|
||||
textStack.spacing = 2;
|
||||
[children addObject:textStack];
|
||||
}
|
||||
if (_slider) {
|
||||
_slider.style.flexGrow = 1.0;
|
||||
[children addObject:_slider];
|
||||
}
|
||||
|
||||
ASStackLayoutSpec *verticalStackSpec = [ASStackLayoutSpec verticalStackLayoutSpec];
|
||||
verticalStackSpec.children = children;
|
||||
verticalStackSpec.spacing = 2;
|
||||
verticalStackSpec.style.flexGrow = 1.0;
|
||||
verticalStackSpec.style.alignSelf = ASStackLayoutAlignSelfStretch;
|
||||
|
||||
ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(8, 8, 8, 8) child:verticalStackSpec];
|
||||
|
||||
return insetSpec;
|
||||
}
|
||||
|
||||
#pragma mark - gesture handling
|
||||
- (void)enumOptionSelected:(ASButtonNode *)sender
|
||||
{
|
||||
sender.selected = !sender.selected;
|
||||
for (ASButtonNode *node in _textNodes) {
|
||||
if (node != sender) {
|
||||
node.selected = NO;
|
||||
}
|
||||
}
|
||||
[self.delegate valueChangedToIndex:[_textNodes indexOfObject:sender]];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)sliderValueChanged:(UISlider *)sender
|
||||
{
|
||||
[self.delegate valueChangedToIndex:roundf(sender.value)];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#endif
|
@ -1,426 +0,0 @@
|
||||
//
|
||||
// ASLayoutElementInspectorNode.m
|
||||
// Sample
|
||||
//
|
||||
// Created by Hannah Troisi on 3/19/16.
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASLayoutElementInspectorNode.h>
|
||||
#ifndef MINIMAL_ASDK
|
||||
#import <AsyncDisplayKit/ASLayoutElementInspectorCell.h>
|
||||
#endif
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASLayoutSpec+Debug.h>
|
||||
#import <AsyncDisplayKit/ASTableNode.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
|
||||
@interface ASLayoutElementInspectorNode ()
|
||||
#ifndef MINIMAL_ASDK
|
||||
<ASTableDelegate, ASTableDataSource>
|
||||
#endif
|
||||
@end
|
||||
|
||||
@implementation ASLayoutElementInspectorNode
|
||||
{
|
||||
#ifndef MINIMAL_ASDK
|
||||
ASTableNode *_tableNode;
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark - class methods
|
||||
+ (instancetype)sharedInstance
|
||||
{
|
||||
static ASLayoutElementInspectorNode *__inspector = nil;
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
__inspector = [[ASLayoutElementInspectorNode alloc] init];
|
||||
});
|
||||
|
||||
return __inspector;
|
||||
}
|
||||
|
||||
#pragma mark - lifecycle
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
_tableNode = [[ASTableNode alloc] init];
|
||||
_tableNode.delegate = self;
|
||||
_tableNode.dataSource = self;
|
||||
|
||||
[self addSubnode:_tableNode]; // required because of manual layout
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)didLoad
|
||||
{
|
||||
[super didLoad];
|
||||
#ifndef MINIMAL_ASDK
|
||||
_tableNode.view.backgroundColor = [UIColor colorWithRed:40/255.0 green:43/255.0 blue:53/255.0 alpha:1];
|
||||
_tableNode.view.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
_tableNode.view.allowsSelection = NO;
|
||||
_tableNode.view.sectionHeaderHeight = 40;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)layout
|
||||
{
|
||||
[super layout];
|
||||
#ifndef MINIMAL_ASDK
|
||||
_tableNode.frame = self.bounds;
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark - intstance methods
|
||||
- (void)setLayoutElementToEdit:(id<ASLayoutElement>)layoutElementToEdit
|
||||
{
|
||||
if (_layoutElementToEdit != layoutElementToEdit) {
|
||||
_layoutElementToEdit = layoutElementToEdit;
|
||||
}
|
||||
#ifndef MINIMAL_ASDK
|
||||
[_tableNode reloadData];
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark - ASTableDataSource
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.section == 0) {
|
||||
NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1],
|
||||
NSFontAttributeName : [UIFont fontWithName:@"Menlo-Regular" size:12]};
|
||||
ASTextCellNode *textCell = [[ASTextCellNode alloc] initWithAttributes:attributes insets:UIEdgeInsetsMake(0, 4, 0, 0)];
|
||||
textCell.text = [_layoutElementToEdit description];
|
||||
return textCell;
|
||||
} else {
|
||||
return [[ASLayoutElementInspectorCell alloc] initWithProperty:(ASLayoutElementPropertyType)indexPath.row layoutElementToEdit:_layoutElementToEdit];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
if (section == 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return ASLayoutElementPropertyCount;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
|
||||
{
|
||||
UILabel *headerTitle = [[UILabel alloc] initWithFrame:CGRectZero];
|
||||
|
||||
NSString *title;
|
||||
if (section == 0) {
|
||||
title = @"<Layoutable> Item";
|
||||
} else {
|
||||
title = @"<Layoutable> Properties";
|
||||
}
|
||||
|
||||
NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor whiteColor],
|
||||
NSFontAttributeName : [UIFont fontWithName:@"Menlo-Bold" size:12]};
|
||||
headerTitle.attributedText = [[NSAttributedString alloc] initWithString:title attributes:attributes];
|
||||
|
||||
return headerTitle;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
//- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
|
||||
//{
|
||||
// // navigate layout hierarchy
|
||||
//
|
||||
// _parentNodeNavBtn.alignSelf = ASStackLayoutAlignSelfCenter;
|
||||
// _childNodeNavBtn.alignSelf = ASStackLayoutAlignSelfCenter;
|
||||
//
|
||||
// ASStackLayoutSpec *horizontalStackNav = [ASStackLayoutSpec horizontalStackLayoutSpec];
|
||||
// horizontalStackNav.style.flexGrow = 1.0;
|
||||
// horizontalStackNav.alignSelf = ASStackLayoutAlignSelfCenter;
|
||||
// horizontalStackNav.children = @[_siblingNodeLefttNavBtn, _siblingNodeRightNavBtn];
|
||||
//
|
||||
// ASStackLayoutSpec *horizontalStack = [ASStackLayoutSpec horizontalStackLayoutSpec];
|
||||
// horizontalStack.style.flexGrow = 1.0;
|
||||
// ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init];
|
||||
//
|
||||
// spacer.style.flexGrow = 1.0;
|
||||
// horizontalStack.children = @[_flexGrowBtn, spacer];
|
||||
// _flexGrowValue.alignSelf = ASStackLayoutAlignSelfEnd; // FIXME: make framework give a warning if you use ASAlignmentBottom!!!!!
|
||||
//
|
||||
// ASStackLayoutSpec *horizontalStack2 = [ASStackLayoutSpec horizontalStackLayoutSpec];
|
||||
// horizontalStack2.style.flexGrow = 1.0;
|
||||
// horizontalStack2.children = @[_flexShrinkBtn, spacer];
|
||||
// _flexShrinkValue.alignSelf = ASStackLayoutAlignSelfEnd;
|
||||
//
|
||||
// ASStackLayoutSpec *horizontalStack3 = [ASStackLayoutSpec horizontalStackLayoutSpec];
|
||||
// horizontalStack3.style.flexGrow = 1.0;
|
||||
// horizontalStack3.children = @[_flexBasisBtn, spacer, _flexBasisValue];
|
||||
// _flexBasisValue.alignSelf = ASStackLayoutAlignSelfEnd;
|
||||
//
|
||||
// ASStackLayoutSpec *itemDescriptionStack = [ASStackLayoutSpec verticalStackLayoutSpec];
|
||||
// itemDescriptionStack.children = @[_itemDescription];
|
||||
// itemDescriptionStack.spacing = 5;
|
||||
// itemDescriptionStack.style.flexGrow = 1.0;
|
||||
//
|
||||
// ASStackLayoutSpec *layoutableStack = [ASStackLayoutSpec verticalStackLayoutSpec];
|
||||
// layoutableStack.children = @[_layoutablePropertiesSectionTitle, horizontalStack, horizontalStack2, horizontalStack3, _alignSelfBtn];
|
||||
// layoutableStack.spacing = 5;
|
||||
// layoutableStack.style.flexGrow = 1.0;
|
||||
//
|
||||
// ASStackLayoutSpec *layoutSpecStack = [ASStackLayoutSpec verticalStackLayoutSpec];
|
||||
// layoutSpecStack.children = @[_layoutSpecPropertiesSectionTitle, _alignItemsBtn];
|
||||
// layoutSpecStack.spacing = 5;
|
||||
// layoutSpecStack.style.flexGrow = 1.0;
|
||||
//
|
||||
// ASStackLayoutSpec *debugHelpStack = [ASStackLayoutSpec verticalStackLayoutSpec];
|
||||
// debugHelpStack.children = @[_debugSectionTitle, _vizNodeInsetSizeBtn, _vizNodeBordersBtn];
|
||||
// debugHelpStack.spacing = 5;
|
||||
// debugHelpStack.style.flexGrow = 1.0;
|
||||
//
|
||||
// ASStackLayoutSpec *verticalLayoutableStack = [ASStackLayoutSpec verticalStackLayoutSpec];
|
||||
// verticalLayoutableStack.style.flexGrow = 1.0;
|
||||
// verticalLayoutableStack.spacing = 20;
|
||||
// verticalLayoutableStack.children = @[_parentNodeNavBtn, horizontalStackNav, _childNodeNavBtn, itemDescriptionStack, layoutableStack, layoutSpecStack, debugHelpStack];
|
||||
// verticalLayoutableStack.alignItems = ASStackLayoutAlignItemsStretch; // stretch headerStack to fill horizontal space
|
||||
//
|
||||
// ASLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(100, 10, 10, 10) child:verticalLayoutableStack];
|
||||
// insetSpec.style.flexGrow = 1.0;
|
||||
// return insetSpec;
|
||||
//}
|
||||
//
|
||||
//#pragma mark - configure Inspector node for layoutable
|
||||
//- (void)updateInspectorWithLayoutable
|
||||
//{
|
||||
// _itemDescription.attributedText = [self attributedStringFromLayoutable:_layoutElementToEdit];
|
||||
//
|
||||
// if ([self node]) {
|
||||
// UIColor *nodeBackgroundColor = [[self node] backgroundColor];
|
||||
// UIImage *colorBtnImg = [ASLayoutElementInspectorNode imageForButtonWithBackgroundColor:nodeBackgroundColor
|
||||
// borderColor:[UIColor whiteColor]
|
||||
// borderWidth:3];
|
||||
// [_itemBackgroundColorBtn setBackgroundImage:colorBtnImg forState:ASControlStateNormal];
|
||||
// } else {
|
||||
// _itemBackgroundColorBtn.enabled = NO;
|
||||
// }
|
||||
//
|
||||
// _flexGrowBtn.selected = [self.layoutElementToEdit flexGrow];
|
||||
// _flexGrowValue.attributedText = [self attributedStringFromString: (_flexGrowBtn.selected) ? @"YES" : @"NO"];
|
||||
//
|
||||
// _flexShrinkBtn.selected = self.layoutElementToEdit.style.flexShrink;
|
||||
// _flexShrinkValue.attributedText = [self attributedStringFromString: (_flexShrinkBtn.selected) ? @"YES" : @"NO"];
|
||||
//
|
||||
// // _flexBasisBtn.selected = self.layoutElementToEdit.style.flexShrink;
|
||||
// // _flexBasisValue.attributedText = [self attributedStringFromString: (_flexBasisBtn.selected) ? @"YES" : @"NO"];
|
||||
//
|
||||
//
|
||||
// NSUInteger alignSelfValue = [self.layoutElementToEdit alignSelf];
|
||||
// NSString *newTitle = [@"alignSelf:" stringByAppendingString:[self alignSelfName:alignSelfValue]];
|
||||
// [_alignSelfBtn setAttributedTitle:[self attributedStringFromString:newTitle] forState:ASControlStateNormal];
|
||||
//
|
||||
// if ([self layoutSpec]) {
|
||||
// _alignItemsBtn.enabled = YES;
|
||||
//// NSUInteger alignItemsValue = [[self layoutSpec] alignItems];
|
||||
//// newTitle = [@"alignItems:" stringByAppendingString:[self alignSelfName:alignItemsValue]];
|
||||
//// [_alignItemsBtn setAttributedTitle:[self attributedStringFromString:newTitle] forState:ASControlStateNormal];
|
||||
// }
|
||||
//
|
||||
// [self setNeedsLayout];
|
||||
//}
|
||||
|
||||
|
||||
//- (void)enableInspectorNodesForLayoutable
|
||||
//{
|
||||
// if ([self layoutSpec]) {
|
||||
//
|
||||
// _itemBackgroundColorBtn.enabled = YES;
|
||||
// _flexGrowBtn.enabled = YES;
|
||||
// _flexShrinkBtn.enabled = YES;
|
||||
// _flexBasisBtn.enabled = YES;
|
||||
// _alignSelfBtn.enabled = YES;
|
||||
// _spacingBeforeBtn.enabled = YES;
|
||||
// _spacingAfterBtn.enabled = YES;
|
||||
// _alignItemsBtn.enabled = YES;
|
||||
//
|
||||
// } else if ([self node]) {
|
||||
//
|
||||
// _itemBackgroundColorBtn.enabled = YES;
|
||||
// _flexGrowBtn.enabled = YES;
|
||||
// _flexShrinkBtn.enabled = YES;
|
||||
// _flexBasisBtn.enabled = YES;
|
||||
// _alignSelfBtn.enabled = YES;
|
||||
// _spacingBeforeBtn.enabled = YES;
|
||||
// _spacingAfterBtn.enabled = YES;
|
||||
// _alignItemsBtn.enabled = NO;
|
||||
//
|
||||
// } else {
|
||||
//
|
||||
// _itemBackgroundColorBtn.enabled = NO;
|
||||
// _flexGrowBtn.enabled = NO;
|
||||
// _flexShrinkBtn.enabled = NO;
|
||||
// _flexBasisBtn.enabled = NO;
|
||||
// _alignSelfBtn.enabled = NO;
|
||||
// _spacingBeforeBtn.enabled = NO;
|
||||
// _spacingAfterBtn.enabled = NO;
|
||||
// _alignItemsBtn.enabled = YES;
|
||||
// }
|
||||
//}
|
||||
|
||||
//+ (NSDictionary *)alignSelfTypeNames
|
||||
//{
|
||||
// return @{@(ASStackLayoutAlignSelfAuto) : @"Auto",
|
||||
// @(ASStackLayoutAlignSelfStart) : @"Start",
|
||||
// @(ASStackLayoutAlignSelfEnd) : @"End",
|
||||
// @(ASStackLayoutAlignSelfCenter) : @"Center",
|
||||
// @(ASStackLayoutAlignSelfStretch) : @"Stretch"};
|
||||
//}
|
||||
//
|
||||
//- (NSString *)alignSelfName:(NSUInteger)type
|
||||
//{
|
||||
// return [[self class] alignSelfTypeNames][@(type)];
|
||||
//}
|
||||
//
|
||||
//+ (NSDictionary *)alignItemTypeNames
|
||||
//{
|
||||
// return @{@(ASStackLayoutAlignItemsBaselineFirst) : @"BaselineFirst",
|
||||
// @(ASStackLayoutAlignItemsBaselineLast) : @"BaselineLast",
|
||||
// @(ASStackLayoutAlignItemsCenter) : @"Center",
|
||||
// @(ASStackLayoutAlignItemsEnd) : @"End",
|
||||
// @(ASStackLayoutAlignItemsStart) : @"Start",
|
||||
// @(ASStackLayoutAlignItemsStretch) : @"Stretch"};
|
||||
//}
|
||||
//
|
||||
//- (NSString *)alignItemName:(NSUInteger)type
|
||||
//{
|
||||
// return [[self class] alignItemTypeNames][@(type)];
|
||||
//}
|
||||
|
||||
//#pragma mark - gesture handling
|
||||
//- (void)changeColor:(ASButtonNode *)sender
|
||||
//{
|
||||
// if ([self node]) {
|
||||
// NSArray *colorArray = @[[UIColor orangeColor],
|
||||
// [UIColor redColor],
|
||||
// [UIColor greenColor],
|
||||
// [UIColor purpleColor]];
|
||||
//
|
||||
// UIColor *nodeBackgroundColor = [(ASDisplayNode *)self.layoutElementToEdit backgroundColor];
|
||||
//
|
||||
// NSUInteger colorIndex = [colorArray indexOfObject:nodeBackgroundColor];
|
||||
// colorIndex = (colorIndex + 1 < [colorArray count]) ? colorIndex + 1 : 0;
|
||||
//
|
||||
// [[self node] setBackgroundColor: [colorArray objectAtIndex:colorIndex]];
|
||||
// }
|
||||
//
|
||||
// [self updateInspectorWithLayoutable];
|
||||
//}
|
||||
//
|
||||
//- (void)setFlexGrowValue:(ASButtonNode *)sender
|
||||
//{
|
||||
// [sender setSelected:!sender.isSelected]; // FIXME: fix ASControlNode documentation that this is automatic - unlike highlighted, it is up to the application to decide when a button should be selected or not. Selected is a more persistant thing and highlighted is for the moment, like as a user has a finger on it,
|
||||
//
|
||||
// if ([self layoutSpec]) {
|
||||
// [[self layoutSpec] setFlexGrow:sender.isSelected];
|
||||
// } else if ([self node]) {
|
||||
// [[self node] setFlexGrow:sender.isSelected];
|
||||
// }
|
||||
//
|
||||
// [self updateInspectorWithLayoutable];
|
||||
//}
|
||||
//
|
||||
//- (void)setFlexShrinkValue:(ASButtonNode *)sender
|
||||
//{
|
||||
// [sender setSelected:!sender.isSelected]; // FIXME: fix ASControlNode documentation that this is automatic - unlike highlighted, it is up to the application to decide when a button should be selected or not. Selected is a more persistant thing and highlighted is for the moment, like as a user has a finger on it,
|
||||
//
|
||||
// if ([self layoutSpec]) {
|
||||
// [[self layoutSpec] setFlexShrink:sender.isSelected];
|
||||
// } else if ([self node]) {
|
||||
// [[self node] setFlexShrink:sender.isSelected];
|
||||
// }
|
||||
//
|
||||
// [self updateInspectorWithLayoutable];
|
||||
//}
|
||||
//
|
||||
//- (void)setAlignSelfValue:(ASButtonNode *)sender
|
||||
//{
|
||||
// NSUInteger currentAlignSelfValue;
|
||||
// NSUInteger nextAlignSelfValue;
|
||||
//
|
||||
// if ([self layoutSpec]) {
|
||||
// currentAlignSelfValue = [[self layoutSpec] alignSelf];
|
||||
// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0;
|
||||
// [[self layoutSpec] setAlignSelf:nextAlignSelfValue];
|
||||
//
|
||||
// } else if ([self node]) {
|
||||
// currentAlignSelfValue = [[self node] alignSelf];
|
||||
// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0;
|
||||
// [[self node] setAlignSelf:nextAlignSelfValue];
|
||||
// }
|
||||
//
|
||||
// [self updateInspectorWithLayoutable];
|
||||
//}
|
||||
//
|
||||
//- (void)setAlignItemsValue:(ASButtonNode *)sender
|
||||
//{
|
||||
// NSUInteger currentAlignItemsValue;
|
||||
// NSUInteger nextAlignItemsValue;
|
||||
//
|
||||
// if ([self layoutSpec]) {
|
||||
// currentAlignItemsValue = [[self layoutSpec] alignSelf];
|
||||
// nextAlignItemsValue = (currentAlignItemsValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignItemsValue + 1 : 0;
|
||||
//// [[self layoutSpec] setAlignItems:nextAlignItemsValue];
|
||||
//
|
||||
// } else if ([self node]) {
|
||||
// currentAlignItemsValue = [[self node] alignSelf];
|
||||
// nextAlignItemsValue = (currentAlignItemsValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignItemsValue + 1 : 0;
|
||||
//// [[self node] setAlignItems:nextAlignItemsValue];
|
||||
// }
|
||||
//
|
||||
// [self updateInspectorWithLayoutable];
|
||||
//}
|
||||
//- (void)setFlexBasisValue:(ASButtonNode *)sender
|
||||
//{
|
||||
// [sender setSelected:!sender.isSelected]; // FIXME: fix ASControlNode documentation that this is automatic - unlike highlighted, it is up to the application to decide when a button should be selected or not. Selected is a more persistant thing and highlighted is for the moment, like as a user has a finger on it,
|
||||
// FIXME: finish
|
||||
//}
|
||||
//
|
||||
//- (void)setVizNodeInsets:(ASButtonNode *)sender
|
||||
//{
|
||||
// BOOL newState = !sender.selected;
|
||||
//
|
||||
// if (newState == YES) {
|
||||
// self.vizNodeInsetSize = 0;
|
||||
// [self.delegate toggleVisualization:NO]; // FIXME
|
||||
// [self.delegate toggleVisualization:YES]; // FIXME
|
||||
// _vizNodeBordersBtn.selected = YES;
|
||||
//
|
||||
// } else {
|
||||
// self.vizNodeInsetSize = 10;
|
||||
// [self.delegate toggleVisualization:NO]; // FIXME
|
||||
// [self.delegate toggleVisualization:YES]; // FIXME
|
||||
// }
|
||||
//
|
||||
// sender.selected = newState;
|
||||
//}
|
||||
//
|
||||
//- (void)setVizNodeBorders:(ASButtonNode *)sender
|
||||
//{
|
||||
// BOOL newState = !sender.selected;
|
||||
//
|
||||
// [self.delegate toggleVisualization:newState]; // FIXME
|
||||
//
|
||||
// sender.selected = newState;
|
||||
//}
|
||||
|
||||
@end
|
@ -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 <AsyncDisplayKit/ASDataController.h>
|
||||
|
||||
/**
|
||||
* @abstract Subclass of ASDataController that simulates ordering of operations in batch updates defined in UITableView and UICollectionView.
|
||||
*
|
||||
* @discussion The ordering is achieved by using _ASHierarchyChangeSet to enqueue and sort operations.
|
||||
* More information about the ordering and the index paths used for operations can be found here:
|
||||
* https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/TableView_iPhone/ManageInsertDeleteRow/ManageInsertDeleteRow.html#//apple_ref/doc/uid/TP40007451-CH10-SW17
|
||||
*
|
||||
* @see ASDataController
|
||||
* @see _ASHierarchyChangeSet
|
||||
*/
|
||||
@interface ASChangeSetDataController : ASDataController
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
@ -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
|
@ -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 <UIKit/UIKit.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASDataController.h>
|
||||
#import <AsyncDisplayKit/ASDimension.h>
|
||||
|
||||
@class ASDisplayNode;
|
||||
@class ASCollectionDataController;
|
||||
@protocol ASSectionContext;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol ASCollectionDataControllerSource <ASDataControllerSource>
|
||||
|
||||
/**
|
||||
The constrained size range for layout.
|
||||
*/
|
||||
- (ASSizeRange)dataController:(ASCollectionDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
- (NSArray<NSString *> *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController sections:(NSIndexSet *)sections;
|
||||
|
||||
- (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section;
|
||||
|
||||
- (nullable id<ASSectionContext>)dataController:(ASCollectionDataController *)dataController contextForSection:(NSInteger)section;
|
||||
|
||||
@optional
|
||||
|
||||
- (ASCellNode *)dataController:(ASCollectionDataController *)dataController supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
- (ASCellNodeBlock)dataController:(ASCollectionDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASCollectionDataController : ASDataController
|
||||
|
||||
- (instancetype)initWithDataSource:(id<ASCollectionDataControllerSource>)dataSource eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (nullable ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
- (nullable id<ASSectionContext>)contextForSection:(NSInteger)section;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
@ -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 <AsyncDisplayKit/ASCollectionDataController.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASMultidimensionalArrayUtils.h>
|
||||
#import <AsyncDisplayKit/ASCellNode.h>
|
||||
#import <AsyncDisplayKit/ASDataController+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASIndexedNodeContext.h>
|
||||
#import <AsyncDisplayKit/ASSection.h>
|
||||
#import <AsyncDisplayKit/ASSectionContext.h>
|
||||
#import <AsyncDisplayKit/NSIndexSet+ASHelpers.h>
|
||||
|
||||
//#define LOG(...) NSLog(__VA_ARGS__)
|
||||
#define LOG(...)
|
||||
|
||||
@interface ASCollectionDataController () {
|
||||
BOOL _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath;
|
||||
NSInteger _nextSectionID;
|
||||
NSMutableArray<ASSection *> *_sections;
|
||||
NSArray<ASSection *> *_pendingSections;
|
||||
|
||||
/**
|
||||
* supplementaryKinds can only be accessed on the main thread
|
||||
* and so we set this in the -prepare stage, and then read it during the -will
|
||||
* stage of each update operation.
|
||||
*/
|
||||
NSArray *_supplementaryKindsForPendingOperation;
|
||||
}
|
||||
|
||||
- (id<ASCollectionDataControllerSource>)collectionDataSource;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASCollectionDataController {
|
||||
NSMutableDictionary<NSString *, NSMutableArray<ASIndexedNodeContext *> *> *_pendingNodeContexts;
|
||||
}
|
||||
|
||||
- (instancetype)initWithDataSource:(id<ASCollectionDataControllerSource>)dataSource eventLog:(ASEventLog *)eventLog
|
||||
{
|
||||
self = [super initWithDataSource:dataSource eventLog:eventLog];
|
||||
if (self != nil) {
|
||||
_pendingNodeContexts = [NSMutableDictionary dictionary];
|
||||
_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath = [dataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)];
|
||||
|
||||
ASDisplayNodeAssertTrue(_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath || [dataSource respondsToSelector:@selector(dataController:supplementaryNodeOfKind:atIndexPath:)]);
|
||||
|
||||
_nextSectionID = 0;
|
||||
_sections = [NSMutableArray array];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
NSIndexSet *sections = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newSectionCount)];
|
||||
|
||||
[_sections removeAllObjects];
|
||||
[self _populatePendingSectionsFromDataSource:sections];
|
||||
|
||||
for (NSString *kind in [self supplementaryKindsInSections:sections]) {
|
||||
LOG(@"Populating elements of kind: %@", kind);
|
||||
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
|
||||
[self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts];
|
||||
_pendingNodeContexts[kind] = contexts;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount
|
||||
{
|
||||
NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newSectionCount)];
|
||||
|
||||
[self applyPendingSections:sectionIndexes];
|
||||
|
||||
// Assert that ASDataController has already deleted all the old sections for us.
|
||||
ASDisplayNodeAssert([self editingNodesOfKind:ASDataControllerRowNodeKind].count == 0, @"Expected that all old sections were deleted before %@. Sections: %@", NSStringFromSelector(_cmd), [self editingNodesOfKind:ASDataControllerRowNodeKind]);
|
||||
|
||||
[_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray<ASIndexedNodeContext *> * _Nonnull contexts, __unused BOOL * _Nonnull stop) {
|
||||
// Insert each section
|
||||
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:newSectionCount];
|
||||
for (int i = 0; i < newSectionCount; i++) {
|
||||
[sections addObject:[NSMutableArray array]];
|
||||
}
|
||||
[self insertSections:sections ofKind:kind atIndexSet:sectionIndexes completion:nil];
|
||||
|
||||
[self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
|
||||
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
|
||||
}];
|
||||
}];
|
||||
[_pendingNodeContexts removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)prepareForInsertSections:(NSIndexSet *)sections
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[self _populatePendingSectionsFromDataSource:sections];
|
||||
|
||||
for (NSString *kind in [self supplementaryKindsInSections:sections]) {
|
||||
LOG(@"Populating elements of kind: %@, for sections: %@", kind, sections);
|
||||
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
|
||||
[self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts];
|
||||
_pendingNodeContexts[kind] = contexts;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)willInsertSections:(NSIndexSet *)sections
|
||||
{
|
||||
[self applyPendingSections:sections];
|
||||
|
||||
[_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray<ASIndexedNodeContext *> * _Nonnull contexts, BOOL * _Nonnull stop) {
|
||||
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count];
|
||||
for (NSUInteger i = 0; i < sections.count; i++) {
|
||||
[sectionArray addObject:[NSMutableArray array]];
|
||||
}
|
||||
|
||||
[self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil];
|
||||
[self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
|
||||
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
|
||||
}];
|
||||
}];
|
||||
[_pendingNodeContexts removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)willDeleteSections:(NSIndexSet *)sections
|
||||
{
|
||||
[_sections removeObjectsAtIndexes:sections];
|
||||
}
|
||||
|
||||
- (void)prepareForInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
NSIndexSet *sections = [NSIndexSet as_sectionsFromIndexPaths:indexPaths];
|
||||
for (NSString *kind in [self supplementaryKindsInSections:sections]) {
|
||||
LOG(@"Populating elements of kind: %@, for index paths: %@", kind, indexPaths);
|
||||
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
|
||||
[self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts];
|
||||
_pendingNodeContexts[kind] = contexts;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)willInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
|
||||
{
|
||||
[_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray<ASIndexedNodeContext *> * _Nonnull contexts, BOOL * _Nonnull stop) {
|
||||
[self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
|
||||
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
|
||||
}];
|
||||
}];
|
||||
|
||||
[_pendingNodeContexts removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)prepareForDeleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
NSIndexSet *sections = [NSIndexSet as_sectionsFromIndexPaths:indexPaths];
|
||||
_supplementaryKindsForPendingOperation = [self supplementaryKindsInSections:sections];
|
||||
for (NSString *kind in _supplementaryKindsForPendingOperation) {
|
||||
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
|
||||
[self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts];
|
||||
_pendingNodeContexts[kind] = contexts;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)willDeleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
|
||||
{
|
||||
for (NSString *kind in _supplementaryKindsForPendingOperation) {
|
||||
NSArray<NSIndexPath *> *deletedIndexPaths = ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths([self editingNodesOfKind:kind], indexPaths);
|
||||
|
||||
[self deleteNodesOfKind:kind atIndexPaths:deletedIndexPaths completion:nil];
|
||||
|
||||
// If any of the contexts remain after the deletion, re-insert them, e.g.
|
||||
// UICollectionElementKindSectionHeader remains even if item 0 is deleted.
|
||||
NSMutableArray<ASIndexedNodeContext *> *reinsertedContexts = [NSMutableArray array];
|
||||
for (ASIndexedNodeContext *context in _pendingNodeContexts[kind]) {
|
||||
if ([deletedIndexPaths containsObject:context.indexPath]) {
|
||||
[reinsertedContexts addObject:context];
|
||||
}
|
||||
}
|
||||
|
||||
[self batchLayoutNodesFromContexts:reinsertedContexts batchCompletion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
|
||||
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
|
||||
}];
|
||||
}
|
||||
[_pendingNodeContexts removeAllObjects];
|
||||
_supplementaryKindsForPendingOperation = nil;
|
||||
}
|
||||
|
||||
- (void)_populatePendingSectionsFromDataSource:(NSIndexSet *)sectionIndexes
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
NSMutableArray<ASSection *> *sections = [NSMutableArray arrayWithCapacity:sectionIndexes.count];
|
||||
[sectionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
id<ASSectionContext> context = [self.collectionDataSource dataController:self contextForSection:idx];
|
||||
[sections addObject:[[ASSection alloc] initWithSectionID:_nextSectionID context:context]];
|
||||
_nextSectionID++;
|
||||
}];
|
||||
_pendingSections = sections;
|
||||
}
|
||||
|
||||
- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withSections:(NSIndexSet *)sections mutableContexts:(NSMutableArray<ASIndexedNodeContext *> *)contexts
|
||||
{
|
||||
__weak id<ASTraitEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
|
||||
|
||||
[sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) {
|
||||
for (NSUInteger sec = range.location; sec < NSMaxRange(range); sec++) {
|
||||
NSUInteger itemCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:sec];
|
||||
for (NSUInteger i = 0; i < itemCount; i++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sec];
|
||||
[self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environment:environment];
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_populateSupplementaryNodesOfKind:(NSString *)kind atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths mutableContexts:(NSMutableArray<ASIndexedNodeContext *> *)contexts
|
||||
{
|
||||
__weak id<ASTraitEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
|
||||
|
||||
NSMutableIndexSet *sections = [NSMutableIndexSet indexSet];
|
||||
for (NSIndexPath *indexPath in indexPaths) {
|
||||
[sections addIndex:indexPath.section];
|
||||
}
|
||||
|
||||
[sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) {
|
||||
for (NSUInteger sec = range.location; sec < NSMaxRange(range); sec++) {
|
||||
NSUInteger itemCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:sec];
|
||||
for (NSUInteger i = 0; i < itemCount; i++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sec];
|
||||
[self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environment:environment];
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_populateSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath mutableContexts:(NSMutableArray<ASIndexedNodeContext *> *)contexts environment:(id<ASTraitEnvironment>)environment
|
||||
{
|
||||
ASCellNodeBlock supplementaryCellBlock;
|
||||
if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) {
|
||||
supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath];
|
||||
} else {
|
||||
ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath];
|
||||
supplementaryCellBlock = ^{ return supplementaryNode; };
|
||||
}
|
||||
|
||||
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
|
||||
ASIndexedNodeContext *context = [[ASIndexedNodeContext alloc] initWithNodeBlock:supplementaryCellBlock
|
||||
indexPath:indexPath
|
||||
supplementaryElementKind:kind
|
||||
constrainedSize:constrainedSize
|
||||
environment:environment];
|
||||
[contexts addObject:context];
|
||||
}
|
||||
|
||||
#pragma mark - Sizing query
|
||||
|
||||
- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if ([kind isEqualToString:ASDataControllerRowNodeKind]) {
|
||||
return [super constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
|
||||
} else {
|
||||
ASDisplayNodeAssertMainThread();
|
||||
return [self.collectionDataSource dataController:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - External supplementary store and section context querying
|
||||
|
||||
- (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
NSArray *nodesOfKind = [self completedNodesOfKind:kind];
|
||||
NSInteger section = indexPath.section;
|
||||
if (section < nodesOfKind.count) {
|
||||
NSArray *nodesOfKindInSection = nodesOfKind[section];
|
||||
NSInteger itemIndex = indexPath.item;
|
||||
if (itemIndex < nodesOfKindInSection.count) {
|
||||
return nodesOfKindInSection[itemIndex];
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id<ASSectionContext>)contextForSection:(NSInteger)section
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssertTrue(section >= 0 && section < _sections.count);
|
||||
return _sections[section].context;
|
||||
}
|
||||
|
||||
#pragma mark - Private Helpers
|
||||
|
||||
- (NSArray *)supplementaryKindsInSections:(NSIndexSet *)sections
|
||||
{
|
||||
return [self.collectionDataSource supplementaryNodeKindsInDataController:self sections:sections];
|
||||
}
|
||||
|
||||
- (id<ASCollectionDataControllerSource>)collectionDataSource
|
||||
{
|
||||
return (id<ASCollectionDataControllerSource>)self.dataSource;
|
||||
}
|
||||
|
||||
- (void)applyPendingSections:(NSIndexSet *)sectionIndexes
|
||||
{
|
||||
[_sections insertObjects:_pendingSections atIndexes:sectionIndexes];
|
||||
_pendingSections = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
@ -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 <Foundation/Foundation.h>
|
||||
#import <AsyncDisplayKit/ASAbstractLayoutController.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ASCellNode;
|
||||
|
||||
typedef NS_ENUM(NSUInteger, ASFlowLayoutDirection) {
|
||||
ASFlowLayoutDirectionVertical,
|
||||
ASFlowLayoutDirectionHorizontal,
|
||||
};
|
||||
|
||||
@protocol ASFlowLayoutControllerDataSource
|
||||
|
||||
- (NSArray<NSArray <ASCellNode *> *> *)completedNodes; // This provides access to ASDataController's _completedNodes multidimensional array.
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* An optimized flow layout controller that supports only vertical or horizontal scrolling, not simultaneously two-dimensional scrolling.
|
||||
* It is used for all ASTableViews, and may be used with ASCollectionView.
|
||||
*/
|
||||
@interface ASFlowLayoutController : ASAbstractLayoutController
|
||||
|
||||
@property (nonatomic, readonly, assign) ASFlowLayoutDirection layoutDirection;
|
||||
@property (nonatomic, readwrite, weak) id <ASFlowLayoutControllerDataSource> dataSource;
|
||||
|
||||
- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
@ -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 <AsyncDisplayKit/ASFlowLayoutController.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASIndexPath.h>
|
||||
#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
@interface ASFlowLayoutController()
|
||||
{
|
||||
ASIndexPathRange _visibleRange;
|
||||
std::vector<ASIndexPathRange> _rangesByType; // All ASLayoutRangeTypes besides visible.
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASFlowLayoutController
|
||||
|
||||
- (instancetype)initWithScrollOption:(ASFlowLayoutDirection)layoutDirection
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
_layoutDirection = layoutDirection;
|
||||
_rangesByType = std::vector<ASIndexPathRange>(ASLayoutRangeTypeCount);
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Visible Indices
|
||||
|
||||
- (void)setVisibleNodeIndexPaths:(NSArray *)indexPaths
|
||||
{
|
||||
_visibleRange = [self indexPathRangeForIndexPaths:indexPaths];
|
||||
}
|
||||
|
||||
/**
|
||||
* IndexPath array for the element in the working range.
|
||||
*/
|
||||
|
||||
- (NSSet *)indexPathsForScrolling:(ASScrollDirection)scrollDirection rangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType
|
||||
{
|
||||
CGSize viewportSize = [self viewportSize];
|
||||
|
||||
CGFloat viewportDirectionalSize = 0.0;
|
||||
ASDirectionalScreenfulBuffer directionalBuffer = { 0, 0 };
|
||||
ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType];
|
||||
|
||||
if (_layoutDirection == ASFlowLayoutDirectionHorizontal) {
|
||||
viewportDirectionalSize = viewportSize.width;
|
||||
directionalBuffer = ASDirectionalScreenfulBufferHorizontal(scrollDirection, tuningParameters);
|
||||
} else {
|
||||
viewportDirectionalSize = viewportSize.height;
|
||||
directionalBuffer = ASDirectionalScreenfulBufferVertical(scrollDirection, tuningParameters);
|
||||
}
|
||||
|
||||
ASIndexPath startPath = [self findIndexPathAtDistance:(-directionalBuffer.negativeDirection * viewportDirectionalSize)
|
||||
fromIndexPath:_visibleRange.start];
|
||||
|
||||
ASIndexPath endPath = [self findIndexPathAtDistance:(directionalBuffer.positiveDirection * viewportDirectionalSize)
|
||||
fromIndexPath:_visibleRange.end];
|
||||
|
||||
ASDisplayNodeAssert(startPath.section <= endPath.section, @"startPath should never begin at a further position than endPath");
|
||||
|
||||
NSMutableSet *indexPathSet = [[NSMutableSet alloc] init];
|
||||
|
||||
NSArray *completedNodes = [_dataSource completedNodes];
|
||||
|
||||
ASIndexPath currPath = startPath;
|
||||
|
||||
while (!ASIndexPathEqualToIndexPath(currPath, endPath)) {
|
||||
[indexPathSet addObject:[NSIndexPath indexPathWithASIndexPath:currPath]];
|
||||
currPath.row++;
|
||||
|
||||
// Once we reach the end of the section, advance to the next one. Keep advancing if the next section is zero-sized.
|
||||
while (currPath.row >= [(NSArray *)completedNodes[currPath.section] count] && currPath.section < endPath.section) {
|
||||
currPath.row = 0;
|
||||
currPath.section++;
|
||||
}
|
||||
}
|
||||
ASDisplayNodeAssert(currPath.section <= endPath.section, @"currPath should never reach a further section than endPath");
|
||||
|
||||
[indexPathSet addObject:[NSIndexPath indexPathWithASIndexPath:endPath]];
|
||||
|
||||
return indexPathSet;
|
||||
}
|
||||
|
||||
#pragma mark - Utility
|
||||
|
||||
- (ASIndexPathRange)indexPathRangeForIndexPaths:(NSArray *)indexPaths
|
||||
{
|
||||
// Set up an initial value so the MIN and MAX can work in the enumeration.
|
||||
__block ASIndexPath currentIndexPath = [[indexPaths firstObject] ASIndexPathValue];
|
||||
__block ASIndexPathRange range;
|
||||
range.start = currentIndexPath;
|
||||
range.end = currentIndexPath;
|
||||
|
||||
for (NSIndexPath *indexPath in indexPaths) {
|
||||
currentIndexPath = [indexPath ASIndexPathValue];
|
||||
range.start = ASIndexPathMinimum(range.start, currentIndexPath);
|
||||
range.end = ASIndexPathMaximum(range.end, currentIndexPath);
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
- (ASIndexPath)findIndexPathAtDistance:(CGFloat)distance fromIndexPath:(ASIndexPath)start
|
||||
{
|
||||
// "end" is the index path we'll advance until we have gone far enough from "start" to reach "distance"
|
||||
ASIndexPath end = start;
|
||||
// "previous" will store one iteration before "end", in case we go too far and need to reset "end" to be "previous"
|
||||
ASIndexPath previous = start;
|
||||
|
||||
NSArray *completedNodes = [_dataSource completedNodes];
|
||||
NSUInteger numberOfSections = [completedNodes count];
|
||||
NSUInteger numberOfRowsInSection = [(NSArray *)completedNodes[end.section] count];
|
||||
|
||||
// If "distance" is negative, advance "end" backwards across rows and sections.
|
||||
// Otherwise, advance forward. In either case, bring "distance" closer to zero by the dimension of each row passed.
|
||||
if (distance < 0.0 && end.section >= 0 && end.section < numberOfSections && end.row >= 0 && end.row < numberOfRowsInSection) {
|
||||
while (distance < 0.0 && end.section >= 0 && end.row >= 0) {
|
||||
previous = end;
|
||||
ASDisplayNode *node = completedNodes[end.section][end.row];
|
||||
CGSize size = node.calculatedSize;
|
||||
distance += (_layoutDirection == ASFlowLayoutDirectionHorizontal ? size.width : size.height);
|
||||
end.row--;
|
||||
// If we've gone to a negative row, set to the last row of the previous section. While loop is required to handle empty sections.
|
||||
while (end.row < 0 && end.section > 0) {
|
||||
end.section--;
|
||||
numberOfRowsInSection = [(NSArray *)completedNodes[end.section] count];
|
||||
end.row = numberOfRowsInSection - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (end.row < 0) {
|
||||
end = previous;
|
||||
}
|
||||
} else {
|
||||
while (distance > 0.0 && end.section >= 0 && end.section < numberOfSections && end.row >= 0 && end.row < numberOfRowsInSection) {
|
||||
previous = end;
|
||||
ASDisplayNode *node = completedNodes[end.section][end.row];
|
||||
CGSize size = node.calculatedSize;
|
||||
distance -= _layoutDirection == ASFlowLayoutDirectionHorizontal ? size.width : size.height;
|
||||
|
||||
end.row++;
|
||||
// If we've gone beyond the section, reset to the beginning of the next section. While loop is required to handle empty sections.
|
||||
while (end.row >= numberOfRowsInSection && end.section < numberOfSections - 1) {
|
||||
end.row = 0;
|
||||
end.section++;
|
||||
numberOfRowsInSection = [(NSArray *)completedNodes[end.section] count];
|
||||
}
|
||||
}
|
||||
|
||||
if (end.row >= numberOfRowsInSection) {
|
||||
end = previous;
|
||||
}
|
||||
}
|
||||
|
||||
return end;
|
||||
}
|
||||
|
||||
- (NSInteger)flowLayoutDistanceForRange:(ASIndexPathRange)range
|
||||
{
|
||||
// This method should only be called with the range in proper order (start comes before end).
|
||||
ASDisplayNodeAssert(ASIndexPathEqualToIndexPath(ASIndexPathMinimum(range.start, range.end), range.start), @"flowLayoutDistanceForRange: called with invalid range");
|
||||
|
||||
if (ASIndexPathEqualToIndexPath(range.start, range.end)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
NSInteger totalRowCount = 0;
|
||||
NSUInteger numberOfRowsInSection = 0;
|
||||
NSArray *completedNodes = [_dataSource completedNodes];
|
||||
|
||||
for (NSInteger section = range.start.section; section <= range.end.section; section++) {
|
||||
numberOfRowsInSection = [(NSArray *)completedNodes[section] count];
|
||||
totalRowCount += numberOfRowsInSection;
|
||||
|
||||
if (section == range.start.section) {
|
||||
// For the start section, make sure we don't count the rows before the start row.
|
||||
totalRowCount -= range.start.row;
|
||||
} else if (section == range.end.section) {
|
||||
// For the start section, make sure we don't count the rows after the end row.
|
||||
totalRowCount -= (numberOfRowsInSection - (range.end.row + 1));
|
||||
}
|
||||
}
|
||||
|
||||
ASDisplayNodeAssert(totalRowCount >= 0, @"totalRowCount in flowLayoutDistanceForRange: should not be negative");
|
||||
return totalRowCount;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
@ -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 <vector>
|
||||
|
||||
@class ASIndexedNodeContext;
|
||||
|
||||
typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths);
|
||||
|
||||
@interface ASDataController (Subclasses)
|
||||
|
||||
#pragma mark - Internal editing & completed store querying
|
||||
|
||||
/**
|
||||
* Read-only access to the underlying editing nodes of the given kind
|
||||
*/
|
||||
- (NSMutableArray *)editingNodesOfKind:(NSString *)kind;
|
||||
|
||||
/**
|
||||
* Read only access to the underlying completed nodes of the given kind
|
||||
*/
|
||||
- (NSMutableArray *)completedNodesOfKind:(NSString *)kind;
|
||||
|
||||
#pragma mark - Node sizing
|
||||
|
||||
/**
|
||||
* Measure and layout the given nodes in optimized batches, constraining each to a given size in `constrainedSizeForNodeOfKind:atIndexPath:`.
|
||||
*
|
||||
* This method runs synchronously.
|
||||
* @param batchCompletion A handler to be run after each batch is completed. It is executed synchronously on the calling thread.
|
||||
*/
|
||||
- (void)batchLayoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts batchCompletion:(ASDataControllerCompletionBlock)batchCompletionHandler;
|
||||
|
||||
/**
|
||||
* Provides the size range for a specific node during the layout process.
|
||||
*/
|
||||
- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
#pragma mark - Node & Section Insertion/Deletion API
|
||||
|
||||
/**
|
||||
* Inserts the given nodes of the specified kind into the backing store, calling completion on the main thread when the write finishes.
|
||||
*/
|
||||
- (void)insertNodes:(NSArray *)nodes ofKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock;
|
||||
|
||||
/**
|
||||
* Deletes the given nodes of the specified kind in the backing store, calling completion on the main thread when the deletion finishes.
|
||||
*/
|
||||
- (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(ASDataControllerCompletionBlock)completionBlock;
|
||||
|
||||
/**
|
||||
* Inserts the given sections of the specified kind in the backing store, calling completion on the main thread when finished.
|
||||
*/
|
||||
- (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock;
|
||||
|
||||
/**
|
||||
* Deletes the given sections in the backing store, calling completion on the main thread when finished.
|
||||
*/
|
||||
- (void)deleteSections:(NSIndexSet *)indexSet completion:(void (^)())completionBlock;
|
||||
|
||||
#pragma mark - Data Manipulation Hooks
|
||||
|
||||
/**
|
||||
* Notifies the subclass to perform any work needed before the data controller is reloaded entirely
|
||||
*
|
||||
* @discussion This method will be performed before the data controller enters its editing queue.
|
||||
* The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
|
||||
* data stores before entering into editing the backing store on a background thread.
|
||||
*/
|
||||
- (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount;
|
||||
|
||||
/**
|
||||
* Notifies the subclass that the data controller is about to reload its data entirely
|
||||
*
|
||||
* @discussion This method will be performed on the data controller's editing background queue before the parent's
|
||||
* concrete implementation. This is a great place to perform new node creation like supplementary views
|
||||
* or header/footer nodes.
|
||||
*/
|
||||
- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount;
|
||||
|
||||
/**
|
||||
* Notifies the subclass to perform setup before sections are inserted in the data controller
|
||||
*
|
||||
* @discussion This method will be performed before the data controller enters its editing queue.
|
||||
* The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
|
||||
* data stores before entering into editing the backing store on a background thread.
|
||||
*
|
||||
* @param sections Indices of sections to be inserted
|
||||
*/
|
||||
- (void)prepareForInsertSections:(NSIndexSet *)sections;
|
||||
|
||||
/**
|
||||
* Notifies the subclass that the data controller will insert new sections at the given position
|
||||
*
|
||||
* @discussion This method will be performed on the data controller's editing background queue before the parent's
|
||||
* concrete implementation. This is a great place to perform any additional transformations like supplementary views
|
||||
* or header/footer nodes.
|
||||
*
|
||||
* @param sections Indices of sections to be inserted
|
||||
*/
|
||||
- (void)willInsertSections:(NSIndexSet *)sections;
|
||||
|
||||
/**
|
||||
* Notifies the subclass to perform setup before sections are deleted in the data controller
|
||||
*
|
||||
* @discussion This method will be performed before the data controller enters its editing queue.
|
||||
* The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
|
||||
* data stores before entering into editing the backing store on a background thread.
|
||||
*
|
||||
* @param sections Indices of sections to be inserted
|
||||
*/
|
||||
- (void)prepareForDeleteSections:(NSIndexSet *)sections;
|
||||
|
||||
/**
|
||||
* Notifies the subclass that the data controller will delete sections at the given positions
|
||||
*
|
||||
* @discussion This method will be performed on the data controller's editing background queue before the parent's
|
||||
* concrete implementation. This is a great place to perform any additional transformations like supplementary views
|
||||
* or header/footer nodes.
|
||||
*
|
||||
* @param sections Indices of sections to be deleted
|
||||
*/
|
||||
- (void)willDeleteSections:(NSIndexSet *)sections;
|
||||
|
||||
/**
|
||||
* Notifies the subclass to perform setup before rows are inserted in the data controller.
|
||||
*
|
||||
* @discussion This method will be performed before the data controller enters its editing queue.
|
||||
* The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
|
||||
* data stores before entering into editing the backing store on a background thread.
|
||||
*
|
||||
* @param indexPaths Index paths for the rows to be inserted.
|
||||
*/
|
||||
- (void)prepareForInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
|
||||
|
||||
/**
|
||||
* Notifies the subclass that the data controller will insert new rows at the given index paths.
|
||||
*
|
||||
* @discussion This method will be performed on the data controller's editing background queue before the parent's
|
||||
* concrete implementation. This is a great place to perform any additional transformations like supplementary views
|
||||
* or header/footer nodes.
|
||||
*
|
||||
* @param indexPaths Index paths for the rows to be inserted.
|
||||
*/
|
||||
- (void)willInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
|
||||
|
||||
/**
|
||||
* Notifies the subclass to perform setup before rows are deleted in the data controller.
|
||||
*
|
||||
* @discussion This method will be performed before the data controller enters its editing queue.
|
||||
* The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
|
||||
* data stores before entering into editing the backing store on a background thread.
|
||||
*
|
||||
* @param indexPaths Index paths for the rows to be deleted.
|
||||
*/
|
||||
- (void)prepareForDeleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
|
||||
|
||||
/**
|
||||
* Notifies the subclass that the data controller will delete rows at the given index paths.
|
||||
*
|
||||
* @discussion This method will be performed before the data controller enters its editing queue.
|
||||
* The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or
|
||||
* data stores before entering into editing the backing store on a background thread.
|
||||
*
|
||||
* @param indexPaths Index paths for the rows to be deleted.
|
||||
*/
|
||||
- (void)willDeleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
124
submodules/AsyncDisplayKit/BUILD
vendored
124
submodules/AsyncDisplayKit/BUILD
vendored
@ -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",
|
||||
|
@ -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 <AsyncDisplayKit/ASLayoutSpec.h>
|
||||
|
||||
/** 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<id<ASLayoutElement>> *)children NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
@param children Children to be positioned at fixed positions
|
||||
*/
|
||||
+ (instancetype)absoluteLayoutSpecWithChildren:(NSArray<id<ASLayoutElement>> *)children NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -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 <AsyncDisplayKit/ASCollections.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASLayoutSpec+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASLayoutSpecUtilities.h>
|
||||
#import <AsyncDisplayKit/ASLayoutElementStylePrivate.h>
|
||||
|
||||
#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<id<ASLayoutElement>> *)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<id<ASLayoutElement>> *)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<ASLayoutElement> 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<ASLayout *> 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
|
||||
|
@ -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 <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol ASLayoutElementAsciiArtProtocol <NSObject>
|
||||
/**
|
||||
* 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<NSString *> *)children parent:(NSString *)parent;
|
||||
|
||||
/**
|
||||
* Renders an ascii art box with the children aligned vertically.
|
||||
* Example:
|
||||
* --ASStackLayoutSpec--
|
||||
* | ASTextNode |
|
||||
* | ASTextNode |
|
||||
* | ASTextNode |
|
||||
* ---------------------
|
||||
*/
|
||||
+ (NSString *)verticalBoxStringForChildren:(NSArray<NSString *> *)children parent:(NSString *)parent;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -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 <AsyncDisplayKit/ASAsciiArtBoxCreator.h>
|
||||
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
#import <cmath>
|
||||
|
||||
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
|
@ -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 <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
|
||||
<<<<<<< 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 <dispatch/once.h>
|
||||
|
||||
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
|
@ -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 <AsyncDisplayKit/ASLayoutSpec.h>
|
||||
|
||||
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<ASLayoutElement> 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<ASLayoutElement>)child background:(id<ASLayoutElement>)background NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -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 <AsyncDisplayKit/ASBackgroundLayoutSpec.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASLayoutSpec+Subclasses.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASCollections.h>
|
||||
|
||||
static NSUInteger const kForegroundChildIndex = 0;
|
||||
static NSUInteger const kBackgroundChildIndex = 1;
|
||||
|
||||
@implementation ASBackgroundLayoutSpec
|
||||
|
||||
#pragma mark - Class
|
||||
|
||||
+ (instancetype)backgroundLayoutSpecWithChild:(id<ASLayoutElement>)child background:(id<ASLayoutElement>)background NS_RETURNS_RETAINED
|
||||
{
|
||||
return [[self alloc] initWithChild:child background:background];
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithChild:(id<ASLayoutElement>)child background:(id<ASLayoutElement>)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<ASLayout *> arrayByTransferring:rawSublayouts count:i];
|
||||
return [ASLayout layoutWithLayoutElement:self size:contentsLayout.size sublayouts:sublayouts];
|
||||
}
|
||||
|
||||
#pragma mark - Background
|
||||
|
||||
- (void)setChild:(id<ASLayoutElement>)child
|
||||
{
|
||||
ASDisplayNodeAssertNotNil(child, @"Child cannot be nil");
|
||||
[super setChild:child atIndex:kForegroundChildIndex];
|
||||
}
|
||||
|
||||
- (id<ASLayoutElement>)child
|
||||
{
|
||||
return [super childAtIndex:kForegroundChildIndex];
|
||||
}
|
||||
|
||||
- (void)setBackground:(id<ASLayoutElement>)background
|
||||
{
|
||||
ASDisplayNodeAssertNotNil(background, @"Background cannot be nil");
|
||||
[super setChild:background atIndex:kBackgroundChildIndex];
|
||||
}
|
||||
|
||||
- (id<ASLayoutElement>)background
|
||||
{
|
||||
return [super childAtIndex:kBackgroundChildIndex];
|
||||
}
|
||||
|
||||
@end
|
@ -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 <AsyncDisplayKit/ASImageProtocols.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* @abstract Simple NSURLSession-based image downloader.
|
||||
*/
|
||||
@interface ASBasicImageDownloader : NSObject <ASImageDownloaderProtocol>
|
||||
|
||||
/**
|
||||
* 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
|
@ -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 <AsyncDisplayKit/ASBasicImageDownloader.h>
|
||||
|
||||
#import <Foundation/NSURLSession.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASBasicImageDownloaderInternal.h>
|
||||
#import <AsyncDisplayKit/ASImageContainerProtocolCategories.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
|
||||
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 () <NSURLSessionDownloadDelegate>
|
||||
{
|
||||
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
|
@ -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
|
@ -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 <Foundation/Foundation.h>
|
||||
|
||||
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
|
@ -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 <AsyncDisplayKit/ASBatchContext.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
#import <stdatomic.h>
|
||||
|
||||
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
|
@ -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 <UIKit/UIKit.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASScrollDirection.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ASBatchContext;
|
||||
@protocol ASBatchFetchingDelegate;
|
||||
|
||||
@protocol ASBatchFetchingScrollView <NSObject>
|
||||
|
||||
- (BOOL)canBatchFetch;
|
||||
- (ASBatchContext *)batchContext;
|
||||
- (CGFloat)leadingScreensForBatching;
|
||||
- (nullable id<ASBatchFetchingDelegate>)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<ASBatchFetchingScrollView> *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<ASBatchFetchingDelegate> delegate);
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
@ -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 <AsyncDisplayKit/ASBatchFetching.h>
|
||||
#import <AsyncDisplayKit/ASBatchContext.h>
|
||||
#import <AsyncDisplayKit/ASBatchFetchingDelegate.h>
|
||||
|
||||
BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView<ASBatchFetchingScrollView> *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<ASBatchFetchingDelegate> 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<ASBatchFetchingDelegate> 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
|
@ -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 <UIKit/UIKit.h>
|
||||
|
||||
@protocol ASBatchFetchingDelegate <NSObject>
|
||||
|
||||
/**
|
||||
* @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
|
@ -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 <AsyncDisplayKit/ASButtonNode.h>
|
||||
#import <AsyncDisplayKit/ASTextNode.h>
|
||||
#import <AsyncDisplayKit/ASImageNode.h>
|
||||
#import <AsyncDisplayKit/ASStackLayoutDefines.h>
|
||||
|
||||
@interface ASButtonNode() {
|
||||
NSAttributedString *_normalAttributedTitle;
|
||||
NSAttributedString *_highlightedAttributedTitle;
|
||||
NSAttributedString *_selectedAttributedTitle;
|
||||
NSAttributedString *_selectedHighlightedAttributedTitle;
|
||||
NSAttributedString *_disabledAttributedTitle;
|
||||
|
||||
UIImage *_normalImage;
|
||||
UIImage *_highlightedImage;
|
||||
UIImage *_selectedImage;
|
||||
UIImage *_selectedHighlightedImage;
|
||||
UIImage *_disabledImage;
|
||||
|
||||
UIImage *_normalBackgroundImage;
|
||||
UIImage *_highlightedBackgroundImage;
|
||||
UIImage *_selectedBackgroundImage;
|
||||
UIImage *_selectedHighlightedBackgroundImage;
|
||||
UIImage *_disabledBackgroundImage;
|
||||
|
||||
CGFloat _contentSpacing;
|
||||
BOOL _laysOutHorizontally;
|
||||
ASVerticalAlignment _contentVerticalAlignment;
|
||||
ASHorizontalAlignment _contentHorizontalAlignment;
|
||||
UIEdgeInsets _contentEdgeInsets;
|
||||
ASButtonNodeImageAlignment _imageAlignment;
|
||||
ASTextNode *_titleNode;
|
||||
ASImageNode *_imageNode;
|
||||
ASImageNode *_backgroundImageNode;
|
||||
}
|
||||
|
||||
@end
|
@ -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 <Foundation/Foundation.h>
|
||||
#import <AsyncDisplayKit/ASButtonNode.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ASButtonNode (Yoga)
|
||||
|
||||
- (void)updateYogaLayoutIfNeeded;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -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 <AsyncDisplayKit/ASButtonNode+Private.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||
#import <AsyncDisplayKit/ASStackLayoutSpecUtilities.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
|
||||
static void ASButtonNodeResolveHorizontalAlignmentForStyle(ASLayoutElementStyle *style, ASStackLayoutDirection _direction, ASHorizontalAlignment _horizontalAlignment, ASStackLayoutJustifyContent _justifyContent, ASStackLayoutAlignItems _alignItems) {
|
||||
if (_direction == ASStackLayoutDirectionHorizontal) {
|
||||
style.justifyContent = justifyContent(_horizontalAlignment, _justifyContent);
|
||||
} else {
|
||||
style.alignItems = alignment(_horizontalAlignment, _alignItems);
|
||||
}
|
||||
}
|
||||
|
||||
static void ASButtonNodeResolveVerticalAlignmentForStyle(ASLayoutElementStyle *style, ASStackLayoutDirection _direction, ASVerticalAlignment _verticalAlignment, ASStackLayoutJustifyContent _justifyContent, ASStackLayoutAlignItems _alignItems) {
|
||||
if (_direction == ASStackLayoutDirectionHorizontal) {
|
||||
style.alignItems = alignment(_verticalAlignment, _alignItems);
|
||||
} else {
|
||||
style.justifyContent = justifyContent(_verticalAlignment, _justifyContent);
|
||||
}
|
||||
}
|
||||
|
||||
@implementation ASButtonNode (Yoga)
|
||||
|
||||
- (void)updateYogaLayoutIfNeeded
|
||||
{
|
||||
NSMutableArray<ASDisplayNode *> *children = [[NSMutableArray alloc] initWithCapacity:2];
|
||||
{
|
||||
ASLockScopeSelf();
|
||||
|
||||
// Build up yoga children for button node again
|
||||
unowned ASLayoutElementStyle *style = [self _locked_style];
|
||||
[style yogaNodeCreateIfNeeded];
|
||||
|
||||
// Setup stack layout values
|
||||
style.flexDirection = _laysOutHorizontally ? ASStackLayoutDirectionHorizontal : ASStackLayoutDirectionVertical;
|
||||
|
||||
// Resolve horizontal and vertical alignment
|
||||
ASButtonNodeResolveHorizontalAlignmentForStyle(style, style.flexDirection, _contentHorizontalAlignment, style.justifyContent, style.alignItems);
|
||||
ASButtonNodeResolveVerticalAlignmentForStyle(style, style.flexDirection, _contentVerticalAlignment, style.justifyContent, style.alignItems);
|
||||
|
||||
// Setup new yoga children
|
||||
if (_imageNode.image != nil) {
|
||||
[_imageNode.style yogaNodeCreateIfNeeded];
|
||||
[children addObject:_imageNode];
|
||||
}
|
||||
|
||||
if (_titleNode.attributedText.length > 0) {
|
||||
[_titleNode.style yogaNodeCreateIfNeeded];
|
||||
if (_imageAlignment == ASButtonNodeImageAlignmentBeginning) {
|
||||
[children addObject:_titleNode];
|
||||
} else {
|
||||
[children insertObject:_titleNode atIndex:0];
|
||||
}
|
||||
}
|
||||
|
||||
// Add spacing between title and button
|
||||
if (children.count == 2) {
|
||||
unowned ASLayoutElementStyle *firstChildStyle = children.firstObject.style;
|
||||
if (_laysOutHorizontally) {
|
||||
firstChildStyle.margin = ASEdgeInsetsMake(UIEdgeInsetsMake(0, 0, 0, _contentSpacing));
|
||||
} else {
|
||||
firstChildStyle.margin = ASEdgeInsetsMake(UIEdgeInsetsMake(0, 0, _contentSpacing, 0));
|
||||
}
|
||||
}
|
||||
|
||||
// Add padding to button
|
||||
if (UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, _contentEdgeInsets) == NO) {
|
||||
style.padding = ASEdgeInsetsMake(_contentEdgeInsets);
|
||||
}
|
||||
|
||||
// Add background node
|
||||
if (_backgroundImageNode.image) {
|
||||
[_backgroundImageNode.style yogaNodeCreateIfNeeded];
|
||||
[children insertObject:_backgroundImageNode atIndex:0];
|
||||
|
||||
_backgroundImageNode.style.positionType = YGPositionTypeAbsolute;
|
||||
_backgroundImageNode.style.position = ASEdgeInsetsMake(UIEdgeInsetsZero);
|
||||
}
|
||||
}
|
||||
|
||||
// Update new children
|
||||
[self setYogaChildren:children];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#else
|
||||
|
||||
#import "ASButtonNode+Private.h"
|
||||
|
||||
@implementation ASButtonNode (Yoga)
|
||||
|
||||
- (void)updateYogaLayoutIfNeeded {}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
@ -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 <AsyncDisplayKit/ASControlNode.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ASImageNode, ASTextNode;
|
||||
|
||||
/**
|
||||
Image alignment defines where the image will be placed relative to the text.
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, ASButtonNodeImageAlignment) {
|
||||
/** Places the image before the text. */
|
||||
ASButtonNodeImageAlignmentBeginning,
|
||||
/** Places the image after the text. */
|
||||
ASButtonNodeImageAlignmentEnd
|
||||
};
|
||||
|
||||
@interface ASButtonNode : ASControlNode
|
||||
|
||||
@property (readonly) ASTextNode * titleNode;
|
||||
@property (readonly) ASImageNode * imageNode;
|
||||
@property (readonly) ASImageNode * backgroundImageNode;
|
||||
|
||||
/**
|
||||
Spacing between image and title. Defaults to 8.0.
|
||||
*/
|
||||
@property CGFloat contentSpacing;
|
||||
|
||||
/**
|
||||
Whether button should be laid out vertically (image on top of text) or horizontally (image to the left of text).
|
||||
ASButton node does not yet support RTL but it should be fairly easy to implement.
|
||||
Defaults to YES.
|
||||
*/
|
||||
@property BOOL laysOutHorizontally;
|
||||
|
||||
/** Horizontally align content (text or image).
|
||||
Defaults to ASHorizontalAlignmentMiddle.
|
||||
*/
|
||||
@property ASHorizontalAlignment contentHorizontalAlignment;
|
||||
|
||||
/** Vertically align content (text or image).
|
||||
Defaults to ASVerticalAlignmentCenter.
|
||||
*/
|
||||
@property ASVerticalAlignment contentVerticalAlignment;
|
||||
|
||||
/**
|
||||
* @discussion The insets used around the title and image node
|
||||
*/
|
||||
@property UIEdgeInsets contentEdgeInsets;
|
||||
|
||||
/**
|
||||
* @discusstion Whether the image should be aligned at the beginning or at the end of node. Default is `ASButtonNodeImageAlignmentBeginning`.
|
||||
*/
|
||||
@property ASButtonNodeImageAlignment imageAlignment;
|
||||
|
||||
/**
|
||||
* Returns the styled title associated with the specified state.
|
||||
*
|
||||
* @param state The control state that uses the styled title.
|
||||
*
|
||||
* @return The title for the specified state.
|
||||
*/
|
||||
- (nullable NSAttributedString *)attributedTitleForState:(UIControlState)state AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Sets the styled title to use for the specified state. This will reset styled title previously set with -setTitle:withFont:withColor:forState.
|
||||
*
|
||||
* @param title The styled text string to use for the title.
|
||||
* @param state The control state that uses the specified title.
|
||||
*/
|
||||
- (void)setAttributedTitle:(nullable NSAttributedString *)title forState:(UIControlState)state;
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
/**
|
||||
* Sets the title to use for the specified state. This will reset styled title previously set with -setAttributedTitle:forState.
|
||||
*
|
||||
* @param title The styled text string to use for the title.
|
||||
* @param font The font to use for the title.
|
||||
* @param color The color to use for the title.
|
||||
* @param state The control state that uses the specified title.
|
||||
*/
|
||||
- (void)setTitle:(NSString *)title withFont:(nullable UIFont *)font withColor:(nullable UIColor *)color forState:(UIControlState)state;
|
||||
#endif
|
||||
/**
|
||||
* Returns the image used for a button state.
|
||||
*
|
||||
* @param state The control state that uses the image.
|
||||
*
|
||||
* @return The image used for the specified state.
|
||||
*/
|
||||
- (nullable UIImage *)imageForState:(UIControlState)state AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Sets the image to use for the specified state.
|
||||
*
|
||||
* @param image The image to use for the specified state.
|
||||
* @param state The control state that uses the specified title.
|
||||
*/
|
||||
- (void)setImage:(nullable UIImage *)image forState:(UIControlState)state;
|
||||
|
||||
/**
|
||||
* Sets the background image to use for the specified state.
|
||||
*
|
||||
* @param image The image to use for the specified state.
|
||||
* @param state The control state that uses the specified title.
|
||||
*/
|
||||
- (void)setBackgroundImage:(nullable UIImage *)image forState:(UIControlState)state;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the background image used for a button state.
|
||||
*
|
||||
* @param state The control state that uses the image.
|
||||
*
|
||||
* @return The background image used for the specified state.
|
||||
*/
|
||||
- (nullable UIImage *)backgroundImageForState:(UIControlState)state AS_WARN_UNUSED_RESULT;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -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 <AsyncDisplayKit/ASButtonNode+Private.h>
|
||||
#import <AsyncDisplayKit/ASButtonNode+Yoga.h>
|
||||
#import <AsyncDisplayKit/ASStackLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASBackgroundLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASAbsoluteLayoutSpec.h>
|
||||
#import "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
|
@ -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 <AsyncDisplayKit/ASCellNode.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ASCollectionElement;
|
||||
|
||||
@protocol ASCellNodeInteractionDelegate <NSObject>
|
||||
|
||||
/**
|
||||
* 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 <ASCellNodeInteractionDelegate> 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<ASRangeManagingNode> 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
|
@ -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 <AsyncDisplayKit/ASDisplayNode.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ASCellNode, ASTextNode;
|
||||
@protocol ASRangeManagingNode;
|
||||
|
||||
typedef NSUInteger ASCellNodeAnimation;
|
||||
|
||||
typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) {
|
||||
/**
|
||||
* Indicates a cell has just became visible
|
||||
*/
|
||||
ASCellNodeVisibilityEventVisible,
|
||||
/**
|
||||
* Its position (determined by scrollView.contentOffset) has changed while at least 1px remains visible.
|
||||
* It is possible that 100% of the cell is visible both before and after and only its position has changed,
|
||||
* or that the position change has resulted in more or less of the cell being visible.
|
||||
* Use CGRectIntersect between cellFrame and scrollView.bounds to get this rectangle
|
||||
*/
|
||||
ASCellNodeVisibilityEventVisibleRectChanged,
|
||||
/**
|
||||
* Indicates a cell is no longer visible
|
||||
*/
|
||||
ASCellNodeVisibilityEventInvisible,
|
||||
/**
|
||||
* Indicates user has started dragging the visible cell
|
||||
*/
|
||||
ASCellNodeVisibilityEventWillBeginDragging,
|
||||
/**
|
||||
* Indicates user has ended dragging the visible cell
|
||||
*/
|
||||
ASCellNodeVisibilityEventDidEndDragging,
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic cell node. Subclass this instead of `ASDisplayNode` to use with `ASTableView` and `ASCollectionView`.
|
||||
|
||||
* @note When a cell node is contained inside a collection view (or table view),
|
||||
* calling `-setNeedsLayout` will also notify the collection on the main thread
|
||||
* so that the collection can update its item layout if the cell's size changed.
|
||||
*/
|
||||
@interface ASCellNode : ASDisplayNode
|
||||
|
||||
/**
|
||||
* @abstract When enabled, ensures that the cell is completely displayed before allowed onscreen.
|
||||
*
|
||||
* @default NO
|
||||
* @discussion Normally, ASCellNodes are preloaded and have finished display before they are onscreen.
|
||||
* However, if the Table or Collection's rangeTuningParameters are set to small values (or 0),
|
||||
* or if the user is scrolling rapidly on a slow device, it is possible for a cell's display to
|
||||
* be incomplete when it becomes visible.
|
||||
*
|
||||
* In this case, normally placeholder states are shown and scrolling continues uninterrupted.
|
||||
* The finished, drawn content is then shown as soon as it is ready.
|
||||
*
|
||||
* With this property set to YES, the main thread will be blocked until display is complete for
|
||||
* the cell. This is more similar to UIKit, and in fact makes AsyncDisplayKit scrolling visually
|
||||
* indistinguishable from UIKit's, except being faster.
|
||||
*
|
||||
* Using this option does not eliminate all of the performance advantages of AsyncDisplayKit.
|
||||
* Normally, a cell has been preloading and is almost done when it reaches the screen, so the
|
||||
* blocking time is very short. If the rangeTuningParameters are set to 0, still this option
|
||||
* outperforms UIKit: while the main thread is waiting, subnode display executes concurrently.
|
||||
*/
|
||||
@property BOOL neverShowPlaceholders;
|
||||
|
||||
/*
|
||||
* The kind of supplementary element this node represents, if any.
|
||||
*
|
||||
* @return The supplementary element kind, or @c nil if this node does not represent a supplementary element.
|
||||
*/
|
||||
@property (nullable, copy, readonly) NSString *supplementaryElementKind;
|
||||
|
||||
/*
|
||||
* The layout attributes currently assigned to this node, if any.
|
||||
*
|
||||
* @discussion This property is useful because it is set before @c collectionView:willDisplayNode:forItemAtIndexPath:
|
||||
* is called, when the node is not yet in the hierarchy and its frame cannot be converted to/from other nodes. Instead
|
||||
* you can use the layout attributes object to learn where and how the cell will be displayed.
|
||||
*/
|
||||
@property (nullable, copy, readonly) UICollectionViewLayoutAttributes *layoutAttributes;
|
||||
|
||||
/**
|
||||
* A Boolean value that is synchronized with the underlying collection or tableView cell property.
|
||||
* Setting this value is equivalent to calling selectItem / deselectItem on the collection or table.
|
||||
*/
|
||||
@property (getter=isSelected) BOOL selected;
|
||||
|
||||
/**
|
||||
* A Boolean value that is synchronized with the underlying collection or tableView cell property.
|
||||
* Setting this value is equivalent to calling highlightItem / unHighlightItem on the collection or table.
|
||||
*/
|
||||
@property (getter=isHighlighted) BOOL highlighted;
|
||||
|
||||
/**
|
||||
* The current index path of this cell node, or @c nil if this node is
|
||||
* not a valid item inside a table node or collection node.
|
||||
*/
|
||||
@property (nullable, copy, readonly) NSIndexPath *indexPath;
|
||||
|
||||
/**
|
||||
* BETA: API is under development. We will attempt to provide an easy migration pathway for any changes.
|
||||
*
|
||||
* The view-model currently assigned to this node, if any.
|
||||
*
|
||||
* This property may be set off the main thread, but this method will never be invoked concurrently on the
|
||||
*/
|
||||
@property (nullable) id nodeModel;
|
||||
|
||||
/**
|
||||
* Asks the node whether it can be updated to the given node model.
|
||||
*
|
||||
* The default implementation returns YES if the class matches that of the current view-model.
|
||||
*/
|
||||
- (BOOL)canUpdateToNodeModel:(id)nodeModel;
|
||||
|
||||
/**
|
||||
* The backing view controller, or @c nil if the node wasn't initialized with backing view controller
|
||||
* @note This property must be accessed on the main thread.
|
||||
*/
|
||||
@property (nullable, nonatomic, readonly) UIViewController *viewController;
|
||||
|
||||
|
||||
/**
|
||||
* The table- or collection-node that this cell is a member of, if any.
|
||||
*/
|
||||
@property (nullable, weak, readonly) id<ASRangeManagingNode> owningNode;
|
||||
|
||||
/*
|
||||
* ASCellNode must forward touch events in order for UITableView and UICollectionView tap handling to work. Overriding
|
||||
* these methods (e.g. for highlighting) requires the super method be called.
|
||||
*/
|
||||
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
|
||||
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
|
||||
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
|
||||
- (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
|
||||
|
||||
/**
|
||||
* Called by the system when ASCellNode is used with an ASCollectionNode. It will not be called by ASTableNode.
|
||||
* When the UICollectionViewLayout object returns a new UICollectionViewLayoutAttributes object, the corresponding ASCellNode will be updated.
|
||||
* See UICollectionViewCell's applyLayoutAttributes: for a full description.
|
||||
*/
|
||||
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes;
|
||||
|
||||
/**
|
||||
* @abstract Initializes a cell with a given view controller block.
|
||||
*
|
||||
* @param viewControllerBlock The block that will be used to create the backing view controller.
|
||||
* @param didLoadBlock The block that will be called after the view controller's view is loaded.
|
||||
*
|
||||
* @return An ASCellNode created using the root view of the view controller provided by the viewControllerBlock.
|
||||
* The view controller's root view is resized to match the calculated size produced during layout.
|
||||
*
|
||||
*/
|
||||
- (instancetype)initWithViewControllerBlock:(ASDisplayNodeViewControllerBlock)viewControllerBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock;
|
||||
|
||||
/**
|
||||
* @abstract Notifies the cell node of certain visibility events, such as changing visible rect.
|
||||
*
|
||||
* @warning In cases where an ASCellNode is used as a plain node – i.e. not returned from the
|
||||
* nodeBlockForItemAtIndexPath/nodeForItemAtIndexPath data source methods – this method will
|
||||
* deliver only the `Visible` and `Invisible` events, `scrollView` will be nil, and
|
||||
* `cellFrame` will be the zero rect.
|
||||
*/
|
||||
- (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(nullable UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame;
|
||||
|
||||
#pragma mark - UITableViewCell specific passthrough properties
|
||||
|
||||
/* @abstract The selection style when a tap on a cell occurs
|
||||
* @default UITableViewCellSelectionStyleDefault
|
||||
* ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes.
|
||||
*/
|
||||
@property UITableViewCellSelectionStyle selectionStyle;
|
||||
|
||||
/* @abstract The focus style when a cell is focused
|
||||
* @default UITableViewCellFocusStyleDefault
|
||||
* ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes.
|
||||
*/
|
||||
@property UITableViewCellFocusStyle focusStyle;
|
||||
|
||||
/* @abstract The view used as the background of the cell when it is selected.
|
||||
* ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes.
|
||||
* ASCollectionView uses these properties when configuring UICollectionViewCells that host ASCellNodes.
|
||||
*/
|
||||
@property (nullable) UIView *selectedBackgroundView;
|
||||
|
||||
/* @abstract The view used as the background of the cell.
|
||||
* ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes.
|
||||
* ASCollectionView uses these properties when configuring UICollectionViewCells that host ASCellNodes.
|
||||
*/
|
||||
@property (nullable) UIView *backgroundView;
|
||||
|
||||
/* @abstract The accessory type view on the right side of the cell. Please take care of your ASLayoutSpec so that doesn't overlay the accessoryView
|
||||
* @default UITableViewCellAccessoryNone
|
||||
* ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes.
|
||||
*/
|
||||
@property UITableViewCellAccessoryType accessoryType;
|
||||
|
||||
/* @abstract The inset of the cell separator line
|
||||
* ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes.
|
||||
*/
|
||||
@property UIEdgeInsets separatorInset;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASCellNode (Unavailable)
|
||||
|
||||
- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE;
|
||||
|
||||
- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE;
|
||||
|
||||
- (void)setLayerBacked:(BOOL)layerBacked AS_UNAVAILABLE("ASCellNode does not support layer-backing, although subnodes may be layer-backed.");
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
* Simple label-style cell node. Read its source for an example of custom <ASCellNode>s.
|
||||
*/
|
||||
@interface ASTextCellNode : ASCellNode
|
||||
|
||||
/**
|
||||
* Initializes a text cell with given text attributes and text insets
|
||||
*/
|
||||
- (instancetype)initWithAttributes:(NSDictionary *)textAttributes insets:(UIEdgeInsets)textInsets;
|
||||
|
||||
/**
|
||||
* Text to display.
|
||||
*/
|
||||
@property (nullable, copy) NSString *text;
|
||||
|
||||
/**
|
||||
* A dictionary containing key-value pairs for text attributes. You can specify the font, text color, text shadow color, and text shadow offset using the keys listed in NSString UIKit Additions Reference.
|
||||
*/
|
||||
@property (copy) NSDictionary<NSAttributedStringKey, id> *textAttributes;
|
||||
|
||||
/**
|
||||
* The text inset or outset for each edge. The default value is 15.0 horizontal and 11.0 vertical padding.
|
||||
*/
|
||||
@property UIEdgeInsets textInsets;
|
||||
|
||||
/**
|
||||
* The text node used by this cell node.
|
||||
*/
|
||||
@property (readonly) ASTextNode *textNode;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
@ -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 <AsyncDisplayKit/ASCellNode+Internal.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASEqualityHelpers.h>
|
||||
#import "Private/ASInternalHelpers.h"
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASCollectionView+Undeprecated.h>
|
||||
#import <AsyncDisplayKit/ASCollectionElement.h>
|
||||
#import <AsyncDisplayKit/ASTableView+Undeprecated.h>
|
||||
#import <AsyncDisplayKit/_ASDisplayView.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASTextNode.h>
|
||||
#import <AsyncDisplayKit/ASCollectionNode.h>
|
||||
#import <AsyncDisplayKit/ASTableNode.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASViewController.h>
|
||||
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark ASCellNode
|
||||
|
||||
@interface ASCellNode ()
|
||||
{
|
||||
ASDisplayNodeViewControllerBlock _viewControllerBlock;
|
||||
ASDisplayNodeDidLoadBlock _viewControllerDidLoadBlock;
|
||||
ASDisplayNode *_viewControllerNode;
|
||||
UIViewController *_viewController;
|
||||
BOOL _suspendInteractionDelegate;
|
||||
BOOL _selected;
|
||||
BOOL _highlighted;
|
||||
UICollectionViewLayoutAttributes *_layoutAttributes;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASCellNode
|
||||
@synthesize interactionDelegate = _interactionDelegate;
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
// Use UITableViewCell defaults
|
||||
_selectionStyle = UITableViewCellSelectionStyleDefault;
|
||||
_focusStyle = UITableViewCellFocusStyleDefault;
|
||||
self.clipsToBounds = YES;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithViewControllerBlock:(ASDisplayNodeViewControllerBlock)viewControllerBlock didLoadBlock:(ASDisplayNodeDidLoadBlock)didLoadBlock
|
||||
{
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
ASDisplayNodeAssertNotNil(viewControllerBlock, @"should initialize with a valid block that returns a UIViewController");
|
||||
_viewControllerBlock = viewControllerBlock;
|
||||
_viewControllerDidLoadBlock = didLoadBlock;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)didLoad
|
||||
{
|
||||
[super didLoad];
|
||||
|
||||
if (_viewControllerBlock != nil) {
|
||||
|
||||
_viewController = _viewControllerBlock();
|
||||
_viewControllerBlock = nil;
|
||||
|
||||
if ([_viewController isKindOfClass:[ASViewController class]]) {
|
||||
ASViewController *asViewController = (ASViewController *)_viewController;
|
||||
_viewControllerNode = asViewController.node;
|
||||
[_viewController loadViewIfNeeded];
|
||||
} else {
|
||||
// Careful to avoid retain cycle
|
||||
UIViewController *viewController = _viewController;
|
||||
_viewControllerNode = [[ASDisplayNode alloc] initWithViewBlock:^{
|
||||
return viewController.view;
|
||||
}];
|
||||
}
|
||||
[self addSubnode:_viewControllerNode];
|
||||
|
||||
// Since we just loaded our node, and added _viewControllerNode as a subnode,
|
||||
// _viewControllerNode must have just loaded its view, so now is an appropriate
|
||||
// time to execute our didLoadBlock, if we were given one.
|
||||
if (_viewControllerDidLoadBlock != nil) {
|
||||
_viewControllerDidLoadBlock(self);
|
||||
_viewControllerDidLoadBlock = nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layout
|
||||
{
|
||||
[super layout];
|
||||
|
||||
_viewControllerNode.frame = self.bounds;
|
||||
}
|
||||
|
||||
- (void)_rootNodeDidInvalidateSize
|
||||
{
|
||||
if (_interactionDelegate != nil) {
|
||||
[_interactionDelegate nodeDidInvalidateSize:self];
|
||||
} else {
|
||||
[super _rootNodeDidInvalidateSize];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_layoutTransitionMeasurementDidFinish
|
||||
{
|
||||
if (_interactionDelegate != nil) {
|
||||
[_interactionDelegate nodeDidInvalidateSize:self];
|
||||
} else {
|
||||
[super _layoutTransitionMeasurementDidFinish];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isSelected
|
||||
{
|
||||
return ASLockedSelf(_selected);
|
||||
}
|
||||
|
||||
- (void)setSelected:(BOOL)selected
|
||||
{
|
||||
if (ASLockedSelfCompareAssign(_selected, selected)) {
|
||||
if (!_suspendInteractionDelegate) {
|
||||
ASPerformBlockOnMainThread(^{
|
||||
[_interactionDelegate nodeSelectedStateDidChange:self];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isHighlighted
|
||||
{
|
||||
return ASLockedSelf(_highlighted);
|
||||
}
|
||||
|
||||
- (void)setHighlighted:(BOOL)highlighted
|
||||
{
|
||||
if (ASLockedSelfCompareAssign(_highlighted, highlighted)) {
|
||||
if (!_suspendInteractionDelegate) {
|
||||
ASPerformBlockOnMainThread(^{
|
||||
[_interactionDelegate nodeHighlightedStateDidChange:self];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)__setSelectedFromUIKit:(BOOL)selected;
|
||||
{
|
||||
// Note: Race condition could mean redundant sets. Risk is low.
|
||||
if (ASLockedSelf(_selected != selected)) {
|
||||
_suspendInteractionDelegate = YES;
|
||||
self.selected = selected;
|
||||
_suspendInteractionDelegate = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)__setHighlightedFromUIKit:(BOOL)highlighted;
|
||||
{
|
||||
// Note: Race condition could mean redundant sets. Risk is low.
|
||||
if (ASLockedSelf(_highlighted != highlighted)) {
|
||||
_suspendInteractionDelegate = YES;
|
||||
self.highlighted = highlighted;
|
||||
_suspendInteractionDelegate = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)canUpdateToNodeModel:(id)nodeModel
|
||||
{
|
||||
return [self.nodeModel class] == [nodeModel class];
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPath
|
||||
{
|
||||
return [self.owningNode indexPathForNode:self];
|
||||
}
|
||||
|
||||
- (UIViewController *)viewController
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
// Force the view to load so that we will create the
|
||||
// view controller if we haven't already.
|
||||
if (self.isNodeLoaded == NO) {
|
||||
[self view];
|
||||
}
|
||||
return _viewController;
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-missing-super-calls"
|
||||
|
||||
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssert([self.view isKindOfClass:_ASDisplayView.class], @"ASCellNode views must be of type _ASDisplayView");
|
||||
[(_ASDisplayView *)self.view __forwardTouchesBegan:touches withEvent:event];
|
||||
}
|
||||
|
||||
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssert([self.view isKindOfClass:_ASDisplayView.class], @"ASCellNode views must be of type _ASDisplayView");
|
||||
[(_ASDisplayView *)self.view __forwardTouchesMoved:touches withEvent:event];
|
||||
}
|
||||
|
||||
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssert([self.view isKindOfClass:_ASDisplayView.class], @"ASCellNode views must be of type _ASDisplayView");
|
||||
[(_ASDisplayView *)self.view __forwardTouchesEnded:touches withEvent:event];
|
||||
}
|
||||
|
||||
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssert([self.view isKindOfClass:_ASDisplayView.class], @"ASCellNode views must be of type _ASDisplayView");
|
||||
[(_ASDisplayView *)self.view __forwardTouchesCancelled:touches withEvent:event];
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (UICollectionViewLayoutAttributes *)layoutAttributes
|
||||
{
|
||||
return ASLockedSelf(_layoutAttributes);
|
||||
}
|
||||
|
||||
- (void)setLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if (ASLockedSelfCompareAssignObjects(_layoutAttributes, layoutAttributes)) {
|
||||
if (layoutAttributes != nil) {
|
||||
[self applyLayoutAttributes:layoutAttributes];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
|
||||
{
|
||||
// To be overriden by subclasses
|
||||
}
|
||||
|
||||
- (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame
|
||||
{
|
||||
// To be overriden by subclasses
|
||||
}
|
||||
|
||||
- (void)didEnterVisibleState
|
||||
{
|
||||
[super didEnterVisibleState];
|
||||
if (self.neverShowPlaceholders) {
|
||||
[self recursivelyEnsureDisplaySynchronously:YES];
|
||||
}
|
||||
[self handleVisibilityChange:YES];
|
||||
}
|
||||
|
||||
- (void)didExitVisibleState
|
||||
{
|
||||
[super didExitVisibleState];
|
||||
[self handleVisibilityChange:NO];
|
||||
}
|
||||
|
||||
+ (BOOL)requestsVisibilityNotifications
|
||||
{
|
||||
static NSCache<Class, NSNumber *> *cache;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
cache = [[NSCache alloc] init];
|
||||
});
|
||||
NSNumber *result = [cache objectForKey:self];
|
||||
if (result == nil) {
|
||||
BOOL overrides = ASSubclassOverridesSelector([ASCellNode class], self, @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:));
|
||||
result = overrides ? (NSNumber *)kCFBooleanTrue : (NSNumber *)kCFBooleanFalse;
|
||||
[cache setObject:result forKey:self];
|
||||
}
|
||||
return (result == (NSNumber *)kCFBooleanTrue);
|
||||
}
|
||||
|
||||
- (void)handleVisibilityChange:(BOOL)isVisible
|
||||
{
|
||||
if ([self.class requestsVisibilityNotifications] == NO) {
|
||||
return; // The work below is expensive, and only valuable for subclasses watching visibility events.
|
||||
}
|
||||
|
||||
// NOTE: This assertion is failing in some apps and will be enabled soon.
|
||||
// ASDisplayNodeAssert(self.isNodeLoaded, @"Node should be loaded in order for it to become visible or invisible. If not in this situation, we shouldn't trigger creating the view.");
|
||||
|
||||
UIView *view = self.view;
|
||||
CGRect cellFrame = CGRectZero;
|
||||
|
||||
// Ensure our _scrollView is still valid before converting. It's also possible that we have already been removed from the _scrollView,
|
||||
// in which case it is not valid to perform a convertRect (this actually crashes on iOS 8).
|
||||
UIScrollView *scrollView = (_scrollView != nil && view.superview != nil && [view isDescendantOfView:_scrollView]) ? _scrollView : nil;
|
||||
if (scrollView) {
|
||||
cellFrame = [view convertRect:view.bounds toView:_scrollView];
|
||||
}
|
||||
|
||||
// If we did not convert, we'll pass along CGRectZero and a nil scrollView. The EventInvisible call is thus equivalent to
|
||||
// didExitVisibileState, but is more convenient for the developer than implementing multiple methods.
|
||||
[self cellNodeVisibilityEvent:isVisible ? ASCellNodeVisibilityEventVisible
|
||||
: ASCellNodeVisibilityEventInvisible
|
||||
inScrollView:scrollView
|
||||
withCellFrame:cellFrame];
|
||||
}
|
||||
|
||||
- (NSMutableArray<NSDictionary *> *)propertiesForDebugDescription
|
||||
{
|
||||
NSMutableArray *result = [super propertiesForDebugDescription];
|
||||
|
||||
UIScrollView *scrollView = self.scrollView;
|
||||
|
||||
ASDisplayNode *owningNode = scrollView.asyncdisplaykit_node;
|
||||
if ([owningNode isKindOfClass:[ASCollectionNode class]]) {
|
||||
NSIndexPath *ip = [(ASCollectionNode *)owningNode indexPathForNode:self];
|
||||
if (ip != nil) {
|
||||
[result addObject:@{ @"indexPath" : ip }];
|
||||
}
|
||||
[result addObject:@{ @"collectionNode" : owningNode }];
|
||||
} else if ([owningNode isKindOfClass:[ASTableNode class]]) {
|
||||
NSIndexPath *ip = [(ASTableNode *)owningNode indexPathForNode:self];
|
||||
if (ip != nil) {
|
||||
[result addObject:@{ @"indexPath" : ip }];
|
||||
}
|
||||
[result addObject:@{ @"tableNode" : owningNode }];
|
||||
|
||||
} else if ([scrollView isKindOfClass:[ASCollectionView class]]) {
|
||||
NSIndexPath *ip = [(ASCollectionView *)scrollView indexPathForNode:self];
|
||||
if (ip != nil) {
|
||||
[result addObject:@{ @"indexPath" : ip }];
|
||||
}
|
||||
[result addObject:@{ @"collectionView" : ASObjectDescriptionMakeTiny(scrollView) }];
|
||||
|
||||
} else if ([scrollView isKindOfClass:[ASTableView class]]) {
|
||||
NSIndexPath *ip = [(ASTableView *)scrollView indexPathForNode:self];
|
||||
if (ip != nil) {
|
||||
[result addObject:@{ @"indexPath" : ip }];
|
||||
}
|
||||
[result addObject:@{ @"tableView" : ASObjectDescriptionMakeTiny(scrollView) }];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSString *)supplementaryElementKind
|
||||
{
|
||||
return self.collectionElement.supplementaryElementKind;
|
||||
}
|
||||
|
||||
- (BOOL)supportsLayerBacking
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)shouldUseUIKitCell
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark ASWrapperCellNode
|
||||
|
||||
// TODO: Consider if other calls, such as willDisplayCell, should be bridged to this class.
|
||||
@implementation ASWrapperCellNode : ASCellNode
|
||||
|
||||
- (BOOL)shouldUseUIKitCell
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark ASTextCellNode
|
||||
|
||||
@implementation ASTextCellNode {
|
||||
NSDictionary<NSAttributedStringKey, id> *_textAttributes;
|
||||
UIEdgeInsets _textInsets;
|
||||
NSString *_text;
|
||||
}
|
||||
|
||||
static const CGFloat kASTextCellNodeDefaultFontSize = 18.0f;
|
||||
static const CGFloat kASTextCellNodeDefaultHorizontalPadding = 15.0f;
|
||||
static const CGFloat kASTextCellNodeDefaultVerticalPadding = 11.0f;
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithAttributes:[ASTextCellNode defaultTextAttributes] insets:[ASTextCellNode defaultTextInsets]];
|
||||
}
|
||||
|
||||
- (instancetype)initWithAttributes:(NSDictionary *)textAttributes insets:(UIEdgeInsets)textInsets
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_textInsets = textInsets;
|
||||
_textAttributes = [textAttributes copy];
|
||||
_textNode = [[ASTextNode alloc] init];
|
||||
self.automaticallyManagesSubnodes = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:self.textInsets child:self.textNode];
|
||||
}
|
||||
|
||||
+ (NSDictionary *)defaultTextAttributes
|
||||
{
|
||||
return @{NSFontAttributeName : [UIFont systemFontOfSize:kASTextCellNodeDefaultFontSize]};
|
||||
}
|
||||
|
||||
+ (UIEdgeInsets)defaultTextInsets
|
||||
{
|
||||
return UIEdgeInsetsMake(kASTextCellNodeDefaultVerticalPadding, kASTextCellNodeDefaultHorizontalPadding, kASTextCellNodeDefaultVerticalPadding, kASTextCellNodeDefaultHorizontalPadding);
|
||||
}
|
||||
|
||||
- (NSDictionary *)textAttributes
|
||||
{
|
||||
return ASLockedSelf(_textAttributes);
|
||||
}
|
||||
|
||||
- (void)setTextAttributes:(NSDictionary *)textAttributes
|
||||
{
|
||||
ASDisplayNodeAssertNotNil(textAttributes, @"Invalid text attributes");
|
||||
ASLockScopeSelf();
|
||||
if (ASCompareAssignCopy(_textAttributes, textAttributes)) {
|
||||
[self locked_updateAttributedText];
|
||||
}
|
||||
}
|
||||
|
||||
- (UIEdgeInsets)textInsets
|
||||
{
|
||||
return ASLockedSelf(_textInsets);
|
||||
}
|
||||
|
||||
- (void)setTextInsets:(UIEdgeInsets)textInsets
|
||||
{
|
||||
if (ASLockedSelfCompareAssignCustom(_textInsets, textInsets, UIEdgeInsetsEqualToEdgeInsets)) {
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)text
|
||||
{
|
||||
return ASLockedSelf(_text);
|
||||
}
|
||||
|
||||
- (void)setText:(NSString *)text
|
||||
{
|
||||
ASLockScopeSelf();
|
||||
if (ASCompareAssignCopy(_text, text)) {
|
||||
[self locked_updateAttributedText];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)locked_updateAttributedText
|
||||
{
|
||||
if (_text == nil) {
|
||||
_textNode.attributedText = nil;
|
||||
return;
|
||||
}
|
||||
|
||||
_textNode.attributedText = [[NSAttributedString alloc] initWithString:_text attributes:_textAttributes];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
@ -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 <AsyncDisplayKit/ASRelativeLayoutSpec.h>
|
||||
|
||||
/**
|
||||
* 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<ASLayoutElement>)child NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -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 <AsyncDisplayKit/ASCenterLayoutSpec.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
|
||||
@implementation ASCenterLayoutSpec
|
||||
{
|
||||
ASCenterLayoutSpecCenteringOptions _centeringOptions;
|
||||
ASCenterLayoutSpecSizingOptions _sizingOptions;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions
|
||||
sizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions
|
||||
child:(id<ASLayoutElement>)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<ASLayoutElement>)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
|
@ -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 <AsyncDisplayKit/ASDataController.h>
|
||||
#import <AsyncDisplayKit/ASTraitCollection.h>
|
||||
|
||||
@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<ASRangeManagingNode> 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<ASRangeManagingNode>)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
|
@ -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 <AsyncDisplayKit/ASCollectionElement.h>
|
||||
#import <AsyncDisplayKit/ASCellNode+Internal.h>
|
||||
#import <mutex>
|
||||
|
||||
@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<ASRangeManagingNode>)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<std::mutex> 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<std::mutex> l(_lock);
|
||||
return _node;
|
||||
}
|
||||
|
||||
- (void)setTraitCollection:(ASPrimitiveTraitCollection)traitCollection
|
||||
{
|
||||
ASCellNode *nodeIfNeedsPropagation;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_lock);
|
||||
if (! ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(_traitCollection, traitCollection)) {
|
||||
_traitCollection = traitCollection;
|
||||
nodeIfNeedsPropagation = _node;
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeIfNeedsPropagation != nil) {
|
||||
ASTraitCollectionPropagateDown(nodeIfNeedsPropagation, traitCollection);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
@ -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 <AsyncDisplayKit/ASCollectionLayoutDelegate.h>
|
||||
|
||||
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 <ASCollectionLayoutDelegate>
|
||||
|
||||
- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
@ -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 <AsyncDisplayKit/ASCollectionFlowLayoutDelegate.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASCellNode+Internal.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutState.h>
|
||||
#import <AsyncDisplayKit/ASCollectionElement.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutDefines.h>
|
||||
#import <AsyncDisplayKit/ASCollections.h>
|
||||
#import <AsyncDisplayKit/ASElementMap.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASStackLayoutSpec.h>
|
||||
|
||||
@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<ASCellNode *> *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
|
@ -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 <AsyncDisplayKit/ASCollectionLayoutDelegate.h>
|
||||
#import <AsyncDisplayKit/ASScrollDirection.h>
|
||||
|
||||
@class ASElementMap;
|
||||
@class ASCollectionGalleryLayoutDelegate;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol ASCollectionGalleryLayoutPropertiesProviding <NSObject>
|
||||
|
||||
/**
|
||||
* 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 <ASCollectionLayoutDelegate>
|
||||
|
||||
@property (nonatomic, weak) id<ASCollectionGalleryLayoutPropertiesProviding> 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
|
@ -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 <AsyncDisplayKit/ASCollectionGalleryLayoutDelegate.h>
|
||||
|
||||
#import <AsyncDisplayKit/_ASCollectionGalleryLayoutItem.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASCellNode.h>
|
||||
#import <AsyncDisplayKit/ASCollectionElement.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutDefines.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutState.h>
|
||||
#import <AsyncDisplayKit/ASElementMap.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASLayoutRangeType.h>
|
||||
#import <AsyncDisplayKit/ASStackLayoutSpec.h>
|
||||
|
||||
#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
|
@ -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 <AsyncDisplayKit/ASCollectionGalleryLayoutDelegate.h>
|
||||
|
||||
#import <AsyncDisplayKit/_ASCollectionGalleryLayoutInfo.h>
|
||||
#import <AsyncDisplayKit/_ASCollectionGalleryLayoutItem.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASCellNode.h>
|
||||
#import <AsyncDisplayKit/ASCollectionElement.h>
|
||||
#import <AsyncDisplayKit/ASCollections.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutDefines.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutState.h>
|
||||
#import <AsyncDisplayKit/ASElementMap.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASLayoutRangeType.h>
|
||||
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASStackLayoutSpec.h>
|
||||
|
||||
#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<ASCollectionGalleryLayoutPropertiesProviding>)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<ASCollectionGalleryLayoutPropertiesProviding> 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
|
@ -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 <AsyncDisplayKit/ASCollectionView.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol ASCollectionViewLayoutFacilitatorProtocol;
|
||||
@class ASCollectionNode;
|
||||
@class ASDataController;
|
||||
@class ASRangeController;
|
||||
|
||||
@interface ASCollectionView ()
|
||||
- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id<ASCollectionViewLayoutFacilitatorProtocol>)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<NSIndexPath *> *)convertIndexPathsToCollectionNode:(nullable NSArray<NSIndexPath *> *)indexPaths;
|
||||
|
||||
- (void)beginUpdates;
|
||||
|
||||
- (void)endUpdatesAnimated:(BOOL)animated completion:(nullable void (^)(BOOL))completion;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
@ -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 <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
|
||||
@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<ASCollectionLayoutDelegate> 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<ASCollectionLayoutDelegate>)layoutDelegate NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (instancetype)init __unavailable;
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder __unavailable;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
@ -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 <AsyncDisplayKit/ASCollectionLayout.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASAbstractLayoutController.h>
|
||||
#import <AsyncDisplayKit/ASCellNode.h>
|
||||
#import <AsyncDisplayKit/ASCollectionElement.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutCache.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutContext+Private.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutDelegate.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutState+Private.h>
|
||||
#import <AsyncDisplayKit/ASCollectionNode+Beta.h>
|
||||
#import <AsyncDisplayKit/ASDispatch.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASElementMap.h>
|
||||
#import <AsyncDisplayKit/ASEqualityHelpers.h>
|
||||
#import <AsyncDisplayKit/ASPageTable.h>
|
||||
|
||||
static const ASRangeTuningParameters kASDefaultMeasureRangeTuningParameters = {
|
||||
.leadingBufferScreenfuls = 2.0,
|
||||
.trailingBufferScreenfuls = 2.0
|
||||
};
|
||||
|
||||
static const ASScrollDirection kASStaticScrollDirection = (ASScrollDirectionRight | ASScrollDirectionDown);
|
||||
|
||||
@interface ASCollectionLayout () <ASDataControllerLayoutDelegate> {
|
||||
ASCollectionLayoutCache *_layoutCache;
|
||||
ASCollectionLayoutState *_layout; // Main thread only.
|
||||
|
||||
struct {
|
||||
unsigned int implementsAdditionalInfoForLayoutWithElements:1;
|
||||
} _layoutDelegateFlags;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASCollectionLayout
|
||||
|
||||
- (instancetype)initWithLayoutDelegate:(id<ASCollectionLayoutDelegate>)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<ASCollectionLayoutDelegate> 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<UIDataSourceTranslating> 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<UICollectionViewLayoutAttributes *> *)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<UICollectionViewLayoutAttributes *> *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<UICollectionViewLayoutAttributes *> *blockingAttrs = hasBlockingRect ? [NSMutableOrderedSet orderedSet] : nil;
|
||||
NSMutableOrderedSet<UICollectionViewLayoutAttributes *> *nonBlockingAttrs = [NSMutableOrderedSet orderedSet];
|
||||
for (id pagePtr in attrsTable) {
|
||||
ASPageCoordinate page = (ASPageCoordinate)pagePtr;
|
||||
NSArray<UICollectionViewLayoutAttributes *> *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
|
@ -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 <Foundation/Foundation.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
|
||||
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
|
@ -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 <AsyncDisplayKit/ASCollectionLayoutCache.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutState.h>
|
||||
#import <AsyncDisplayKit/ASElementMap.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
|
||||
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<ASElementMap *, NSMapTable<ASCollectionLayoutContext *, ASCollectionLayoutState *> *> *_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
|
@ -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 <AsyncDisplayKit/ASCollectionLayoutContext.h>
|
||||
|
||||
@class ASCollectionLayoutCache;
|
||||
@protocol ASCollectionLayoutDelegate;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ASCollectionLayoutContext (Private)
|
||||
|
||||
@property (nonatomic, readonly) Class<ASCollectionLayoutDelegate> layoutDelegateClass;
|
||||
@property (nonatomic, weak, readonly) ASCollectionLayoutCache *layoutCache;
|
||||
|
||||
- (instancetype)initWithViewportSize:(CGSize)viewportSize
|
||||
initialContentOffset:(CGPoint)initialContentOffset
|
||||
scrollableDirections:(ASScrollDirection)scrollableDirections
|
||||
elements:(ASElementMap *)elements
|
||||
layoutDelegateClass:(Class<ASCollectionLayoutDelegate>)layoutDelegateClass
|
||||
layoutCache:(ASCollectionLayoutCache *)layoutCache
|
||||
additionalInfo:(nullable id)additionalInfo;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
@ -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 <UIKit/UIKit.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
#import <AsyncDisplayKit/ASScrollDirection.h>
|
||||
|
||||
@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
|
@ -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 <AsyncDisplayKit/ASCollectionLayoutContext.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutContext+Private.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutDelegate.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutCache.h>
|
||||
#import <AsyncDisplayKit/ASElementMap.h>
|
||||
#import <AsyncDisplayKit/ASEqualityHelpers.h>
|
||||
#import <AsyncDisplayKit/ASHashing.h>
|
||||
|
||||
@implementation ASCollectionLayoutContext {
|
||||
Class<ASCollectionLayoutDelegate> _layoutDelegateClass;
|
||||
__weak ASCollectionLayoutCache *_layoutCache;
|
||||
}
|
||||
|
||||
- (instancetype)initWithViewportSize:(CGSize)viewportSize
|
||||
initialContentOffset:(CGPoint)initialContentOffset
|
||||
scrollableDirections:(ASScrollDirection)scrollableDirections
|
||||
elements:(ASElementMap *)elements
|
||||
layoutDelegateClass:(Class<ASCollectionLayoutDelegate>)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<ASCollectionLayoutDelegate>)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
|
@ -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 <UIKit/UIKit.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
#import <AsyncDisplayKit/ASDimension.h>
|
||||
#import <AsyncDisplayKit/ASScrollDirection.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
AS_EXTERN ASSizeRange ASSizeRangeForCollectionLayoutThatFitsViewportSize(CGSize viewportSize, ASScrollDirection scrollableDirections) AS_WARN_UNUSED_RESULT;
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -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;
|
||||
}
|
@ -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 <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <AsyncDisplayKit/ASScrollDirection.h>
|
||||
|
||||
@class ASElementMap, ASCollectionLayoutContext, ASCollectionLayoutState;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol ASCollectionLayoutDelegate <NSObject>
|
||||
|
||||
/**
|
||||
* @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
|
@ -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 <AsyncDisplayKit/ASCollectionLayoutState.h>
|
||||
#import <AsyncDisplayKit/ASPageTable.h>
|
||||
|
||||
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
|
@ -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 <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
|
||||
@class ASCollectionLayoutContext, ASLayout, ASCollectionElement;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef ASCollectionElement * _Nullable (^ASCollectionLayoutStateGetElementBlock)(ASLayout *);
|
||||
|
||||
@interface NSMapTable (ASCollectionLayoutConvenience)
|
||||
|
||||
+ (NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *)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<ASCollectionElement *, UICollectionViewLayoutAttributes *> *)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<UICollectionViewLayoutAttributes *> *)allLayoutAttributes;
|
||||
|
||||
/**
|
||||
* Returns layout attributes of elements in the specified rect.
|
||||
*
|
||||
* @param rect The rect containing the target elements.
|
||||
*/
|
||||
- (NSArray<UICollectionViewLayoutAttributes *> *)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
|
@ -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 <AsyncDisplayKit/ASCollectionLayoutState.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutState+Private.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASCellNode.h>
|
||||
#import <AsyncDisplayKit/ASCollectionElement.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASElementMap.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASLayoutSpecUtilities.h>
|
||||
#import <AsyncDisplayKit/ASPageTable.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
|
||||
#import <queue>
|
||||
|
||||
@implementation NSMapTable (ASCollectionLayoutConvenience)
|
||||
|
||||
+ (NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *)elementToLayoutAttributesTable
|
||||
{
|
||||
return [NSMapTable mapTableWithKeyOptions:(NSMapTableWeakMemory | NSMapTableObjectPointerPersonality) valueOptions:NSMapTableStrongMemory];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASCollectionLayoutState {
|
||||
AS::Mutex __instanceLock__;
|
||||
CGSize _contentSize;
|
||||
ASCollectionLayoutContext *_context;
|
||||
NSMapTable<ASCollectionElement *, UICollectionViewLayoutAttributes *> *_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<Context> 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<UICollectionViewLayoutAttributes *> *)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<UICollectionViewLayoutAttributes *> *)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<UICollectionViewLayoutAttributes *> alloc] init];
|
||||
for (id pagePtr in pages) {
|
||||
ASPageCoordinate page = (ASPageCoordinate)pagePtr;
|
||||
NSArray<UICollectionViewLayoutAttributes *> *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<ASCollectionElement *, UICollectionViewLayoutAttributes *> *)table
|
||||
contentSize:(CGSize)contentSize
|
||||
pageSize:(CGSize)pageSize
|
||||
{
|
||||
NSMutableArray<UICollectionViewLayoutAttributes *> *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
|
@ -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 <AsyncDisplayKit/ASCollectionNode.h>
|
||||
|
||||
@protocol ASCollectionViewLayoutFacilitatorProtocol, ASCollectionLayoutDelegate, ASBatchFetchingDelegate;
|
||||
@class ASElementMap;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ASCollectionNode (Beta)
|
||||
|
||||
/**
|
||||
* Allows providing a custom subclass of ASCollectionView to be managed by ASCollectionNode.
|
||||
*
|
||||
* @default [ASCollectionView class] is used whenever this property is unset or nil.
|
||||
*/
|
||||
@property (nullable, nonatomic) Class collectionViewClass;
|
||||
|
||||
/**
|
||||
* The elements that are currently displayed. The "UIKit index space". Must be accessed on main thread.
|
||||
*/
|
||||
@property (nonatomic, readonly) ASElementMap *visibleElements;
|
||||
|
||||
@property (nullable, readonly) id<ASCollectionLayoutDelegate> layoutDelegate;
|
||||
|
||||
@property (nullable, nonatomic, weak) id<ASBatchFetchingDelegate> batchFetchingDelegate;
|
||||
|
||||
/**
|
||||
* When this mode is enabled, ASCollectionView matches the timing of UICollectionView as closely as
|
||||
* possible, ensuring that all reload and edit operations are performed on the main thread as
|
||||
* blocking calls.
|
||||
*
|
||||
* This mode is useful for applications that are debugging issues with their collection view
|
||||
* implementation. In particular, some applications do not properly conform to the API requirement
|
||||
* of UICollectionView, and these applications may experience difficulties with ASCollectionView.
|
||||
* Providing this mode allows for developers to work towards resolving technical debt in their
|
||||
* collection view data source, while ramping up asynchronous collection layout.
|
||||
*
|
||||
* NOTE: Because this mode results in expensive operations like cell layout being performed on the
|
||||
* main thread, it should be used as a tool to resolve data source conformance issues with Apple
|
||||
* collection view API.
|
||||
*
|
||||
* @default defaults to ASCellLayoutModeNone.
|
||||
*/
|
||||
@property (nonatomic) ASCellLayoutMode cellLayoutMode;
|
||||
|
||||
/**
|
||||
* Returns YES if the ASCollectionNode contents are completely synchronized with the underlying collection-view layout.
|
||||
*/
|
||||
@property (nonatomic, readonly, getter=isSynchronized) BOOL synchronized;
|
||||
|
||||
/**
|
||||
* Schedules a block to be performed (on the main thread) as soon as the completion block is called
|
||||
* on performBatchUpdates:.
|
||||
*
|
||||
* When isSynchronized == YES, the block is run block immediately (before the method returns).
|
||||
*/
|
||||
- (void)onDidFinishSynchronizing:(void (^)(void))didFinishSynchronizing;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id<ASCollectionViewLayoutFacilitatorProtocol>)layoutFacilitator;
|
||||
|
||||
- (instancetype)initWithLayoutDelegate:(id<ASCollectionLayoutDelegate>)layoutDelegate layoutFacilitator:(nullable id<ASCollectionViewLayoutFacilitatorProtocol>)layoutFacilitator;
|
||||
|
||||
- (void)beginUpdates ASDISPLAYNODE_DEPRECATED_MSG("Use -performBatchUpdates:completion: instead.");
|
||||
|
||||
- (void)endUpdatesAnimated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use -performBatchUpdates:completion: instead.");
|
||||
|
||||
- (void)endUpdatesAnimated:(BOOL)animated completion:(nullable void (^)(BOOL))completion ASDISPLAYNODE_DEPRECATED_MSG("Use -performBatchUpdates:completion: instead.");
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
@ -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 <UIKit/UICollectionView.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
#import <AsyncDisplayKit/ASRangeControllerUpdateRangeProtocol+Beta.h>
|
||||
#import <AsyncDisplayKit/ASCollectionView.h>
|
||||
#import <AsyncDisplayKit/ASBlockTypes.h>
|
||||
#import <AsyncDisplayKit/ASRangeManagingNode.h>
|
||||
|
||||
@protocol ASCollectionViewLayoutFacilitatorProtocol;
|
||||
@protocol ASCollectionDelegate;
|
||||
@protocol ASCollectionDataSource;
|
||||
@class ASCollectionView;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* ASCollectionNode is a node based class that wraps an ASCollectionView. It can be used
|
||||
* as a subnode of another node, and provide room for many (great) features and improvements later on.
|
||||
*/
|
||||
@interface ASCollectionNode : ASDisplayNode <ASRangeControllerUpdateRangeProtocol, ASRangeManagingNode>
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
* Initializes an ASCollectionNode
|
||||
*
|
||||
* @discussion Initializes and returns a newly allocated collection node object with the specified layout.
|
||||
*
|
||||
* @param layout The layout object to use for organizing items. The collection view stores a strong reference to the specified object. Must not be nil.
|
||||
*/
|
||||
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout;
|
||||
|
||||
/**
|
||||
* Initializes an ASCollectionNode
|
||||
*
|
||||
* @discussion Initializes and returns a newly allocated collection node object with the specified frame and layout.
|
||||
*
|
||||
* @param frame The frame rectangle for the collection view, measured in points. The origin of the frame is relative to the superview in which you plan to add it. This frame is passed to the superclass during initialization.
|
||||
* @param layout The layout object to use for organizing items. The collection view stores a strong reference to the specified object. Must not be nil.
|
||||
*/
|
||||
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
|
||||
|
||||
/**
|
||||
* Returns the corresponding ASCollectionView
|
||||
*
|
||||
* @return view The corresponding ASCollectionView.
|
||||
*/
|
||||
@property (readonly) ASCollectionView *view;
|
||||
|
||||
/**
|
||||
* The object that acts as the asynchronous delegate of the collection view
|
||||
*
|
||||
* @discussion The delegate must adopt the ASCollectionDelegate protocol. The collection view maintains a weak reference to the delegate object.
|
||||
*
|
||||
* The delegate object is responsible for providing size constraints for nodes and indicating whether batch fetching should begin.
|
||||
* @note This is a convenience method which sets the asyncDelegate on the collection node's collection view.
|
||||
*/
|
||||
@property (nullable, weak) id <ASCollectionDelegate> delegate;
|
||||
|
||||
/**
|
||||
* The object that acts as the asynchronous data source of the collection view
|
||||
*
|
||||
* @discussion The datasource must adopt the ASCollectionDataSource protocol. The collection view maintains a weak reference to the datasource object.
|
||||
*
|
||||
* The datasource object is responsible for providing nodes or node creation blocks to the collection view.
|
||||
* @note This is a convenience method which sets the asyncDatasource on the collection node's collection view.
|
||||
*/
|
||||
@property (nullable, weak) id <ASCollectionDataSource> dataSource;
|
||||
|
||||
/**
|
||||
* The number of screens left to scroll before the delegate -collectionNode:beginBatchFetchingWithContext: is called.
|
||||
*
|
||||
* Defaults to two screenfuls.
|
||||
*/
|
||||
@property (nonatomic) CGFloat leadingScreensForBatching;
|
||||
|
||||
/*
|
||||
* A Boolean value that determines whether the collection node will be flipped.
|
||||
* If the value of this property is YES, the first cell node will be at the bottom of the collection node (as opposed to the top by default). This is useful for chat/messaging apps. The default value is NO.
|
||||
*/
|
||||
@property (nonatomic) BOOL inverted;
|
||||
|
||||
/**
|
||||
* A Boolean value that indicates whether users can select items in the collection node.
|
||||
* If the value of this property is YES (the default), users can select items. If you want more fine-grained control over the selection of items, you must provide a delegate object and implement the appropriate methods of the UICollectionNodeDelegate protocol.
|
||||
*/
|
||||
@property (nonatomic) BOOL allowsSelection;
|
||||
|
||||
/**
|
||||
* A Boolean value that determines whether users can select more than one item in the collection node.
|
||||
* This property controls whether multiple items can be selected simultaneously. The default value of this property is NO.
|
||||
* When the value of this property is YES, tapping a cell adds it to the current selection (assuming the delegate permits the cell to be selected). Tapping the cell again removes it from the selection.
|
||||
*/
|
||||
@property (nonatomic) BOOL allowsMultipleSelection;
|
||||
|
||||
/**
|
||||
* A Boolean value that determines whether bouncing always occurs when vertical scrolling reaches the end of the content.
|
||||
* The default value of this property is NO.
|
||||
*/
|
||||
@property (nonatomic) BOOL alwaysBounceVertical;
|
||||
|
||||
/**
|
||||
* A Boolean value that determines whether bouncing always occurs when horizontal scrolling reaches the end of the content view.
|
||||
* The default value of this property is NO.
|
||||
*/
|
||||
@property (nonatomic) BOOL alwaysBounceHorizontal;
|
||||
|
||||
/**
|
||||
* A Boolean value that controls whether the vertical scroll indicator is visible.
|
||||
* The default value of this property is YES.
|
||||
*/
|
||||
@property (nonatomic) BOOL showsVerticalScrollIndicator;
|
||||
|
||||
/**
|
||||
* A Boolean value that controls whether the horizontal scroll indicator is visible.
|
||||
* The default value of this property is NO.
|
||||
*/
|
||||
@property (nonatomic) BOOL showsHorizontalScrollIndicator;
|
||||
|
||||
/**
|
||||
* The layout used to organize the node's items.
|
||||
*
|
||||
* @discussion Assigning a new layout object to this property causes the new layout to be applied (without animations) to the 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<ASCollectionViewLayoutInspecting> layoutInspector;
|
||||
|
||||
/**
|
||||
* The distance that the content view is inset from the collection node edges. Defaults to UIEdgeInsetsZero.
|
||||
*/
|
||||
@property (nonatomic) UIEdgeInsets contentInset;
|
||||
|
||||
/**
|
||||
* The offset of the content view's origin from the collection node's origin. Defaults to CGPointZero.
|
||||
*/
|
||||
@property (nonatomic) CGPoint contentOffset;
|
||||
|
||||
/**
|
||||
* Sets the offset from the content 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<NSIndexPath *> *)indexPaths;
|
||||
|
||||
/**
|
||||
* Deletes the items specified by an array of index paths.
|
||||
*
|
||||
* @param indexPaths An array of NSIndexPath objects identifying the items to delete.
|
||||
*
|
||||
* @discussion This method must be called from the main thread. The data source must be updated to reflect the changes
|
||||
* before this method is called.
|
||||
*/
|
||||
- (void)deleteItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
|
||||
|
||||
/**
|
||||
* Reloads the specified items.
|
||||
*
|
||||
* @param indexPaths An array of NSIndexPath objects identifying the items to reload.
|
||||
*
|
||||
* @discussion This method must be called from the main thread. The data source must be updated to reflect the changes
|
||||
* before this method is called.
|
||||
*/
|
||||
- (void)reloadItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
|
||||
|
||||
/**
|
||||
* Moves the item at a specified location to a destination location.
|
||||
*
|
||||
* @param indexPath The index path identifying the item to move.
|
||||
*
|
||||
* @param newIndexPath The index path that is the destination of the move for the item.
|
||||
*
|
||||
* @discussion This method must be called from the main thread. The data source must be updated to reflect the changes
|
||||
* before this method is called.
|
||||
*/
|
||||
- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath;
|
||||
|
||||
/**
|
||||
* Reload everything from scratch, destroying the working range and all cached nodes.
|
||||
*
|
||||
* @param completion block to run on completion of asynchronous loading or nil. If supplied, the block is run on
|
||||
* the main thread.
|
||||
* @warning This method is substantially more expensive than UICollectionView's version.
|
||||
*/
|
||||
- (void)reloadDataWithCompletion:(nullable void (^)(void))completion;
|
||||
|
||||
|
||||
/**
|
||||
* Reload everything from scratch, destroying the working range and all cached nodes.
|
||||
*
|
||||
* @warning This method is substantially more expensive than UICollectionView's version.
|
||||
*/
|
||||
- (void)reloadData;
|
||||
|
||||
/**
|
||||
* Triggers a relayout of all nodes.
|
||||
*
|
||||
* @discussion This method invalidates and lays out every cell node in the collection view.
|
||||
*/
|
||||
- (void)relayoutItems;
|
||||
|
||||
#pragma mark - Selection
|
||||
|
||||
/**
|
||||
* The index paths of the selected items, or @c nil if no items are selected.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy, readonly) NSArray<NSIndexPath *> *indexPathsForSelectedItems;
|
||||
|
||||
/**
|
||||
* Selects the item at the specified index path and optionally scrolls it into view.
|
||||
* If the `allowsSelection` property is NO, calling this method has no effect. If there is an existing selection with a different index path and the `allowsMultipleSelection` property is NO, calling this method replaces the previous selection.
|
||||
* This method does not cause any selection-related delegate methods to be called.
|
||||
*
|
||||
* @param indexPath The index path of the item to select. Specifying nil for this parameter clears the current selection.
|
||||
*
|
||||
* @param animated Specify YES to animate the change in the selection or NO to make the change without animating it.
|
||||
*
|
||||
* @param scrollPosition An option that specifies where the item should be positioned when scrolling finishes. For a list of possible values, see `UICollectionViewScrollPosition`.
|
||||
*
|
||||
* @discussion This method must be called from the main thread.
|
||||
*/
|
||||
- (void)selectItemAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition;
|
||||
|
||||
/**
|
||||
* Deselects the item at the specified index.
|
||||
* If the allowsSelection property is NO, calling this method has no effect.
|
||||
* This method does not cause any selection-related delegate methods to be called.
|
||||
*
|
||||
* @param indexPath The index path of the item to select. Specifying nil for this parameter clears the current selection.
|
||||
*
|
||||
* @param animated Specify YES to animate the change in the selection or NO to make the change without animating it.
|
||||
*
|
||||
* @discussion This method must be called from the main thread.
|
||||
*/
|
||||
- (void)deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated;
|
||||
|
||||
#pragma mark - Querying Data
|
||||
|
||||
/**
|
||||
* Retrieves the number of items in the given section.
|
||||
*
|
||||
* @param section The section.
|
||||
*
|
||||
* @return The number of items.
|
||||
*/
|
||||
- (NSInteger)numberOfItemsInSection:(NSInteger)section AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* The number of sections.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSInteger numberOfSections;
|
||||
|
||||
/**
|
||||
* Similar to -visibleCells.
|
||||
*
|
||||
* @return an array containing the nodes being displayed on screen. This must be called on the main thread.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray<__kindof ASCellNode *> *visibleNodes;
|
||||
|
||||
/**
|
||||
* Retrieves the node for the item at the given index path.
|
||||
*
|
||||
* @param indexPath The index path of the requested item.
|
||||
*
|
||||
* @return The node for the given item, or @c nil if no item exists at the specified path.
|
||||
*/
|
||||
- (nullable __kindof ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Retrieves the node-model for the item at the given index path, if any.
|
||||
*
|
||||
* @param indexPath The index path of the requested item.
|
||||
*
|
||||
* @return The node-model for the given item, or @c nil if no item exists at the specified path or no node-model was provided.
|
||||
*
|
||||
* @warning This API is beta and subject to change. We'll try to provide an easy migration path.
|
||||
*/
|
||||
- (nullable id)nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Retrieve the index path for the item with the given node.
|
||||
*
|
||||
* @param cellNode A node for an item in the collection node.
|
||||
*
|
||||
* @return The indexPath for this item.
|
||||
*/
|
||||
- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Retrieve the index paths of all visible items.
|
||||
*
|
||||
* @return an array containing the index paths of all visible items. This must be called on the main thread.
|
||||
*/
|
||||
@property (nonatomic, readonly) NSArray<NSIndexPath *> *indexPathsForVisibleItems;
|
||||
|
||||
/**
|
||||
* Retrieve the index path of the item at the given point.
|
||||
*
|
||||
* @param point The point of the requested item.
|
||||
*
|
||||
* @return The indexPath for the item at the given point. This must be called on the main thread.
|
||||
*/
|
||||
- (nullable NSIndexPath *)indexPathForItemAtPoint:(CGPoint)point AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Retrieve the cell at the given index path.
|
||||
*
|
||||
* @param indexPath The index path of the requested item.
|
||||
*
|
||||
* @return The cell for the given index path. This must be called on the main thread.
|
||||
*/
|
||||
- (nullable UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
* Retrieves the context object for the given section, as provided by the data source in
|
||||
* the @c collectionNode:contextForSection: method.
|
||||
*
|
||||
* @param section The section to get the context for.
|
||||
*
|
||||
* @return The context object, or @c nil if no context was provided.
|
||||
*
|
||||
* TODO: This method currently accepts @c section in the _view_ index space, but it should
|
||||
* be in the node index space. To get the context in the view index space (e.g. for subclasses
|
||||
* of @c UICollectionViewLayout, the user will call the same method on @c ASCollectionView.
|
||||
*/
|
||||
- (nullable id<ASSectionContext>)contextForSection:(NSInteger)section AS_WARN_UNUSED_RESULT;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASCollectionNode (Deprecated)
|
||||
|
||||
- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("This method has been renamed to -waitUntilAllUpdatesAreProcessed.");
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* This is a node-based UICollectionViewDataSource.
|
||||
*/
|
||||
@protocol ASCollectionDataSource <ASCommonCollectionDataSource>
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Asks the data source for the number of items in the given section of the collection node.
|
||||
*
|
||||
* @see @c collectionView:numberOfItemsInSection:
|
||||
*/
|
||||
- (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section;
|
||||
|
||||
/**
|
||||
* Asks the data source for the number of sections in the collection node.
|
||||
*
|
||||
* @see @c numberOfSectionsInCollectionView:
|
||||
*/
|
||||
- (NSInteger)numberOfSectionsInCollectionNode:(ASCollectionNode *)collectionNode;
|
||||
|
||||
/**
|
||||
* --BETA--
|
||||
* Asks the data source for a view-model for the item at the given index path.
|
||||
*
|
||||
* @param collectionNode The sender.
|
||||
* @param indexPath The index path of the item.
|
||||
*
|
||||
* @return An object that contains all the data for this item.
|
||||
*/
|
||||
- (nullable id)collectionNode:(ASCollectionNode *)collectionNode nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
* Similar to -collectionNode:nodeForItemAtIndexPath:
|
||||
* This method takes precedence over collectionNode:nodeForItemAtIndexPath: if implemented.
|
||||
*
|
||||
* @param collectionNode The sender.
|
||||
* @param indexPath The index path of the item.
|
||||
*
|
||||
* @return a block that creates the node for display for this item.
|
||||
* Must be thread-safe (can be called on the main thread or a background
|
||||
* queue) and should not implement reuse (it will be called once per row).
|
||||
*/
|
||||
- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
* Similar to -collectionView:cellForItemAtIndexPath:.
|
||||
*
|
||||
* @param collectionNode The sender.
|
||||
* @param indexPath The index path of the item.
|
||||
*
|
||||
* @return A node to display for the given item. This will be called on the main thread and should
|
||||
* not implement reuse (it will be called once per item). Unlike UICollectionView's version,
|
||||
* this method is not called when the item is about to display.
|
||||
*/
|
||||
- (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
* Asks the data source to provide a node-block to display for the given supplementary element in the collection view.
|
||||
*
|
||||
* @param collectionNode The sender.
|
||||
* @param kind The kind of supplementary element.
|
||||
* @param indexPath The index path of the supplementary element.
|
||||
*/
|
||||
- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
* Asks the data source to provide a node to display for the given supplementary element in the collection view.
|
||||
*
|
||||
* @param collectionNode The sender.
|
||||
* @param kind The kind of supplementary element.
|
||||
* @param indexPath The index path of the supplementary element.
|
||||
*/
|
||||
- (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
* Asks the data source to provide a context object for the given section. This object
|
||||
* can later be retrieved by calling @c contextForSection: and is useful when implementing
|
||||
* custom @c UICollectionViewLayout subclasses. The context object is ret
|
||||
*
|
||||
* @param collectionNode The sender.
|
||||
* @param section The index of the section to provide context for.
|
||||
*
|
||||
* @return A context object to assign to the given section, or @c nil.
|
||||
*/
|
||||
- (nullable id<ASSectionContext>)collectionNode:(ASCollectionNode *)collectionNode contextForSection:(NSInteger)section;
|
||||
|
||||
/**
|
||||
* Asks the data source to provide an array of supplementary element kinds that exist in a given section.
|
||||
*
|
||||
* @param collectionNode The sender.
|
||||
* @param section The index of the section to provide supplementary kinds for.
|
||||
*
|
||||
* @return The supplementary element kinds that exist in the given section, if any.
|
||||
*/
|
||||
- (NSArray<NSString *> *)collectionNode:(ASCollectionNode *)collectionNode supplementaryElementKindsInSection:(NSInteger)section;
|
||||
|
||||
/**
|
||||
* Asks the data source if it's possible to move the specified item interactively.
|
||||
*
|
||||
* See @p -[UICollectionViewDataSource collectionView:canMoveItemAtIndexPath:] @c.
|
||||
*
|
||||
* @param collectionNode The sender.
|
||||
* @param node The display node for the item that may be moved.
|
||||
*
|
||||
* @return Whether the item represented by @p node may be moved.
|
||||
*/
|
||||
- (BOOL)collectionNode:(ASCollectionNode *)collectionNode canMoveItemWithNode:(ASCellNode *)node;
|
||||
|
||||
/**
|
||||
* Called when the user has interactively moved an item. The data source
|
||||
* should update its internal data store to reflect the move. Note that you
|
||||
* should not call [collectionNode moveItemAtIndexPath:toIndexPath:] – the
|
||||
* collection node's internal state will be updated automatically.
|
||||
*
|
||||
* * See @p -[UICollectionViewDataSource collectionView:moveItemAtIndexPath:toIndexPath:] @c.
|
||||
*
|
||||
* @param collectionNode The sender.
|
||||
* @param sourceIndexPath The original item index path.
|
||||
* @param destinationIndexPath The new item index path.
|
||||
*/
|
||||
- (void)collectionNode:(ASCollectionNode *)collectionNode moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;
|
||||
|
||||
/**
|
||||
* Generate a unique identifier for an element in a collection. This helps state restoration persist the scroll position
|
||||
* of a collection view even when the data in that table changes. See the documentation for UIDataSourceModelAssociation for more information.
|
||||
*
|
||||
* @param indexPath The index path of the requested node.
|
||||
*
|
||||
* @param collectionNode The sender.
|
||||
*
|
||||
* @return a unique identifier for the element at the given path. Return nil if the index path does not exist in the collection.
|
||||
*/
|
||||
- (nullable NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)indexPath inNode:(ASCollectionNode *)collectionNode;
|
||||
|
||||
/**
|
||||
* Similar to -collectionView:cellForItemAtIndexPath:. See the documentation for UIDataSourceModelAssociation for more information.
|
||||
*
|
||||
* @param identifier The model identifier of the element, previously generated by a call to modelIdentifierForElementAtIndexPath
|
||||
*
|
||||
* @param collectionNode The sender.
|
||||
*
|
||||
* @return the index path to the current position of the matching element in the collection. Return nil if the element is not found.
|
||||
*/
|
||||
- (nullable NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inNode:(ASCollectionNode *)collectionNode;
|
||||
|
||||
/**
|
||||
* Similar to -collectionView:cellForItemAtIndexPath:.
|
||||
*
|
||||
* @param collectionView The sender.
|
||||
*
|
||||
* @param indexPath The index path of the requested node.
|
||||
*
|
||||
* @return a node for display at this indexpath. This will be called on the main thread and should
|
||||
* not implement reuse (it will be called once per row). Unlike UICollectionView's version,
|
||||
* this method is not called when the row is about to display.
|
||||
*/
|
||||
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead.");
|
||||
|
||||
/**
|
||||
* Similar to -collectionView:nodeForItemAtIndexPath:
|
||||
* This method takes precedence over collectionView:nodeForItemAtIndexPath: if implemented.
|
||||
*
|
||||
* @param collectionView The sender.
|
||||
*
|
||||
* @param indexPath The index path of the requested node.
|
||||
*
|
||||
* @return a block that creates the node for display at this indexpath.
|
||||
* Must be thread-safe (can be called on the main thread or a background
|
||||
* queue) and should not implement reuse (it will be called once per row).
|
||||
*/
|
||||
- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead.");
|
||||
|
||||
/**
|
||||
* Asks the collection view to provide a supplementary node to display in the collection view.
|
||||
*
|
||||
* @param collectionView An object representing the collection view requesting this information.
|
||||
* @param kind The kind of supplementary node to provide.
|
||||
* @param indexPath The index path that specifies the location of the new supplementary node.
|
||||
*/
|
||||
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead.");
|
||||
|
||||
/**
|
||||
* Indicator to lock the data source for data fetching in async mode.
|
||||
* We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistency or exception
|
||||
* due to the data access in async mode.
|
||||
*
|
||||
* @param collectionView The sender.
|
||||
* @deprecated The data source is always accessed on the main thread, and this method will not be called.
|
||||
*/
|
||||
- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Data source accesses are on the main thread. Method will not be called.");
|
||||
|
||||
/**
|
||||
* Indicator to unlock the data source for data fetching in async mode.
|
||||
* We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistency or exception
|
||||
* due to the data access in async mode.
|
||||
*
|
||||
* @param collectionView The sender.
|
||||
* @deprecated The data source is always accessed on the main thread, and this method will not be called.
|
||||
*/
|
||||
- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Data source accesses are on the main thread. Method will not be called.");
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* This is a node-based UICollectionViewDelegate.
|
||||
*/
|
||||
@protocol ASCollectionDelegate <ASCommonCollectionDelegate, NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Provides the constrained size range for measuring the given item.
|
||||
*
|
||||
* @param collectionNode The sender.
|
||||
*
|
||||
* @param indexPath The index path of the item.
|
||||
*
|
||||
* @return A constrained size range for layout for the item at this index path.
|
||||
*/
|
||||
- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode constrainedSizeForItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
- (void)collectionNode:(ASCollectionNode *)collectionNode willDisplayItemWithNode:(ASCellNode *)node;
|
||||
|
||||
- (void)collectionNode:(ASCollectionNode *)collectionNode didEndDisplayingItemWithNode:(ASCellNode *)node;
|
||||
|
||||
- (void)collectionNode:(ASCollectionNode *)collectionNode willDisplaySupplementaryElementWithNode:(ASCellNode *)node NS_AVAILABLE_IOS(8_0);
|
||||
- (void)collectionNode:(ASCollectionNode *)collectionNode didEndDisplayingSupplementaryElementWithNode:(ASCellNode *)node;
|
||||
|
||||
- (BOOL)collectionNode:(ASCollectionNode *)collectionNode shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
- (void)collectionNode:(ASCollectionNode *)collectionNode didHighlightItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
- (void)collectionNode:(ASCollectionNode *)collectionNode didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
- (BOOL)collectionNode:(ASCollectionNode *)collectionNode shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
- (BOOL)collectionNode:(ASCollectionNode *)collectionNode shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
- (void)collectionNode:(ASCollectionNode *)collectionNode didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
- (void)collectionNode:(ASCollectionNode *)collectionNode didDeselectItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
- (BOOL)collectionNode:(ASCollectionNode *)collectionNode shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
- (BOOL)collectionNode:(ASCollectionNode *)collectionNode canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath sender:(nullable id)sender;
|
||||
- (void)collectionNode:(ASCollectionNode *)collectionNode performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath sender:(nullable id)sender;
|
||||
|
||||
/**
|
||||
* Receive a message that the collection node is near the end of its data set and more data should be fetched if
|
||||
* necessary.
|
||||
*
|
||||
* @param collectionNode The sender.
|
||||
* @param context A context object that must be notified when the batch fetch is completed.
|
||||
*
|
||||
* @discussion You must eventually call -completeBatchFetching: with an argument of YES in order to receive future
|
||||
* notifications to do batch fetches. This method is called on a background queue.
|
||||
*
|
||||
* ASCollectionNode currently only supports batch events for tail loads. If you require a head load, consider
|
||||
* implementing a UIRefreshControl.
|
||||
*/
|
||||
- (void)collectionNode:(ASCollectionNode *)collectionNode willBeginBatchFetchWithContext:(ASBatchContext *)context;
|
||||
|
||||
/**
|
||||
* Tell the collection node if batch fetching should begin.
|
||||
*
|
||||
* @param collectionNode The sender.
|
||||
*
|
||||
* @discussion Use this method to conditionally fetch batches. Example use cases are: limiting the total number of
|
||||
* objects that can be fetched or no network connection.
|
||||
*
|
||||
* If not implemented, the collection node assumes that it should notify its asyncDelegate when batch fetching
|
||||
* should occur.
|
||||
*/
|
||||
- (BOOL)shouldBatchFetchForCollectionNode:(ASCollectionNode *)collectionNode;
|
||||
|
||||
/**
|
||||
* Provides the constrained size range for measuring the node at the index path.
|
||||
*
|
||||
* @param collectionView The sender.
|
||||
*
|
||||
* @param indexPath The index path of the node.
|
||||
*
|
||||
* @return A constrained size range for layout the node at this index path.
|
||||
*/
|
||||
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's constrainedSizeForItemAtIndexPath: instead. PLEASE NOTE the very subtle method name change.");
|
||||
|
||||
/**
|
||||
* Informs the delegate that the collection view will add the given node
|
||||
* at the given index path to the view hierarchy.
|
||||
*
|
||||
* @param collectionView The sender.
|
||||
* @param node The node that will be displayed.
|
||||
* @param indexPath The index path of the item that will be displayed.
|
||||
*
|
||||
* @warning AsyncDisplayKit processes collection view edits asynchronously. The index path
|
||||
* passed into this method may not correspond to the same item in your data source
|
||||
* if your data source has been updated since the last edit was processed.
|
||||
*/
|
||||
- (void)collectionView:(ASCollectionView *)collectionView willDisplayNode:(ASCellNode *)node forItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead.");
|
||||
|
||||
/**
|
||||
* Informs the delegate that the collection view did remove the provided node from the view hierarchy.
|
||||
* This may be caused by the node scrolling out of view, or by deleting the item
|
||||
* or its containing section with @c deleteItemsAtIndexPaths: or @c deleteSections: .
|
||||
*
|
||||
* @param collectionView The sender.
|
||||
* @param node The node which was removed from the view hierarchy.
|
||||
* @param indexPath The index path at which the node was located before it was removed.
|
||||
*
|
||||
* @warning AsyncDisplayKit processes collection view edits asynchronously. The index path
|
||||
* passed into this method may not correspond to the same item in your data source
|
||||
* if your data source has been updated since the last edit was processed.
|
||||
*/
|
||||
- (void)collectionView:(ASCollectionView *)collectionView didEndDisplayingNode:(ASCellNode *)node forItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead.");
|
||||
|
||||
- (void)collectionView:(ASCollectionView *)collectionView willBeginBatchFetchWithContext:(ASBatchContext *)context ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead.");
|
||||
|
||||
/**
|
||||
* Tell the collectionView if batch fetching should begin.
|
||||
*
|
||||
* @param collectionView The sender.
|
||||
*
|
||||
* @discussion Use this method to conditionally fetch batches. Example use cases are: limiting the total number of
|
||||
* objects that can be fetched or no network connection.
|
||||
*
|
||||
* If not implemented, the collectionView assumes that it should notify its asyncDelegate when batch fetching
|
||||
* should occur.
|
||||
*/
|
||||
- (BOOL)shouldBatchFetchForCollectionView:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead.");
|
||||
|
||||
/**
|
||||
* Informs the delegate that the collection view will add the node
|
||||
* at the given index path to the view hierarchy.
|
||||
*
|
||||
* @param collectionView The sender.
|
||||
* @param indexPath The index path of the item that will be displayed.
|
||||
*
|
||||
* @warning AsyncDisplayKit processes collection view edits asynchronously. The index path
|
||||
* passed into this method may not correspond to the same item in your data source
|
||||
* if your data source has been updated since the last edit was processed.
|
||||
*
|
||||
* This method is deprecated. Use @c collectionView:willDisplayNode:forItemAtIndexPath: instead.
|
||||
*/
|
||||
- (void)collectionView:(ASCollectionView *)collectionView willDisplayNodeForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead.");
|
||||
|
||||
@end
|
||||
|
||||
@protocol ASCollectionDataSourceInterop <ASCollectionDataSource>
|
||||
|
||||
/**
|
||||
* This method offers compatibility with synchronous, standard UICollectionViewCell objects.
|
||||
* These cells will **not** have the performance benefits of ASCellNodes (like preloading, async layout, and
|
||||
* async drawing) - even when mixed within the same ASCollectionNode.
|
||||
*
|
||||
* In order to use this method, you must:
|
||||
* 1. Implement it on your ASCollectionDataSource object.
|
||||
* 2. Call registerCellClass: on the collectionNode.view (in viewDidLoad, or register an onDidLoad: block).
|
||||
* 3. Return nil from the nodeBlockForItem...: or nodeForItem...: method. NOTE: it is an error to return
|
||||
* nil from within a nodeBlock, if you have returned a nodeBlock object.
|
||||
* 4. Lastly, you must implement a method to provide the size for the cell. There are two ways this is done:
|
||||
* 4a. UICollectionViewFlowLayout (incl. ASPagerNode). Implement
|
||||
collectionNode:constrainedSizeForItemAtIndexPath:.
|
||||
* 4b. Custom collection layouts. Set .layoutInspector and have it implement
|
||||
collectionView:constrainedSizeForNodeAtIndexPath:.
|
||||
*
|
||||
* For an example of using this method with all steps above (including a custom layout, 4b.),
|
||||
* see the app in examples/CustomCollectionView and enable kShowUICollectionViewCells = YES.
|
||||
*/
|
||||
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
@optional
|
||||
|
||||
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
* Implement this property and return YES if you want your interop data source to be
|
||||
* used when dequeuing cells for node-backed items.
|
||||
*
|
||||
* If NO (the default), the interop data source will only be consulted in cases
|
||||
* where no ASCellNode was provided to AsyncDisplayKit.
|
||||
*
|
||||
* If YES, the interop data source will always be consulted to dequeue cells, and
|
||||
* will be expected to return _ASCollectionViewCells in cases where a node was provided.
|
||||
*
|
||||
* The default value is NO.
|
||||
*/
|
||||
@property (class, nonatomic, readonly) BOOL dequeuesCellsForNodeBackedItems;
|
||||
|
||||
@end
|
||||
|
||||
@protocol ASCollectionDelegateInterop <ASCollectionDelegate>
|
||||
|
||||
@optional
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
@ -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 <AsyncDisplayKit/ASCollectionView.h>
|
||||
|
||||
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<ASCollectionDelegate> 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<ASCollectionDataSource> 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<ASCollectionViewLayoutInspecting> 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<NSIndexPath *> *indexPathsForVisibleItems;
|
||||
|
||||
@property (nonatomic, readonly, nullable) NSArray<NSIndexPath *> *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<NSIndexPath *> *)indexPaths;
|
||||
|
||||
/**
|
||||
* Deletes the items specified by an array of index paths.
|
||||
*
|
||||
* @param indexPaths An array of NSIndexPath objects identifying the items to delete.
|
||||
*
|
||||
* @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes
|
||||
* before this method is called.
|
||||
*/
|
||||
- (void)deleteItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
|
||||
|
||||
/**
|
||||
* Reloads the specified items.
|
||||
*
|
||||
* @param indexPaths An array of NSIndexPath objects identifying the items to reload.
|
||||
*
|
||||
* @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes
|
||||
* before this method is called.
|
||||
*/
|
||||
- (void)reloadItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths;
|
||||
|
||||
/**
|
||||
* Moves the item at a specified location to a destination location.
|
||||
*
|
||||
* @param indexPath The index path identifying the item to move.
|
||||
*
|
||||
* @param newIndexPath The index path that is the destination of the move for the item.
|
||||
*
|
||||
* @discussion This method must be called from the main thread. The 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
|
@ -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 <UIKit/UIKit.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASCollectionViewProtocols.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
#import <AsyncDisplayKit/ASBatchContext.h>
|
||||
#import <AsyncDisplayKit/ASDimension.h>
|
||||
#import <AsyncDisplayKit/ASLayoutRangeType.h>
|
||||
#import <AsyncDisplayKit/ASScrollDirection.h>
|
||||
|
||||
@class ASCellNode;
|
||||
@class ASCollectionNode;
|
||||
@protocol ASCollectionDataSource;
|
||||
@protocol ASCollectionDelegate;
|
||||
@protocol ASCollectionViewLayoutInspecting;
|
||||
@protocol ASSectionContext;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* Asynchronous UICollectionView with Intelligent Preloading capabilities.
|
||||
*
|
||||
* @note ASCollectionNode is strongly recommended over ASCollectionView. This class exists for adoption convenience.
|
||||
*/
|
||||
@interface ASCollectionView : UICollectionView
|
||||
|
||||
/**
|
||||
* Returns the corresponding ASCollectionNode
|
||||
*
|
||||
* @return collectionNode The corresponding ASCollectionNode, if one exists.
|
||||
*/
|
||||
@property (nonatomic, weak, readonly) ASCollectionNode *collectionNode;
|
||||
|
||||
/**
|
||||
* Retrieves the node for the item at the given index path.
|
||||
*
|
||||
* @param indexPath The index path of the requested node.
|
||||
* @return The node at the given index path, or @c nil if no item exists at the specified path.
|
||||
*/
|
||||
- (nullable ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Similar to -indexPathForCell:.
|
||||
*
|
||||
* @param cellNode a cellNode in the collection view
|
||||
*
|
||||
* @return The index path for this cell node.
|
||||
*
|
||||
* @discussion This index path returned by this method is in the _view's_ index space
|
||||
* and should only be used with @c ASCollectionView directly. To get an index path suitable
|
||||
* for use with your data source and @c ASCollectionNode, call @c indexPathForNode: on the
|
||||
* collection node instead.
|
||||
*/
|
||||
- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Similar to -supplementaryViewForElementKind:atIndexPath:
|
||||
*
|
||||
* @param elementKind The kind of supplementary node to locate.
|
||||
* @param indexPath The index path of the requested supplementary node.
|
||||
*
|
||||
* @return The specified supplementary node or @c nil.
|
||||
*/
|
||||
- (nullable ASCellNode *)supplementaryNodeForElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Retrieves the context object for the given section, as provided by the data source in
|
||||
* the @c collectionNode:contextForSection: method. This method must be called on the main thread.
|
||||
*
|
||||
* @param section The section to get the context for.
|
||||
*
|
||||
* @return The context object, or @c nil if no context was provided.
|
||||
*/
|
||||
- (nullable id<ASSectionContext>)contextForSection:(NSInteger)section AS_WARN_UNUSED_RESULT;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASCollectionView (Deprecated)
|
||||
|
||||
/*
|
||||
* A Boolean value that determines whether the nodes that the data source renders will be flipped.
|
||||
*/
|
||||
@property (nonatomic) BOOL inverted ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
|
||||
|
||||
/**
|
||||
* The number of screens left to scroll before the delegate -collectionView:beginBatchFetchingWithContext: is called.
|
||||
*
|
||||
* Defaults to two screenfuls.
|
||||
*/
|
||||
@property (nonatomic) CGFloat leadingScreensForBatching ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
|
||||
|
||||
/**
|
||||
* Optional introspection object for the collection view's layout.
|
||||
*
|
||||
* @discussion Since supplementary and decoration views are controlled by the collection view's layout, this object
|
||||
* is used as a bridge to provide information to the internal data controller about the existence of these views and
|
||||
* their associated index paths. For collection views using `UICollectionViewFlowLayout`, a default inspector
|
||||
* implementation `ASCollectionViewFlowLayoutInspector` is created and set on this property by default. Custom
|
||||
* collection view layout subclasses will need to provide their own implementation of an inspector object for their
|
||||
* supplementary views to be compatible with `ASCollectionView`'s supplementary node support.
|
||||
*/
|
||||
@property (nonatomic, weak) id<ASCollectionViewLayoutInspecting> layoutInspector ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
|
||||
|
||||
/**
|
||||
* Determines collection view's current scroll direction. Supports 2-axis collection views.
|
||||
*
|
||||
* @return a bitmask of ASScrollDirection values.
|
||||
*/
|
||||
@property (nonatomic, readonly) ASScrollDirection scrollDirection ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
|
||||
|
||||
/**
|
||||
* Determines collection view's scrollable directions.
|
||||
*
|
||||
* @return a bitmask of ASScrollDirection values.
|
||||
*/
|
||||
@property (nonatomic, readonly) ASScrollDirection scrollableDirections ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
|
||||
|
||||
/**
|
||||
* Forces the .contentInset to be UIEdgeInsetsZero.
|
||||
*
|
||||
* @discussion By default, UIKit sets the top inset to the navigation bar height, even for horizontally
|
||||
* scrolling views. This can only be disabled by setting a property on the containing UIViewController,
|
||||
* automaticallyAdjustsScrollViewInsets, which may not be accessible. ASPagerNode uses this to ensure
|
||||
* its flow layout behaves predictably and does not log undefined layout warnings.
|
||||
*/
|
||||
@property (nonatomic) BOOL zeroContentInsets ASDISPLAYNODE_DEPRECATED_MSG("Set automaticallyAdjustsScrollViewInsets=NO on your view controller instead.");
|
||||
|
||||
/**
|
||||
* The distance that the content view is inset from the collection view edges. Defaults to UIEdgeInsetsZero.
|
||||
*/
|
||||
@property (nonatomic) UIEdgeInsets contentInset ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead");
|
||||
|
||||
/**
|
||||
* The point at which the origin of the content view is offset from the origin of the collection view.
|
||||
*/
|
||||
@property (nonatomic) CGPoint contentOffset ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
|
||||
|
||||
/**
|
||||
* The object that acts as the asynchronous delegate of the collection view
|
||||
*
|
||||
* @discussion The delegate must adopt the ASCollectionDelegate protocol. The collection view maintains a weak reference to the delegate object.
|
||||
*
|
||||
* The delegate object is responsible for providing size constraints for nodes and indicating whether batch fetching should begin.
|
||||
*/
|
||||
@property (nonatomic, weak) id<ASCollectionDelegate> asyncDelegate ASDISPLAYNODE_DEPRECATED_MSG("Please use ASCollectionNode's .delegate property instead.");
|
||||
|
||||
/**
|
||||
* The object that acts as the asynchronous data source of the collection view
|
||||
*
|
||||
* @discussion The datasource must adopt the ASCollectionDataSource protocol. The collection view maintains a weak reference to the datasource object.
|
||||
*
|
||||
* The datasource object is responsible for providing nodes or node creation blocks to the collection view.
|
||||
*/
|
||||
@property (nonatomic, weak) id<ASCollectionDataSource> asyncDataSource ASDISPLAYNODE_DEPRECATED_MSG("Please use ASCollectionNode's .dataSource property instead.");
|
||||
|
||||
/**
|
||||
* Initializes an ASCollectionView
|
||||
*
|
||||
* @discussion Initializes and returns a newly allocated collection view object with the specified layout.
|
||||
*
|
||||
* @param layout The layout object to use for organizing items. The collection view stores a strong reference to the specified object. Must not be nil.
|
||||
*/
|
||||
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout ASDISPLAYNODE_DEPRECATED_MSG("Please use ASCollectionNode instead of ASCollectionView.");
|
||||
|
||||
/**
|
||||
* Initializes an ASCollectionView
|
||||
*
|
||||
* @discussion Initializes and returns a newly allocated collection view object with the specified frame and layout.
|
||||
*
|
||||
* @param frame The frame rectangle for the collection view, measured in points. The origin of the frame is relative to the superview in which you plan to add it. This frame is passed to the superclass during initialization.
|
||||
* @param layout The layout object to use for organizing items. The collection view stores a strong reference to the specified object. Must not be nil.
|
||||
*/
|
||||
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout ASDISPLAYNODE_DEPRECATED_MSG("Please use ASCollectionNode instead of ASCollectionView.");
|
||||
|
||||
/**
|
||||
* Tuning parameters for a range type in full mode.
|
||||
*
|
||||
* @param rangeType The range type to get the tuning parameters for.
|
||||
*
|
||||
* @return A tuning parameter value for the given range type in full mode.
|
||||
*
|
||||
* @see ASLayoutRangeMode
|
||||
* @see ASLayoutRangeType
|
||||
*/
|
||||
- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* Set the tuning parameters for a range type in full mode.
|
||||
*
|
||||
* @param tuningParameters The tuning parameters to store for a range type.
|
||||
* @param rangeType The range type to set the tuning parameters for.
|
||||
*
|
||||
* @see ASLayoutRangeMode
|
||||
* @see ASLayoutRangeType
|
||||
*/
|
||||
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* Tuning parameters for a range type in the specified mode.
|
||||
*
|
||||
* @param rangeMode The range mode to get the running parameters for.
|
||||
* @param rangeType The range type to get the tuning parameters for.
|
||||
*
|
||||
* @return A tuning parameter value for the given range type in the given mode.
|
||||
*
|
||||
* @see ASLayoutRangeMode
|
||||
* @see ASLayoutRangeType
|
||||
*/
|
||||
- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* Set the tuning parameters for a range type in the specified mode.
|
||||
*
|
||||
* @param tuningParameters The tuning parameters to store for a range type.
|
||||
* @param rangeMode The range mode to set the running parameters for.
|
||||
* @param rangeType The range type to set the tuning parameters for.
|
||||
*
|
||||
* @see ASLayoutRangeMode
|
||||
* @see ASLayoutRangeType
|
||||
*/
|
||||
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
- (nullable __kindof UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
- (void)selectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
@property (nonatomic, copy, readonly) NSArray<NSIndexPath *> *indexPathsForVisibleItems ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
|
||||
|
||||
@property (nullable, nonatomic, copy, readonly) NSArray<NSIndexPath *> *indexPathsForSelectedItems ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead.");
|
||||
|
||||
/**
|
||||
* Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread.
|
||||
* The asyncDataSource must be updated to reflect the changes before the update block completes.
|
||||
*
|
||||
* @param animated NO to disable animations for this batch
|
||||
* @param updates The block that performs the relevant insert, delete, reload, or move operations.
|
||||
* @param completion A completion handler block to execute when all of the operations are finished. This block takes a single
|
||||
* Boolean parameter that contains the value YES if all of the related animations completed successfully or
|
||||
* NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread.
|
||||
*/
|
||||
- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* Perform a batch of updates asynchronously. This method must be called from the main thread.
|
||||
* The asyncDataSource must be updated to reflect the changes before update block completes.
|
||||
*
|
||||
* @param updates The block that performs the relevant insert, delete, reload, or move operations.
|
||||
* @param completion A completion handler block to execute when all of the operations are finished. This block takes a single
|
||||
* Boolean parameter that contains the value YES if all of the related animations completed successfully or
|
||||
* NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread.
|
||||
*/
|
||||
- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* Reload everything from scratch, destroying the working range and all cached nodes.
|
||||
*
|
||||
* @param completion block to run on completion of asynchronous loading or nil. If supplied, the block is run on
|
||||
* the main thread.
|
||||
* @warning This method is substantially more expensive than UICollectionView's version.
|
||||
*/
|
||||
- (void)reloadDataWithCompletion:(nullable void (^)(void))completion AS_UNAVAILABLE("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* Reload everything from scratch, destroying the working range and all cached nodes.
|
||||
*
|
||||
* @warning This method is substantially more expensive than UICollectionView's version.
|
||||
*/
|
||||
- (void)reloadData AS_UNAVAILABLE("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* Triggers a relayout of all nodes.
|
||||
*
|
||||
* @discussion This method invalidates and lays out every cell node in the collection.
|
||||
*/
|
||||
- (void)relayoutItems ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* See ASCollectionNode.h for full documentation of these methods.
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL isProcessingUpdates;
|
||||
- (void)onDidFinishProcessingUpdates:(void (^)(void))completion;
|
||||
- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use -[ASCollectionNode waitUntilAllUpdatesAreProcessed] instead.");
|
||||
|
||||
/**
|
||||
* See ASCollectionNode.h for full documentation of these methods.
|
||||
*/
|
||||
@property (nonatomic, readonly, getter=isSynchronized) BOOL synchronized;
|
||||
- (void)onDidFinishSynchronizing:(void (^)(void))completion;
|
||||
|
||||
/**
|
||||
* Registers the given kind of supplementary node for use in creating node-backed supplementary views.
|
||||
*
|
||||
* @param elementKind The kind of supplementary node that will be requested through the data source.
|
||||
*
|
||||
* @discussion Use this method to register support for the use of supplementary nodes in place of the default
|
||||
* `registerClass:forSupplementaryViewOfKind:withReuseIdentifier:` and `registerNib:forSupplementaryViewOfKind:withReuseIdentifier:`
|
||||
* methods. This method will register an internal backing view that will host the contents of the supplementary nodes
|
||||
* returned from the data source.
|
||||
*/
|
||||
- (void)registerSupplementaryNodeOfKind:(NSString *)elementKind ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* Inserts one or more sections.
|
||||
*
|
||||
* @param sections An index set that specifies the sections to insert.
|
||||
*
|
||||
* @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes
|
||||
* before this method is called.
|
||||
*/
|
||||
- (void)insertSections:(NSIndexSet *)sections ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* Deletes one or more sections.
|
||||
*
|
||||
* @param sections An index set that specifies the sections to delete.
|
||||
*
|
||||
* @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes
|
||||
* before this method is called.
|
||||
*/
|
||||
- (void)deleteSections:(NSIndexSet *)sections ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* Reloads the specified sections.
|
||||
*
|
||||
* @param sections An index set that specifies the sections to reload.
|
||||
*
|
||||
* @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes
|
||||
* before this method is called.
|
||||
*/
|
||||
- (void)reloadSections:(NSIndexSet *)sections ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* Moves a section to a new location.
|
||||
*
|
||||
* @param section The index of the section to move.
|
||||
*
|
||||
* @param newSection The index that is the destination of the move for the section.
|
||||
*
|
||||
* @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes
|
||||
* before this method is called.
|
||||
*/
|
||||
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* Inserts items at the locations identified by an array of index paths.
|
||||
*
|
||||
* @param indexPaths An array of NSIndexPath objects, each representing an item index and section index that together identify an item.
|
||||
*
|
||||
* @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes
|
||||
* before this method is called.
|
||||
*/
|
||||
- (void)insertItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* Deletes the items specified by an array of index paths.
|
||||
*
|
||||
* @param indexPaths An array of NSIndexPath objects identifying the items to delete.
|
||||
*
|
||||
* @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes
|
||||
* before this method is called.
|
||||
*/
|
||||
- (void)deleteItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* Reloads the specified items.
|
||||
*
|
||||
* @param indexPaths An array of NSIndexPath objects identifying the items to reload.
|
||||
*
|
||||
* @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes
|
||||
* before this method is called.
|
||||
*/
|
||||
- (void)reloadItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* Moves the item at a specified location to a destination location.
|
||||
*
|
||||
* @param indexPath The index path identifying the item to move.
|
||||
*
|
||||
* @param newIndexPath The index path that is the destination of the move for the item.
|
||||
*
|
||||
* @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes
|
||||
* before this method is called.
|
||||
*/
|
||||
- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
/**
|
||||
* Query the sized node at @c indexPath for its calculatedSize.
|
||||
*
|
||||
* @param indexPath The index path for the node of interest.
|
||||
*
|
||||
* This method is deprecated. Call @c calculatedSize on the node of interest instead. First deprecated in version 2.0.
|
||||
*/
|
||||
- (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Call -calculatedSize on the node of interest instead.");
|
||||
|
||||
/**
|
||||
* Similar to -visibleCells.
|
||||
*
|
||||
* @return an array containing the nodes being displayed on screen.
|
||||
*/
|
||||
- (NSArray<__kindof ASCellNode *> *)visibleNodes AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead.");
|
||||
|
||||
@end
|
||||
|
||||
ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDataSource.")
|
||||
@protocol ASCollectionViewDataSource <ASCollectionDataSource>
|
||||
@end
|
||||
|
||||
ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDelegate.")
|
||||
@protocol ASCollectionViewDelegate <ASCollectionDelegate>
|
||||
@end
|
||||
|
||||
/**
|
||||
* Defines methods that let you coordinate a `UICollectionViewFlowLayout` in combination with an `ASCollectionNode`.
|
||||
*/
|
||||
@protocol ASCollectionDelegateFlowLayout <ASCollectionDelegate>
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Asks the delegate for the inset that should be applied to the given section.
|
||||
*
|
||||
* @see the same method in UICollectionViewDelegate.
|
||||
*/
|
||||
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
|
||||
|
||||
/**
|
||||
* Asks the delegate for the size range that should be used to measure the header in the given flow layout section.
|
||||
*
|
||||
* @param collectionNode The sender.
|
||||
* @param section The section.
|
||||
*
|
||||
* @return The size range for the header, or @c ASSizeRangeZero if there is no header in this section.
|
||||
*
|
||||
* If you want the header to completely determine its own size, return @c ASSizeRangeUnconstrained.
|
||||
*
|
||||
* @note Only the scrollable dimension of the returned size range will be used. In a vertical flow,
|
||||
* only the height will be used. In a horizontal flow, only the width will be used. The other dimension
|
||||
* will be constrained to fill the collection node.
|
||||
*
|
||||
* @discussion If you do not implement this method, ASDK will fall back to calling @c collectionView:layout:referenceSizeForHeaderInSection:
|
||||
* and using that as the exact constrained size. If you don't implement that method, ASDK will read the @c headerReferenceSize from the layout.
|
||||
*/
|
||||
- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForHeaderInSection:(NSInteger)section;
|
||||
|
||||
/**
|
||||
* Asks the delegate for the size range that should be used to measure the footer in the given flow layout section.
|
||||
*
|
||||
* @param collectionNode The sender.
|
||||
* @param section The section.
|
||||
*
|
||||
* @return The size range for the footer, or @c ASSizeRangeZero if there is no footer in this section.
|
||||
*
|
||||
* If you want the footer to completely determine its own size, return @c ASSizeRangeUnconstrained.
|
||||
*
|
||||
* @note Only the scrollable dimension of the returned size range will be used. In a vertical flow,
|
||||
* only the height will be used. In a horizontal flow, only the width will be used. The other dimension
|
||||
* will be constrained to fill the collection node.
|
||||
*
|
||||
* @discussion If you do not implement this method, ASDK will fall back to calling @c collectionView:layout:referenceSizeForFooterInSection:
|
||||
* and using that as the exact constrained size. If you don't implement that method, ASDK will read the @c footerReferenceSize from the layout.
|
||||
*/
|
||||
- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForFooterInSection:(NSInteger)section;
|
||||
|
||||
/**
|
||||
* Asks the delegate for the size of the header in the specified section.
|
||||
*/
|
||||
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:sizeRangeForHeaderInSection: instead.");
|
||||
|
||||
/**
|
||||
* Asks the delegate for the size of the footer in the specified section.
|
||||
*/
|
||||
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:sizeRangeForFooterInSection: instead.");
|
||||
|
||||
@end
|
||||
|
||||
ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDelegateFlowLayout.")
|
||||
@protocol ASCollectionViewDelegateFlowLayout <ASCollectionDelegateFlowLayout>
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
@ -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 <AsyncDisplayKit/ASCollectionViewLayoutInspector.h>
|
||||
|
||||
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 <ASCollectionViewLayoutInspecting>
|
||||
|
||||
@property (nonatomic, weak, readonly) UICollectionViewFlowLayout *layout;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
@ -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 <AsyncDisplayKit/ASCollectionViewFlowLayoutInspector.h>
|
||||
#import <AsyncDisplayKit/ASCollectionView.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASEqualityHelpers.h>
|
||||
#import <AsyncDisplayKit/ASCollectionView+Undeprecated.h>
|
||||
#import <AsyncDisplayKit/ASCollectionNode.h>
|
||||
|
||||
#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<ASCollectionDelegate>)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<ASCollectionDelegateFlowLayout>)delegateForCollectionView:(ASCollectionView *)collectionView
|
||||
{
|
||||
return (id<ASCollectionDelegateFlowLayout>)collectionView.asyncDelegate;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
@ -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 <AsyncDisplayKit/ASAbstractLayoutController.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ASCollectionView;
|
||||
|
||||
AS_SUBCLASSING_RESTRICTED
|
||||
@interface ASCollectionViewLayoutController : ASAbstractLayoutController
|
||||
|
||||
- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
@ -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 <AsyncDisplayKit/ASCollectionViewLayoutController.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASCollectionView+Undeprecated.h>
|
||||
#import <AsyncDisplayKit/ASElementMap.h>
|
||||
#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
|
||||
#import <AsyncDisplayKit/UICollectionViewLayout+ASConvenience.h>
|
||||
|
||||
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<ASCollectionElement *> *)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<ASCollectionElement *> *__autoreleasing _Nullable *)displaySet preloadSet:(NSHashTable<ASCollectionElement *> *__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<ASCollectionElement *> alloc] initWithOptions:NSHashTableObjectPointerPersonality capacity:count];
|
||||
__auto_type preload = [[NSHashTable<ASCollectionElement *> 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<ASCollectionElement *> *)elementsWithinRangeBounds:(CGRect)rangeBounds map:(ASElementMap *)map
|
||||
{
|
||||
NSArray *layoutAttributes = [_collectionViewLayout layoutAttributesForElementsInRect:rangeBounds];
|
||||
NSHashTable<ASCollectionElement *> *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
|
@ -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 <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* This facilitator protocol is intended to help Layout to better
|
||||
* gel with the CollectionView
|
||||
*/
|
||||
@protocol ASCollectionViewLayoutFacilitatorProtocol <NSObject>
|
||||
|
||||
/**
|
||||
* Inform that the collectionView is editing the cells at a list of indexPaths
|
||||
*
|
||||
* @param indexPaths an array of NSIndexPath objects of cells being/will be edited.
|
||||
* @param isBatched indicates whether the editing operation will be batched by the collectionView
|
||||
*
|
||||
* NOTE: when isBatched, used in combination with -collectionViewWillPerformBatchUpdates
|
||||
*/
|
||||
- (void)collectionViewWillEditCellsAtIndexPaths:(NSArray *)indexPaths batched:(BOOL)isBatched;
|
||||
|
||||
/**
|
||||
* Inform that the collectionView is editing the sections at a set of indexes
|
||||
*
|
||||
* @param indexes an NSIndexSet of section indexes being/will be edited.
|
||||
* @param batched indicates whether the editing operation will be batched by the collectionView
|
||||
*
|
||||
* NOTE: when batched, used in combination with -collectionViewWillPerformBatchUpdates
|
||||
*/
|
||||
- (void)collectionViewWillEditSectionsAtIndexSet:(NSIndexSet *)indexes batched:(BOOL)batched;
|
||||
|
||||
/**
|
||||
* Informs the delegate that the collectionView is about to call performBatchUpdates
|
||||
*/
|
||||
- (void)collectionViewWillPerformBatchUpdates;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
@ -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 <Foundation/Foundation.h>
|
||||
#import <AsyncDisplayKit/ASDimension.h>
|
||||
#import <AsyncDisplayKit/ASScrollDirection.h>
|
||||
|
||||
@class ASCollectionView;
|
||||
@protocol ASCollectionDataSource;
|
||||
@protocol ASCollectionDelegate;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
AS_EXTERN ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView *collectionView);
|
||||
|
||||
@protocol ASCollectionViewLayoutInspecting <NSObject>
|
||||
|
||||
/**
|
||||
* 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<ASCollectionDelegate>)delegate;
|
||||
|
||||
/**
|
||||
* Allow the inspector to respond to dataSource changes.
|
||||
*
|
||||
* @discussion A great time to update perform selector caches!
|
||||
*/
|
||||
- (void)didChangeCollectionViewDataSource:(nullable id<ASCollectionDataSource>)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 <ASCollectionViewLayoutInspecting>
|
||||
@end
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
@ -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 <AsyncDisplayKit/ASCollectionViewLayoutInspector.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASCollectionView.h>
|
||||
#import <AsyncDisplayKit/ASCollectionView+Undeprecated.h>
|
||||
#import <AsyncDisplayKit/ASCollectionNode.h>
|
||||
|
||||
#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<ASCollectionDelegate>)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
|
@ -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 <UIKit/UIKit.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
|
||||
typedef NS_OPTIONS(NSUInteger, ASCellLayoutMode) {
|
||||
/**
|
||||
* No options set. If cell layout mode is set to ASCellLayoutModeNone, the default values for
|
||||
* each flag listed below is used.
|
||||
*/
|
||||
ASCellLayoutModeNone = 0,
|
||||
/**
|
||||
* If ASCellLayoutModeAlwaysSync is enabled it will cause the ASDataController to wait on the
|
||||
* background queue, and this ensures that any new / changed cells are in the hierarchy by the
|
||||
* very next CATransaction / frame draw.
|
||||
*
|
||||
* Note: Sync & Async flags force the behavior to be always one or the other, regardless of the
|
||||
* items. Default: If neither ASCellLayoutModeAlwaysSync or ASCellLayoutModeAlwaysAsync is set,
|
||||
* default behavior is synchronous when there are 0 or 1 ASCellNodes in the data source, and
|
||||
* asynchronous when there are 2 or more.
|
||||
*/
|
||||
ASCellLayoutModeAlwaysSync = 1 << 1, // Default OFF
|
||||
ASCellLayoutModeAlwaysAsync = 1 << 2, // Default OFF
|
||||
ASCellLayoutModeForceIfNeeded = 1 << 3, // Deprecated, default OFF.
|
||||
ASCellLayoutModeAlwaysPassthroughDelegate = 1 << 4, // Deprecated, default ON.
|
||||
/** Instead of using performBatchUpdates: prefer using reloadData for changes for collection view */
|
||||
ASCellLayoutModeAlwaysReloadData = 1 << 5, // Default OFF
|
||||
/** If flag is enabled nodes are *not* gonna be range managed. */
|
||||
ASCellLayoutModeDisableRangeController = 1 << 6, // Default OFF
|
||||
ASCellLayoutModeAlwaysLazy = 1 << 7, // Deprecated, default OFF.
|
||||
/**
|
||||
* Defines if the node creation should happen serialized and not in parallel within the
|
||||
* data controller
|
||||
*/
|
||||
ASCellLayoutModeSerializeNodeCreation = 1 << 8, // Default OFF
|
||||
/**
|
||||
* When set, the performBatchUpdates: API (including animation) is used when handling Section
|
||||
* Reload operations. This is useful only when ASCellLayoutModeAlwaysReloadData is enabled and
|
||||
* cell height animations are desired.
|
||||
*/
|
||||
ASCellLayoutModeAlwaysBatchUpdateSectionReload = 1 << 9, // Default OFF
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* This is a subset of UICollectionViewDataSource.
|
||||
*
|
||||
* @see ASCollectionDataSource
|
||||
*/
|
||||
@protocol ASCommonCollectionDataSource <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("Implement -collectionNode:numberOfItemsInSection: instead.");
|
||||
|
||||
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Implement -numberOfSectionsInCollectionNode: instead.");
|
||||
|
||||
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement - collectionNode:nodeForSupplementaryElementOfKind:atIndexPath: instead.");
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
* This is a subset of UICollectionViewDelegate.
|
||||
*
|
||||
* @see ASCollectionDelegate
|
||||
*/
|
||||
@protocol ASCommonCollectionDelegate <NSObject, UIScrollViewDelegate>
|
||||
|
||||
@optional
|
||||
|
||||
- (UICollectionViewTransitionLayout *)collectionView:(UICollectionView *)collectionView transitionLayoutForOldLayout:(UICollectionViewLayout *)fromLayout newLayout:(UICollectionViewLayout *)toLayout;
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -collectionNode:willDisplaySupplementaryView:forElementKind:atIndexPath: instead.");
|
||||
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -collectionNode:didEndDisplayingSupplementaryView:forElementKind:atIndexPath: instead.");
|
||||
|
||||
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:shouldHighlightItemAtIndexPath: instead.");
|
||||
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:didHighlightItemAtIndexPath: instead.");
|
||||
- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:didUnhighlightItemAtIndexPath: instead.");
|
||||
|
||||
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:shouldSelectItemAtIndexPath: instead.");
|
||||
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:didSelectItemAtIndexPath: instead.");
|
||||
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:shouldDeselectItemAtIndexPath: instead.");
|
||||
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:didDeselectItemAtIndexPath: instead.");
|
||||
|
||||
- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:shouldShowMenuForItemAtIndexPath: instead.");
|
||||
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:canPerformAction:forItemAtIndexPath:withSender: instead.");
|
||||
- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:performAction:forItemAtIndexPath:withSender: instead.");
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
@ -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 <AsyncDisplayKit/ASControlNode.h>
|
||||
|
||||
@interface ASControlNode (Private)
|
||||
|
||||
#if TARGET_OS_TV
|
||||
- (void)_pressDown;
|
||||
#endif
|
||||
|
||||
@end
|
@ -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 <AsyncDisplayKit/ASControlNode.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* The subclass header _ASControlNode+Subclasses_ defines methods to be
|
||||
* overridden by custom nodes that subclass ASControlNode.
|
||||
*
|
||||
* These methods should never be called directly by other classes.
|
||||
*/
|
||||
|
||||
@interface ASControlNode (Subclassing)
|
||||
|
||||
/**
|
||||
@abstract Sends action messages for the given control events.
|
||||
@param controlEvents A bitmask whose set flags specify the control events for which action messages are sent. See "Control Events" in ASControlNode.h for bitmask constants.
|
||||
@param touchEvent An event object encapsulating the information specific to the user event.
|
||||
@discussion ASControlNode implements this method to send all action messages associated with controlEvents. The list of targets is constructed from prior invocations of addTarget:action:forControlEvents:.
|
||||
*/
|
||||
- (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(nullable UIEvent *)touchEvent;
|
||||
|
||||
/**
|
||||
@abstract Sent to the control when tracking begins.
|
||||
@param touch The touch on the receiving control.
|
||||
@param touchEvent An event object encapsulating the information specific to the user event.
|
||||
@result YES if the receiver should respond continuously (respond when touch is dragged); NO otherwise.
|
||||
*/
|
||||
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)touchEvent;
|
||||
|
||||
/**
|
||||
@abstract Sent continuously to the control as it tracks a touch within the control's bounds.
|
||||
@param touch The touch on the receiving control.
|
||||
@param touchEvent An event object encapsulating the information specific to the user event.
|
||||
@result YES if touch tracking should continue; NO otherwise.
|
||||
*/
|
||||
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)touchEvent;
|
||||
|
||||
/**
|
||||
@abstract Sent to the control when tracking should be cancelled.
|
||||
@param touchEvent An event object encapsulating the information specific to the user event. This parameter may be nil, indicating that the cancelation was caused by something other than an event, such as the display node being removed from its supernode.
|
||||
*/
|
||||
- (void)cancelTrackingWithEvent:(nullable UIEvent *)touchEvent;
|
||||
|
||||
/**
|
||||
@abstract Sent to the control when the last touch completely ends, telling it to stop tracking.
|
||||
@param touch The touch that ended.
|
||||
@param touchEvent An event object encapsulating the information specific to the user event.
|
||||
*/
|
||||
- (void)endTrackingWithTouch:(nullable UITouch *)touch withEvent:(nullable UIEvent *)touchEvent;
|
||||
|
||||
/**
|
||||
@abstract Settable version of highlighted property.
|
||||
*/
|
||||
@property (getter=isHighlighted) BOOL highlighted;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -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 <AsyncDisplayKit/ASDisplayNode.h>
|
||||
|
||||
#pragma once
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
@abstract Kinds of events possible for control nodes.
|
||||
@discussion These events are identical to their UIControl counterparts.
|
||||
*/
|
||||
typedef NS_OPTIONS(NSUInteger, ASControlNodeEvent)
|
||||
{
|
||||
/** A touch-down event in the control node. */
|
||||
ASControlNodeEventTouchDown = 1 << 0,
|
||||
/** A repeated touch-down event in the control node; for this event the value of the UITouch tapCount method is greater than one. */
|
||||
ASControlNodeEventTouchDownRepeat = 1 << 1,
|
||||
/** An event where a finger is dragged inside the bounds of the control node. */
|
||||
ASControlNodeEventTouchDragInside = 1 << 2,
|
||||
/** An event where a finger is dragged just outside the bounds of the control. */
|
||||
ASControlNodeEventTouchDragOutside = 1 << 3,
|
||||
/** A touch-up event in the control node where the finger is inside the bounds of the node. */
|
||||
ASControlNodeEventTouchUpInside = 1 << 4,
|
||||
/** A touch-up event in the control node where the finger is outside the bounds of the node. */
|
||||
ASControlNodeEventTouchUpOutside = 1 << 5,
|
||||
/** A system event canceling the current touches for the control node. */
|
||||
ASControlNodeEventTouchCancel = 1 << 6,
|
||||
/** A system event triggered when controls like switches, slides, etc change state. */
|
||||
ASControlNodeEventValueChanged = 1 << 12,
|
||||
/** A system event when the Play/Pause button on the Apple TV remote is pressed. */
|
||||
ASControlNodeEventPrimaryActionTriggered = 1 << 13,
|
||||
|
||||
/** All events, including system events. */
|
||||
ASControlNodeEventAllEvents = 0xFFFFFFFF
|
||||
};
|
||||
|
||||
/**
|
||||
* Compatibility aliases for @c ASControlState enum.
|
||||
* We previously provided our own enum, but when it was imported
|
||||
* into Swift, the @c normal (0) option disappeared.
|
||||
*
|
||||
* Apple's UIControlState enum gets special treatment here, and
|
||||
* UIControlStateNormal is available in Swift.
|
||||
*/
|
||||
typedef UIControlState ASControlState ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlState.");
|
||||
static UIControlState const ASControlStateNormal ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlStateNormal.") = UIControlStateNormal;
|
||||
static UIControlState const ASControlStateDisabled ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlStateDisabled.") = UIControlStateDisabled;
|
||||
static UIControlState const ASControlStateHighlighted ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlStateHighlighted.") = UIControlStateHighlighted;
|
||||
static UIControlState const ASControlStateSelected ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlStateSelected.") = UIControlStateSelected;
|
||||
|
||||
/**
|
||||
@abstract ASControlNode is the base class for control nodes (such as buttons), or nodes that track touches to invoke targets with action messages.
|
||||
@discussion ASControlNode cannot be used directly. It instead defines the common interface and behavior structure for all its subclasses. Subclasses should import "ASControlNode+Subclasses.h" for information on methods intended to be overriden.
|
||||
*/
|
||||
@interface ASControlNode : ASDisplayNode
|
||||
|
||||
#pragma mark - Control State
|
||||
|
||||
/**
|
||||
@abstract Indicates whether or not the receiver is enabled.
|
||||
@discussion Specify YES to make the control enabled; otherwise, specify NO to make it disabled. The default value is YES. If the enabled state is NO, the control ignores touch events and subclasses may draw differently.
|
||||
*/
|
||||
@property (getter=isEnabled) BOOL enabled;
|
||||
|
||||
/**
|
||||
@abstract Indicates whether or not the receiver is highlighted.
|
||||
@discussion This is set automatically when the there is a touch inside the control and removed on exit or touch up. This is different from touchInside in that it includes an area around the control, rather than just for touches inside the control.
|
||||
*/
|
||||
@property (getter=isHighlighted) BOOL highlighted;
|
||||
|
||||
/**
|
||||
@abstract Indicates whether or not the receiver is highlighted.
|
||||
@discussion This is set automatically when the receiver is tapped.
|
||||
*/
|
||||
@property (getter=isSelected) BOOL selected;
|
||||
|
||||
#pragma mark - Tracking Touches
|
||||
/**
|
||||
@abstract Indicates whether or not the receiver is currently tracking touches related to an event.
|
||||
@discussion YES if the receiver is tracking touches; NO otherwise.
|
||||
*/
|
||||
@property (readonly, getter=isTracking) BOOL tracking;
|
||||
|
||||
/**
|
||||
@abstract Indicates whether or not a touch is inside the bounds of the receiver.
|
||||
@discussion YES if a touch is inside the receiver's bounds; NO otherwise.
|
||||
*/
|
||||
@property (readonly, getter=isTouchInside) BOOL touchInside;
|
||||
|
||||
#pragma mark - Action Messages
|
||||
/**
|
||||
@abstract Adds a target-action pair for a particular event (or events).
|
||||
@param target The object to which the action message is sent. If this is nil, the responder chain is searched for an object willing to respond to the action message. target is not retained.
|
||||
@param action A selector identifying an action message. May optionally include the sender and the event as parameters, in that order. May not be NULL.
|
||||
@param controlEvents A bitmask specifying the control events for which the action message is sent. May not be 0. See "Control Events" for bitmask constants.
|
||||
@discussion You may call this method multiple times, and you may specify multiple target-action pairs for a particular event. Targets are held weakly.
|
||||
*/
|
||||
- (void)addTarget:(nullable id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEvents;
|
||||
|
||||
/**
|
||||
@abstract Returns the actions that are associated with a target and a particular control event.
|
||||
@param target The target object. May not be nil.
|
||||
@param controlEvent A single constant of type ASControlNodeEvent that specifies a particular user action on the control; for a list of these constants, see "Control Events". May not be 0 or ASControlNodeEventAllEvents.
|
||||
@result An array of selector names as NSString objects, or nil if there are no action selectors associated with controlEvent.
|
||||
*/
|
||||
- (nullable NSArray<NSString *> *)actionsForTarget:(id)target forControlEvent:(ASControlNodeEvent)controlEvent AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
@abstract Returns all target objects associated with the receiver.
|
||||
@result A set of all targets for the receiver. The set may include NSNull to indicate at least one nil target (meaning, the responder chain is searched for a target.)
|
||||
*/
|
||||
- (NSSet *)allTargets AS_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
@abstract Removes a target-action pair for a particular event.
|
||||
@param target The target object. Pass nil to remove all targets paired with action and the specified control events.
|
||||
@param action A selector identifying an action message. Pass NULL to remove all action messages paired with target.
|
||||
@param controlEvents A bitmask specifying the control events associated with target and action. See "Control Events" for bitmask constants. May not be 0.
|
||||
*/
|
||||
- (void)removeTarget:(nullable id)target action:(nullable SEL)action forControlEvents:(ASControlNodeEvent)controlEvents;
|
||||
|
||||
/**
|
||||
@abstract Sends the actions for the control events for a particular event.
|
||||
@param controlEvents A bitmask specifying the control events for which to send actions. See "Control Events" for bitmask constants. May not be 0.
|
||||
@param event The event which triggered these control actions. May be nil.
|
||||
*/
|
||||
- (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(nullable UIEvent *)event;
|
||||
@end
|
||||
|
||||
#if TARGET_OS_TV
|
||||
@interface ASControlNode (tvOS)
|
||||
|
||||
/**
|
||||
@abstract How the node looks when it isn't focused. Exposed here so that subclasses can override.
|
||||
*/
|
||||
- (void)setDefaultFocusAppearance;
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -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 <AsyncDisplayKit/ASControlNode.h>
|
||||
#import <AsyncDisplayKit/ASControlNode+Private.h>
|
||||
#import <AsyncDisplayKit/ASControlNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASImageNode.h>
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit+Debug.h>
|
||||
#import "Private/ASInternalHelpers.h"
|
||||
#import <AsyncDisplayKit/ASControlTargetAction.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
|
||||
// UIControl allows dragging some distance outside of the control itself during
|
||||
// tracking. This value depends on the device idiom (25 or 70 points), so
|
||||
// so replicate that effect with the same values here for our own controls.
|
||||
#define kASControlNodeExpandedInset (([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) ? -25.0f : -70.0f)
|
||||
|
||||
// Initial capacities for dispatch tables.
|
||||
#define kASControlNodeEventDispatchTableInitialCapacity 4
|
||||
#define kASControlNodeActionDispatchTableInitialCapacity 4
|
||||
|
||||
@interface ASControlNode ()
|
||||
{
|
||||
@private
|
||||
// Control Attributes
|
||||
BOOL _enabled;
|
||||
BOOL _highlighted;
|
||||
|
||||
// Tracking
|
||||
BOOL _tracking;
|
||||
BOOL _touchInside;
|
||||
|
||||
// Target action pairs stored in an array for each event type
|
||||
// ASControlEvent -> [ASTargetAction0, ASTargetAction1]
|
||||
NSMutableDictionary<id<NSCopying>, NSMutableArray<ASControlTargetAction *> *> *_controlEventDispatchTable;
|
||||
}
|
||||
|
||||
// Read-write overrides.
|
||||
@property (getter=isTracking) BOOL tracking;
|
||||
@property (getter=isTouchInside) BOOL touchInside;
|
||||
|
||||
/**
|
||||
@abstract Returns a key to be used in _controlEventDispatchTable that identifies the control event.
|
||||
@param controlEvent A control event.
|
||||
@result A key for use in _controlEventDispatchTable.
|
||||
*/
|
||||
id<NSCopying> _ASControlNodeEventKeyForControlEvent(ASControlNodeEvent controlEvent);
|
||||
|
||||
/**
|
||||
@abstract Enumerates the ASControlNode events included mask, invoking the block for each event.
|
||||
@param mask An ASControlNodeEvent mask.
|
||||
@param block The block to be invoked for each ASControlNodeEvent included in mask.
|
||||
*/
|
||||
void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent));
|
||||
|
||||
/**
|
||||
@abstract Returns the expanded bounds used to determine if a touch is considered 'inside' during tracking.
|
||||
@param controlNode A control node.
|
||||
@result The expanded bounds of the node.
|
||||
*/
|
||||
CGRect _ASControlNodeGetExpandedBounds(ASControlNode *controlNode);
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASControlNode
|
||||
{
|
||||
ASImageNode *_debugHighlightOverlay;
|
||||
}
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
_enabled = YES;
|
||||
|
||||
// As we have no targets yet, we start off with user interaction off. When a target is added, it'll get turned back on.
|
||||
self.userInteractionEnabled = NO;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#if TARGET_OS_TV
|
||||
- (void)didLoad
|
||||
{
|
||||
[super didLoad];
|
||||
|
||||
// On tvOS all controls, such as buttons, interact with the focus system even if they don't have a target set on them.
|
||||
// Here we add our own internal tap gesture to handle this behaviour.
|
||||
self.userInteractionEnabled = YES;
|
||||
UITapGestureRecognizer *tapGestureRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_pressDown)];
|
||||
tapGestureRec.allowedPressTypes = @[@(UIPressTypeSelect)];
|
||||
[self.view addGestureRecognizer:tapGestureRec];
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled
|
||||
{
|
||||
[super setUserInteractionEnabled:userInteractionEnabled];
|
||||
self.isAccessibilityElement = userInteractionEnabled;
|
||||
}
|
||||
|
||||
- (void)__exitHierarchy
|
||||
{
|
||||
[super __exitHierarchy];
|
||||
|
||||
// If a control node is exit the hierarchy and is tracking we have to cancel it
|
||||
if (self.tracking) {
|
||||
[self _cancelTrackingWithEvent:nil];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-missing-super-calls"
|
||||
|
||||
#pragma mark - ASDisplayNode Overrides
|
||||
|
||||
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
// If we're not interested in touches, we have nothing to do.
|
||||
if (!self.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the tracking should start
|
||||
UITouch *theTouch = [touches anyObject];
|
||||
if (![self beginTrackingWithTouch:theTouch withEvent:event]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we get more than one touch down on us, cancel.
|
||||
// Additionally, if we're already tracking a touch, a second touch beginning is cause for cancellation.
|
||||
if (touches.count > 1 || self.tracking) {
|
||||
[self _cancelTrackingWithEvent:event];
|
||||
} else {
|
||||
// Otherwise, begin tracking.
|
||||
self.tracking = YES;
|
||||
|
||||
// No need to check bounds on touchesBegan as we wouldn't get the call if it wasn't in our bounds.
|
||||
self.touchInside = YES;
|
||||
self.highlighted = YES;
|
||||
|
||||
// Send the appropriate touch-down control event depending on how many times we've been tapped.
|
||||
ASControlNodeEvent controlEventMask = (theTouch.tapCount == 1) ? ASControlNodeEventTouchDown : ASControlNodeEventTouchDownRepeat;
|
||||
[self sendActionsForControlEvents:controlEventMask withEvent:event];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
// If we're not interested in touches, we have nothing to do.
|
||||
if (!self.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSParameterAssert(touches.count == 1);
|
||||
UITouch *theTouch = [touches anyObject];
|
||||
|
||||
// Check if tracking should continue
|
||||
if (!self.tracking || ![self continueTrackingWithTouch:theTouch withEvent:event]) {
|
||||
self.tracking = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
CGPoint touchLocation = [theTouch locationInView:self.view];
|
||||
|
||||
// Update our touchInside state.
|
||||
BOOL dragIsInsideBounds = [self pointInside:touchLocation withEvent:nil];
|
||||
|
||||
// Update our highlighted state.
|
||||
CGRect expandedBounds = _ASControlNodeGetExpandedBounds(self);
|
||||
BOOL dragIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation);
|
||||
self.touchInside = dragIsInsideExpandedBounds;
|
||||
self.highlighted = dragIsInsideExpandedBounds;
|
||||
|
||||
[self sendActionsForControlEvents:(dragIsInsideBounds ? ASControlNodeEventTouchDragInside : ASControlNodeEventTouchDragOutside)
|
||||
withEvent:event];
|
||||
}
|
||||
|
||||
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
// If we're not interested in touches, we have nothing to do.
|
||||
if (!self.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note that we've cancelled tracking.
|
||||
[self _cancelTrackingWithEvent:event];
|
||||
}
|
||||
|
||||
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
// If we're not interested in touches, we have nothing to do.
|
||||
if (!self.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// On iPhone 6s, iOS 9.2 (and maybe other versions) sometimes calls -touchesEnded:withEvent:
|
||||
// twice on the view for one call to -touchesBegan:withEvent:. On ASControlNode, it used to
|
||||
// trigger an action twice unintentionally. Now, we ignore that event if we're not in a tracking
|
||||
// state in order to have a correct behavior.
|
||||
// It might be related to that issue: http://www.openradar.me/22910171
|
||||
if (!self.tracking) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSParameterAssert([touches count] == 1);
|
||||
UITouch *theTouch = [touches anyObject];
|
||||
CGPoint touchLocation = [theTouch locationInView:self.view];
|
||||
|
||||
// Update state.
|
||||
self.tracking = NO;
|
||||
self.touchInside = NO;
|
||||
self.highlighted = NO;
|
||||
|
||||
// Note that we've ended tracking.
|
||||
[self endTrackingWithTouch:theTouch withEvent:event];
|
||||
|
||||
// Send the appropriate touch-up control event.
|
||||
CGRect expandedBounds = _ASControlNodeGetExpandedBounds(self);
|
||||
BOOL touchUpIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation);
|
||||
|
||||
[self sendActionsForControlEvents:(touchUpIsInsideExpandedBounds ? ASControlNodeEventTouchUpInside : ASControlNodeEventTouchUpOutside)
|
||||
withEvent:event];
|
||||
}
|
||||
|
||||
- (void)_cancelTrackingWithEvent:(UIEvent *)event
|
||||
{
|
||||
// We're no longer tracking and there is no touch to be inside.
|
||||
self.tracking = NO;
|
||||
self.touchInside = NO;
|
||||
self.highlighted = NO;
|
||||
|
||||
// Send the cancel event.
|
||||
[self sendActionsForControlEvents:ASControlNodeEventTouchCancel withEvent:event];
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
// If not enabled we should not care about receving touches
|
||||
if (! self.enabled) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [super hitTest:point withEvent:event];
|
||||
}
|
||||
|
||||
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
|
||||
{
|
||||
// If we're interested in touches, this is a tap (the only gesture we care about) and passed -hitTest for us, then no, you may not begin. Sir.
|
||||
if (self.enabled && [gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] && gestureRecognizer.view != self.view) {
|
||||
UITapGestureRecognizer *tapRecognizer = (UITapGestureRecognizer *)gestureRecognizer;
|
||||
// Allow double-tap gestures
|
||||
return tapRecognizer.numberOfTapsRequired != 1;
|
||||
}
|
||||
|
||||
// Otherwise, go ahead. :]
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)supportsLayerBacking
|
||||
{
|
||||
return super.supportsLayerBacking && !self.userInteractionEnabled;
|
||||
}
|
||||
|
||||
#pragma mark - Action Messages
|
||||
|
||||
- (void)addTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEventMask
|
||||
{
|
||||
NSParameterAssert(action);
|
||||
NSParameterAssert(controlEventMask != 0);
|
||||
|
||||
// ASControlNode cannot be layer backed if adding a target
|
||||
ASDisplayNodeAssert(!self.isLayerBacked, @"ASControlNode is layer backed, will never be able to call target in target:action: pair.");
|
||||
|
||||
ASLockScopeSelf();
|
||||
|
||||
if (!_controlEventDispatchTable) {
|
||||
_controlEventDispatchTable = [[NSMutableDictionary alloc] initWithCapacity:kASControlNodeEventDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries.
|
||||
|
||||
// only show tap-able areas for views with 1 or more addTarget:action: pairs
|
||||
if ([ASControlNode enableHitTestDebug] && _debugHighlightOverlay == nil) {
|
||||
// do not use ASPerformBlockOnMainThread here, if it performs the block synchronously it will continue
|
||||
// holding the lock while calling addSubnode.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// add a highlight overlay node with area of ASControlNode + UIEdgeInsets
|
||||
self.clipsToBounds = NO;
|
||||
_debugHighlightOverlay = [[ASImageNode alloc] init];
|
||||
_debugHighlightOverlay.zPosition = 1000; // ensure we're over the top of any siblings
|
||||
_debugHighlightOverlay.layerBacked = YES;
|
||||
[self addSubnode:_debugHighlightOverlay];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create new target action pair
|
||||
ASControlTargetAction *targetAction = [[ASControlTargetAction alloc] init];
|
||||
targetAction.action = action;
|
||||
targetAction.target = target;
|
||||
|
||||
// Enumerate the events in the mask, adding the target-action pair for each control event included in controlEventMask
|
||||
_ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^
|
||||
(ASControlNodeEvent controlEvent)
|
||||
{
|
||||
// Do we already have an event table for this control event?
|
||||
id<NSCopying> eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent);
|
||||
NSMutableArray *eventTargetActionArray = _controlEventDispatchTable[eventKey];
|
||||
|
||||
if (!eventTargetActionArray) {
|
||||
eventTargetActionArray = [[NSMutableArray alloc] init];
|
||||
}
|
||||
|
||||
// Remove any prior target-action pair for this event, as UIKit does.
|
||||
[eventTargetActionArray removeObject:targetAction];
|
||||
|
||||
// Register the new target-action as the last one to be sent.
|
||||
[eventTargetActionArray addObject:targetAction];
|
||||
|
||||
if (eventKey) {
|
||||
[_controlEventDispatchTable setObject:eventTargetActionArray forKey:eventKey];
|
||||
}
|
||||
});
|
||||
|
||||
self.userInteractionEnabled = YES;
|
||||
}
|
||||
|
||||
- (NSArray *)actionsForTarget:(id)target forControlEvent:(ASControlNodeEvent)controlEvent
|
||||
{
|
||||
NSParameterAssert(target);
|
||||
NSParameterAssert(controlEvent != 0 && controlEvent != ASControlNodeEventAllEvents);
|
||||
|
||||
ASLockScopeSelf();
|
||||
|
||||
// Grab the event target action array for this event.
|
||||
NSMutableArray *eventTargetActionArray = _controlEventDispatchTable[_ASControlNodeEventKeyForControlEvent(controlEvent)];
|
||||
if (!eventTargetActionArray) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableArray *actions = [[NSMutableArray alloc] init];
|
||||
|
||||
// Collect all actions for this target.
|
||||
for (ASControlTargetAction *targetAction in eventTargetActionArray) {
|
||||
if ((target == nil && targetAction.createdWithNoTarget) || (target != nil && target == targetAction.target)) {
|
||||
[actions addObject:NSStringFromSelector(targetAction.action)];
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
- (NSSet *)allTargets
|
||||
{
|
||||
ASLockScopeSelf();
|
||||
|
||||
NSMutableSet *targets = [[NSMutableSet alloc] init];
|
||||
|
||||
// Look at each event...
|
||||
for (NSMutableArray *eventTargetActionArray in [_controlEventDispatchTable objectEnumerator]) {
|
||||
// and each event's targets...
|
||||
for (ASControlTargetAction *targetAction in eventTargetActionArray) {
|
||||
[targets addObject:targetAction.target];
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
- (void)removeTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeEvent)controlEventMask
|
||||
{
|
||||
NSParameterAssert(controlEventMask != 0);
|
||||
|
||||
ASLockScopeSelf();
|
||||
|
||||
// Enumerate the events in the mask, removing the target-action pair for each control event included in controlEventMask.
|
||||
_ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^
|
||||
(ASControlNodeEvent controlEvent)
|
||||
{
|
||||
// Grab the dispatch table for this event (if we have it).
|
||||
id<NSCopying> eventKey = _ASControlNodeEventKeyForControlEvent(controlEvent);
|
||||
NSMutableArray *eventTargetActionArray = _controlEventDispatchTable[eventKey];
|
||||
if (!eventTargetActionArray) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSPredicate *filterPredicate = [NSPredicate predicateWithBlock:^BOOL(ASControlTargetAction *_Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
|
||||
if (!target || evaluatedObject.target == target) {
|
||||
if (!action) {
|
||||
return NO;
|
||||
} else if (evaluatedObject.action == action) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}];
|
||||
[eventTargetActionArray filterUsingPredicate:filterPredicate];
|
||||
|
||||
if (eventTargetActionArray.count == 0) {
|
||||
// If there are no targets for this event anymore, remove it.
|
||||
[_controlEventDispatchTable removeObjectForKey:eventKey];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(UIEvent *)event
|
||||
{
|
||||
ASDisplayNodeAssertMainThread(); //We access self.view below, it's not safe to call this off of main.
|
||||
NSParameterAssert(controlEvents != 0);
|
||||
|
||||
NSMutableArray *resolvedEventTargetActionArray = [[NSMutableArray<ASControlTargetAction *> alloc] init];
|
||||
|
||||
{
|
||||
ASLockScopeSelf();
|
||||
|
||||
// Enumerate the events in the mask, invoking the target-action pairs for each.
|
||||
_ASEnumerateControlEventsIncludedInMaskWithBlock(controlEvents, ^
|
||||
(ASControlNodeEvent controlEvent)
|
||||
{
|
||||
// Iterate on each target action pair
|
||||
for (ASControlTargetAction *targetAction in _controlEventDispatchTable[_ASControlNodeEventKeyForControlEvent(controlEvent)]) {
|
||||
ASControlTargetAction *resolvedTargetAction = [[ASControlTargetAction alloc] init];
|
||||
resolvedTargetAction.action = targetAction.action;
|
||||
resolvedTargetAction.target = targetAction.target;
|
||||
|
||||
// NSNull means that a nil target was set, so start at self and travel the responder chain
|
||||
if (!resolvedTargetAction.target && targetAction.createdWithNoTarget) {
|
||||
// if the target cannot perform the action, travel the responder chain to try to find something that does
|
||||
resolvedTargetAction.target = [self.view targetForAction:resolvedTargetAction.action withSender:self];
|
||||
}
|
||||
|
||||
if (resolvedTargetAction.target) {
|
||||
[resolvedEventTargetActionArray addObject:resolvedTargetAction];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//We don't want to hold the lock while calling out, we could potentially walk up the ownership tree causing a deadlock.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
for (ASControlTargetAction *targetAction in resolvedEventTargetActionArray) {
|
||||
[targetAction.target performSelector:targetAction.action withObject:self withObject:event];
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
#pragma mark - Convenience
|
||||
|
||||
id<NSCopying> _ASControlNodeEventKeyForControlEvent(ASControlNodeEvent controlEvent)
|
||||
{
|
||||
return @(controlEvent);
|
||||
}
|
||||
|
||||
void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent))
|
||||
{
|
||||
if (block == nil) {
|
||||
return;
|
||||
}
|
||||
// Start with our first event (touch down) and work our way up to the last event (PrimaryActionTriggered)
|
||||
for (ASControlNodeEvent thisEvent = ASControlNodeEventTouchDown; thisEvent <= ASControlNodeEventPrimaryActionTriggered; thisEvent <<= 1) {
|
||||
// If it's included in the mask, invoke the block.
|
||||
if ((mask & thisEvent) == thisEvent)
|
||||
block(thisEvent);
|
||||
}
|
||||
}
|
||||
|
||||
CGRect _ASControlNodeGetExpandedBounds(ASControlNode *controlNode) {
|
||||
return CGRectInset(UIEdgeInsetsInsetRect(controlNode.view.bounds, controlNode.hitTestSlop), kASControlNodeExpandedInset, kASControlNodeExpandedInset);
|
||||
}
|
||||
|
||||
#pragma mark - For Subclasses
|
||||
|
||||
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)cancelTrackingWithEvent:(UIEvent *)touchEvent
|
||||
{
|
||||
// Subclass hook
|
||||
}
|
||||
|
||||
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent
|
||||
{
|
||||
// Subclass hook
|
||||
}
|
||||
|
||||
#pragma mark - Debug
|
||||
- (ASImageNode *)debugHighlightOverlay
|
||||
{
|
||||
return _debugHighlightOverlay;
|
||||
}
|
||||
@end
|
@ -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 <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
@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
|
@ -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
|
@ -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 <AsyncDisplayKit/ASLayoutSpec.h>
|
||||
|
||||
/**
|
||||
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 <ASLayoutElement>)child corner:(id <ASLayoutElement>)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 <ASLayoutElement>)child corner:(id <ASLayoutElement>)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 <ASLayoutElement> 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
|
@ -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 <AsyncDisplayKit/ASCornerLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASLayoutSpec+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||
|
||||
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 <ASLayoutElement>)child corner:(id <ASLayoutElement>)corner location:(ASCornerLayoutLocation)location
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.child = child;
|
||||
self.corner = corner;
|
||||
self.cornerLocation = location;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)cornerLayoutSpecWithChild:(id <ASLayoutElement>)child corner:(id <ASLayoutElement>)corner location:(ASCornerLayoutLocation)location NS_RETURNS_RETAINED
|
||||
{
|
||||
return [[self alloc] initWithChild:child corner:corner location:location];
|
||||
}
|
||||
|
||||
#pragma mark - Children
|
||||
|
||||
- (void)setChild:(id<ASLayoutElement>)child
|
||||
{
|
||||
ASDisplayNodeAssertNotNil(child, @"Child shouldn't be nil.");
|
||||
[super setChild:child atIndex:kBaseChildIndex];
|
||||
}
|
||||
|
||||
- (id<ASLayoutElement>)child
|
||||
{
|
||||
return [super childAtIndex:kBaseChildIndex];
|
||||
}
|
||||
|
||||
- (void)setCorner:(id<ASLayoutElement>)corner
|
||||
{
|
||||
ASDisplayNodeAssertNotNil(corner, @"Corner element cannot be nil.");
|
||||
[super setChild:corner atIndex:kCornerChildIndex];
|
||||
}
|
||||
|
||||
- (id<ASLayoutElement>)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 <ASLayoutElement> child = self.child;
|
||||
id <ASLayoutElement> 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 <ASLayoutElement>)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
|
@ -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 <UIKit/UIKit.h>
|
||||
#import <AsyncDisplayKit/ASBlockTypes.h>
|
||||
#import <AsyncDisplayKit/ASDimension.h>
|
||||
#import <AsyncDisplayKit/ASEventLog.h>
|
||||
#ifdef __cplusplus
|
||||
#import <vector>
|
||||
#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 <NSObject>
|
||||
|
||||
/**
|
||||
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<NSString *> *)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<ASSectionContext>)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 <NSObject>
|
||||
|
||||
/**
|
||||
* 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 <NSObject>
|
||||
|
||||
/**
|
||||
* @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<ASDataControllerSource>)dataSource node:(nullable id<ASRangeManagingNode>)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<ASRangeManagingNode> 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<ASDataControllerSource> 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<ASDataControllerDelegate> delegate;
|
||||
|
||||
/**
|
||||
* Delegate for preparing layouts. Main thead only.
|
||||
*/
|
||||
@property (nonatomic, weak) id<ASDataControllerLayoutDelegate> 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<NSInteger>)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<NSFastEnumeration>)nodes nodesSizeChanged:(NSMutableArray<ASCellNode *> *)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
|
@ -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 <AsyncDisplayKit/ASDataController.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#import <AsyncDisplayKit/_ASHierarchyChangeSet.h>
|
||||
#import <AsyncDisplayKit/_ASScopeTimer.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASCellNode.h>
|
||||
#import <AsyncDisplayKit/ASCollectionElement.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutState.h>
|
||||
#import <AsyncDisplayKit/ASDispatch.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/ASElementMap.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
#import <AsyncDisplayKit/ASSignpost.h>
|
||||
#import <AsyncDisplayKit/ASMainSerialQueue.h>
|
||||
#import <AsyncDisplayKit/ASMutableElementMap.h>
|
||||
#import <AsyncDisplayKit/ASRangeManagingNode.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
#import <AsyncDisplayKit/ASTwoDimensionalArrayUtils.h>
|
||||
#import <AsyncDisplayKit/ASSection.h>
|
||||
|
||||
#import "Private/ASInternalHelpers.h"
|
||||
#import <AsyncDisplayKit/ASCellNode+Internal.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/NSIndexSet+ASHelpers.h>
|
||||
|
||||
//#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<ASDataControllerLayoutDelegate> _layoutDelegate;
|
||||
|
||||
NSInteger _nextSectionID;
|
||||
|
||||
BOOL _itemCountsFromDataSourceAreValid; // Main thread only.
|
||||
std::vector<NSInteger> _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<int> _editingTransactionGroupCount;
|
||||
|
||||
BOOL _initialReloadDataHasBeenCalled;
|
||||
|
||||
BOOL _synchronized;
|
||||
NSMutableSet<ASDataControllerSynchronizationBlock> *_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<ASDataControllerSource>)dataSource node:(nullable id<ASRangeManagingNode>)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<ASDataControllerLayoutDelegate>)layoutDelegate
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
return _layoutDelegate;
|
||||
}
|
||||
|
||||
- (void)setLayoutDelegate:(id<ASDataControllerLayoutDelegate>)layoutDelegate
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if (layoutDelegate != _layoutDelegate) {
|
||||
_layoutDelegate = layoutDelegate;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Cell Layout
|
||||
|
||||
- (void)_allocateNodesFromElements:(NSArray<ASCollectionElement *> *)elements
|
||||
{
|
||||
ASSERT_ON_EDITING_QUEUE;
|
||||
|
||||
NSUInteger nodeCount = elements.count;
|
||||
__weak id<ASDataControllerSource> 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<ASDataControllerSource> 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<NSIndexPath *> *)_allIndexPathsForItemsOfKind:(NSString *)kind inSections:(NSIndexSet *)sections
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if (sections.count == 0 || _dataSource == nil) {
|
||||
return @[];
|
||||
}
|
||||
|
||||
const auto indexPaths = [[NSMutableArray<NSIndexPath *> alloc] init];
|
||||
if ([kind isEqualToString:ASDataControllerRowNodeKind]) {
|
||||
std::vector<NSInteger> 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<ASDataControllerSource> 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<NSIndexPath *> *)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<NSIndexPath *> *indexPaths = [self _allIndexPathsForItemsOfKind:kind inSections:sectionIndexes];
|
||||
NSMutableArray<NSIndexPath *> *indexPathsToDeleteForKind = [[NSMutableArray alloc] init];
|
||||
NSMutableArray<NSIndexPath *> *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<NSIndexPath *> *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<NSIndexPath *> *)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<ASDataControllerSource> dataSource = self.dataSource;
|
||||
id<ASRangeManagingNode> 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<NSInteger>)itemCountsFromDataSource
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if (NO == _itemCountsFromDataSourceAreValid) {
|
||||
id<ASDataControllerSource> source = self.dataSource;
|
||||
NSInteger sectionCount = [source numberOfSectionsInDataController:self];
|
||||
std::vector<NSInteger> 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<NSString *> *)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<ASDataControllerSource> 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<ASDataControllerLayoutDelegate> 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<ASCollectionElement *> 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<ASSectionContext> 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<NSFastEnumeration>)nodes nodesSizeChanged:(NSMutableArray<ASCellNode *> *)nodesSizesChanged
|
||||
{
|
||||
NSParameterAssert(nodes);
|
||||
NSParameterAssert(nodesSizesChanged);
|
||||
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if (!_initialReloadDataHasBeenCalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
id<ASDataControllerSource> 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
|
@ -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 <AsyncDisplayKit/ASButtonNode.h>
|
||||
|
||||
@interface ASDefaultPlayButton : ASButtonNode
|
||||
|
||||
@end
|
@ -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 <AsyncDisplayKit/_ASDisplayLayer.h>
|
||||
|
||||
@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
|
@ -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 <AsyncDisplayKit/ASControlNode.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, ASDefaultPlaybackButtonType) {
|
||||
ASDefaultPlaybackButtonTypePlay,
|
||||
ASDefaultPlaybackButtonTypePause
|
||||
};
|
||||
|
||||
@interface ASDefaultPlaybackButton : ASControlNode
|
||||
@property (nonatomic) ASDefaultPlaybackButtonType buttonType;
|
||||
@end
|
@ -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 <AsyncDisplayKit/_ASDisplayLayer.h>
|
||||
|
||||
@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
|
@ -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 <AsyncDisplayKit/ASAvailability.h>
|
||||
|
||||
#if YOGA
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ASLayout;
|
||||
|
||||
AS_EXTERN void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullable node, void(^block)(ASDisplayNode *node));
|
||||
|
||||
@interface ASDisplayNode (Yoga)
|
||||
|
||||
@property (copy) NSArray *yogaChildren;
|
||||
|
||||
- (void)addYogaChild:(ASDisplayNode *)child;
|
||||
- (void)removeYogaChild:(ASDisplayNode *)child;
|
||||
- (void)insertYogaChild:(ASDisplayNode *)child atIndex:(NSUInteger)index;
|
||||
|
||||
- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute;
|
||||
|
||||
@property BOOL yogaLayoutInProgress;
|
||||
// TODO: Make this atomic (lock).
|
||||
@property (nullable, nonatomic) ASLayout *yogaCalculatedLayout;
|
||||
|
||||
// Will walk up the Yoga tree and returns the root node
|
||||
- (ASDisplayNode *)yogaRoot;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@interface ASDisplayNode (YogaLocking)
|
||||
/**
|
||||
* @discussion Attempts(spinning) to lock all node up to root node when yoga is enabled.
|
||||
* This will lock self when yoga is not enabled;
|
||||
*/
|
||||
- (ASLockSet)lockToRootIfNeededForLayout;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
// These methods are intended to be used internally to Texture, and should not be called directly.
|
||||
@interface ASDisplayNode (YogaInternal)
|
||||
|
||||
/// For internal usage only
|
||||
- (BOOL)shouldHaveYogaMeasureFunc;
|
||||
/// For internal usage only
|
||||
- (ASLayout *)calculateLayoutYoga:(ASSizeRange)constrainedSize;
|
||||
/// For internal usage only
|
||||
- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize;
|
||||
/// For internal usage only
|
||||
- (void)invalidateCalculatedYogaLayout;
|
||||
/**
|
||||
* @discussion return true only when yoga enabled and the node is in yoga tree and the node is
|
||||
* not leaf that implemented measure function.
|
||||
*/
|
||||
- (BOOL)locked_shouldLayoutFromYogaRoot;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASDisplayNode (YogaDebugging)
|
||||
|
||||
- (NSString *)yogaTreeDescription;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASLayoutElementStyle (Yoga)
|
||||
|
||||
- (YGNodeRef)yogaNodeCreateIfNeeded;
|
||||
- (void)destroyYogaNode;
|
||||
|
||||
@property (readonly) YGNodeRef yogaNode;
|
||||
|
||||
@property ASStackLayoutDirection flexDirection;
|
||||
@property YGDirection direction;
|
||||
@property ASStackLayoutJustifyContent justifyContent;
|
||||
@property ASStackLayoutAlignItems alignItems;
|
||||
@property YGPositionType positionType;
|
||||
@property ASEdgeInsets position;
|
||||
@property ASEdgeInsets margin;
|
||||
@property ASEdgeInsets padding;
|
||||
@property ASEdgeInsets border;
|
||||
@property CGFloat aspectRatio;
|
||||
@property YGWrap flexWrap;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
// When Yoga is enabled, there are several points where we want to lock the tree to the root but otherwise (without Yoga)
|
||||
// will want to simply lock self.
|
||||
#define ASScopedLockSelfOrToRoot() ASScopedLockSet lockSet = [self lockToRootIfNeededForLayout]
|
||||
#else
|
||||
#define ASScopedLockSelfOrToRoot() ASLockScopeSelf()
|
||||
#endif
|
@ -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 <AsyncDisplayKit/ASAvailability.h>
|
||||
|
||||
#if YOGA /* YOGA */
|
||||
|
||||
#import <AsyncDisplayKit/_ASDisplayViewAccessiblity.h>
|
||||
#import <AsyncDisplayKit/ASYogaUtilities.h>
|
||||
#import <AsyncDisplayKit/ASCollections.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
|
||||
#import <AsyncDisplayKit/ASDimension.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASLayoutElementStylePrivate.h>
|
||||
#import <AsyncDisplayKit/ASNodeController+Beta.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNode+LayoutSpec.h>
|
||||
|
||||
#define YOGA_LAYOUT_LOGGING 0
|
||||
|
||||
#pragma mark - ASDisplayNode+Yoga
|
||||
|
||||
@interface ASDisplayNode (YogaPrivate)
|
||||
@property (nonatomic, weak) ASDisplayNode *yogaParent;
|
||||
- (ASSizeRange)_locked_constrainedSizeForLayoutPass;
|
||||
@end
|
||||
|
||||
@implementation ASDisplayNode (Yoga)
|
||||
|
||||
- (ASDisplayNode *)yogaRoot
|
||||
{
|
||||
ASDisplayNode *yogaRoot = self;
|
||||
ASDisplayNode *yogaParent = nil;
|
||||
while ((yogaParent = yogaRoot.yogaParent)) {
|
||||
yogaRoot = yogaParent;
|
||||
}
|
||||
return yogaRoot;
|
||||
}
|
||||
|
||||
- (void)setYogaChildren:(NSArray *)yogaChildren
|
||||
{
|
||||
ASScopedLockSelfOrToRoot();
|
||||
for (ASDisplayNode *child in [_yogaChildren copy]) {
|
||||
// Make sure to un-associate the YGNodeRef tree before replacing _yogaChildren
|
||||
// If this becomes a performance bottleneck, it can be optimized by not doing the NSArray removals here.
|
||||
[self _locked_removeYogaChild:child];
|
||||
}
|
||||
_yogaChildren = nil;
|
||||
for (ASDisplayNode *child in yogaChildren) {
|
||||
[self _locked_addYogaChild:child];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)yogaChildren
|
||||
{
|
||||
ASLockScope(self.yogaRoot);
|
||||
return [_yogaChildren copy] ?: @[];
|
||||
}
|
||||
|
||||
- (void)addYogaChild:(ASDisplayNode *)child
|
||||
{
|
||||
ASScopedLockSelfOrToRoot();
|
||||
[self _locked_addYogaChild:child];
|
||||
}
|
||||
|
||||
- (void)_locked_addYogaChild:(ASDisplayNode *)child
|
||||
{
|
||||
[self insertYogaChild:child atIndex:_yogaChildren.count];
|
||||
}
|
||||
|
||||
- (void)removeYogaChild:(ASDisplayNode *)child
|
||||
{
|
||||
ASScopedLockSelfOrToRoot();
|
||||
[self _locked_removeYogaChild:child];
|
||||
}
|
||||
|
||||
- (void)_locked_removeYogaChild:(ASDisplayNode *)child
|
||||
{
|
||||
if (child == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
[_yogaChildren removeObjectIdenticalTo:child];
|
||||
|
||||
// YGNodeRef removal is done in setParent:
|
||||
child.yogaParent = nil;
|
||||
}
|
||||
|
||||
- (void)insertYogaChild:(ASDisplayNode *)child atIndex:(NSUInteger)index
|
||||
{
|
||||
ASScopedLockSelfOrToRoot();
|
||||
[self _locked_insertYogaChild:child atIndex:index];
|
||||
}
|
||||
|
||||
- (void)_locked_insertYogaChild:(ASDisplayNode *)child atIndex:(NSUInteger)index
|
||||
{
|
||||
if (child == nil) {
|
||||
return;
|
||||
}
|
||||
if (_yogaChildren == nil) {
|
||||
_yogaChildren = [[NSMutableArray alloc] init];
|
||||
}
|
||||
|
||||
// Clean up state in case this child had another parent.
|
||||
[self _locked_removeYogaChild:child];
|
||||
|
||||
[_yogaChildren insertObject:child atIndex:index];
|
||||
|
||||
// YGNodeRef insertion is done in setParent:
|
||||
child.yogaParent = self;
|
||||
}
|
||||
|
||||
#pragma mark - Subclass Hooks
|
||||
|
||||
- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute
|
||||
{
|
||||
UIUserInterfaceLayoutDirection layoutDirection =
|
||||
[UIView userInterfaceLayoutDirectionForSemanticContentAttribute:attribute];
|
||||
self.style.direction = (layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight
|
||||
? YGDirectionLTR : YGDirectionRTL);
|
||||
}
|
||||
|
||||
- (void)setYogaParent:(ASDisplayNode *)yogaParent
|
||||
{
|
||||
ASLockScopeSelf();
|
||||
if (_yogaParent == yogaParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
YGNodeRef yogaNode = [self.style yogaNodeCreateIfNeeded];
|
||||
YGNodeRef oldParentRef = YGNodeGetParent(yogaNode);
|
||||
if (oldParentRef != NULL) {
|
||||
YGNodeRemoveChild(oldParentRef, yogaNode);
|
||||
}
|
||||
|
||||
_yogaParent = yogaParent;
|
||||
if (yogaParent) {
|
||||
YGNodeRef newParentRef = [yogaParent.style yogaNodeCreateIfNeeded];
|
||||
YGNodeInsertChild(newParentRef, yogaNode, YGNodeGetChildCount(newParentRef));
|
||||
}
|
||||
}
|
||||
|
||||
- (ASDisplayNode *)yogaParent
|
||||
{
|
||||
return _yogaParent;
|
||||
}
|
||||
|
||||
- (void)setYogaCalculatedLayout:(ASLayout *)yogaCalculatedLayout
|
||||
{
|
||||
_yogaCalculatedLayout = yogaCalculatedLayout;
|
||||
}
|
||||
|
||||
- (ASLayout *)yogaCalculatedLayout
|
||||
{
|
||||
return _yogaCalculatedLayout;
|
||||
}
|
||||
|
||||
- (void)setYogaLayoutInProgress:(BOOL)yogaLayoutInProgress
|
||||
{
|
||||
setFlag(YogaLayoutInProgress, yogaLayoutInProgress);
|
||||
[self updateYogaMeasureFuncIfNeeded];
|
||||
}
|
||||
|
||||
- (BOOL)yogaLayoutInProgress
|
||||
{
|
||||
return checkFlag(YogaLayoutInProgress);
|
||||
}
|
||||
|
||||
- (ASLayout *)layoutForYogaNode
|
||||
{
|
||||
YGNodeRef yogaNode = self.style.yogaNode;
|
||||
|
||||
CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode));
|
||||
CGPoint position = CGPointMake(YGNodeLayoutGetLeft(yogaNode), YGNodeLayoutGetTop(yogaNode));
|
||||
|
||||
if (!ASIsCGSizeValidForSize(size)) {
|
||||
size = CGSizeZero;
|
||||
}
|
||||
|
||||
if (!ASIsCGPositionValidForLayout(position)) {
|
||||
position = CGPointZero;
|
||||
}
|
||||
return [ASLayout layoutWithLayoutElement:self size:size position:position sublayouts:nil];
|
||||
}
|
||||
|
||||
- (void)setupYogaCalculatedLayout
|
||||
{
|
||||
ASScopedLockSelfOrToRoot();
|
||||
|
||||
YGNodeRef yogaNode = self.style.yogaNode;
|
||||
uint32_t childCount = YGNodeGetChildCount(yogaNode);
|
||||
ASDisplayNodeAssert(childCount == _yogaChildren.count,
|
||||
@"Yoga tree should always be in sync with .yogaNodes array! %@",
|
||||
_yogaChildren);
|
||||
|
||||
ASLayout *rawSublayouts[childCount];
|
||||
int i = 0;
|
||||
for (ASDisplayNode *subnode in _yogaChildren) {
|
||||
rawSublayouts[i++] = [subnode layoutForYogaNode];
|
||||
}
|
||||
const auto sublayouts = [NSArray<ASLayout *> arrayByTransferring:rawSublayouts count:childCount];
|
||||
|
||||
// The layout for self should have position CGPointNull, but include the calculated size.
|
||||
CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode));
|
||||
if (!ASIsCGSizeValidForSize(size)) {
|
||||
size = CGSizeZero;
|
||||
}
|
||||
ASLayout *layout = [ASLayout layoutWithLayoutElement:self size:size sublayouts:sublayouts];
|
||||
|
||||
#if ASDISPLAYNODE_ASSERTIONS_ENABLED
|
||||
// Assert that the sublayout is already flattened.
|
||||
for (ASLayout *sublayout in layout.sublayouts) {
|
||||
if (sublayout.sublayouts.count > 0 || ASDynamicCast(sublayout.layoutElement, ASDisplayNode) == nil) {
|
||||
ASDisplayNodeAssert(NO, @"Yoga sublayout is not flattened! %@, %@", self, sublayout);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Because this layout won't go through the rest of the logic in calculateLayoutThatFits:, flatten it now.
|
||||
layout = [layout filteredNodeLayoutTree];
|
||||
|
||||
if ([self.yogaCalculatedLayout isEqual:layout] == NO) {
|
||||
self.yogaCalculatedLayout = layout;
|
||||
} else {
|
||||
layout = self.yogaCalculatedLayout;
|
||||
ASYogaLog("-setupYogaCalculatedLayout: applying identical ASLayout: %@", layout);
|
||||
}
|
||||
|
||||
// Setup _pendingDisplayNodeLayout to reference the Yoga-calculated ASLayout, *unless* we are a leaf node.
|
||||
// Leaf yoga nodes may have their own .sublayouts, if they use a layout spec (such as ASButtonNode).
|
||||
// Their _pending variable is set after passing the Yoga checks at the start of -calculateLayoutThatFits:
|
||||
|
||||
// For other Yoga nodes, there is no code that will set _pending unless we do it here. Why does it need to be set?
|
||||
// When CALayer triggers the -[ASDisplayNode __layout] call, we will check if our current _pending layout
|
||||
// has a size which matches our current bounds size. If it does, that layout will be used without recomputing it.
|
||||
|
||||
// NOTE: Yoga does not make the constrainedSize available to intermediate nodes in the tree (e.g. not root or leaves).
|
||||
// Although the size range provided here is not accurate, this will only affect caching of calls to layoutThatFits:
|
||||
// These calls will behave as if they are not cached, starting a new Yoga layout pass, but this will tap into Yoga's
|
||||
// own internal cache.
|
||||
|
||||
if ([self shouldHaveYogaMeasureFunc] == NO) {
|
||||
YGNodeRef parentNode = YGNodeGetParent(yogaNode);
|
||||
CGSize parentSize = CGSizeZero;
|
||||
if (parentNode) {
|
||||
parentSize.width = YGNodeLayoutGetWidth(parentNode);
|
||||
parentSize.height = YGNodeLayoutGetHeight(parentNode);
|
||||
}
|
||||
// For the root node in a Yoga tree, make sure to preserve the constrainedSize originally provided.
|
||||
// This will be used for all relayouts triggered by children, since they escalate to root.
|
||||
ASSizeRange range = parentNode ? ASSizeRangeUnconstrained : self.constrainedSizeForCalculatedLayout;
|
||||
_pendingDisplayNodeLayout = ASDisplayNodeLayout(layout, range, parentSize, _layoutVersion);
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)shouldHaveYogaMeasureFunc
|
||||
{
|
||||
ASLockScopeSelf();
|
||||
// Size calculation via calculateSizeThatFits: or layoutSpecThatFits:
|
||||
// For these nodes, we assume they may need custom Baseline calculation too.
|
||||
// This will be used for ASTextNode, as well as any other node that has no Yoga children
|
||||
BOOL isLeafNode = (_yogaChildren.count == 0);
|
||||
BOOL definesCustomLayout = [self implementsLayoutMethod];
|
||||
return (isLeafNode && definesCustomLayout);
|
||||
}
|
||||
|
||||
- (void)updateYogaMeasureFuncIfNeeded
|
||||
{
|
||||
// We set the measure func only during layout. Otherwise, a cycle is created:
|
||||
// The YGNodeRef Context will retain the ASDisplayNode, which retains the style, which owns the YGNodeRef.
|
||||
BOOL shouldHaveMeasureFunc = ([self shouldHaveYogaMeasureFunc] && checkFlag(YogaLayoutInProgress));
|
||||
|
||||
ASLayoutElementYogaUpdateMeasureFunc(self.style.yogaNode, shouldHaveMeasureFunc ? self : nil);
|
||||
}
|
||||
|
||||
- (void)invalidateCalculatedYogaLayout
|
||||
{
|
||||
ASLockScopeSelf();
|
||||
YGNodeRef yogaNode = self.style.yogaNode;
|
||||
if (yogaNode && [self shouldHaveYogaMeasureFunc]) {
|
||||
// Yoga internally asserts that MarkDirty() may only be called on nodes with a measurement function.
|
||||
BOOL needsTemporaryMeasureFunc = (YGNodeGetMeasureFunc(yogaNode) == NULL);
|
||||
if (needsTemporaryMeasureFunc) {
|
||||
ASDisplayNodeAssert(self.yogaLayoutInProgress == NO,
|
||||
@"shouldHaveYogaMeasureFunc == YES, and inside a layout pass, but no measure func pointer! %@", self);
|
||||
YGNodeSetMeasureFunc(yogaNode, &ASLayoutElementYogaMeasureFunc);
|
||||
}
|
||||
YGNodeMarkDirty(yogaNode);
|
||||
if (needsTemporaryMeasureFunc) {
|
||||
YGNodeSetMeasureFunc(yogaNode, NULL);
|
||||
}
|
||||
}
|
||||
self.yogaCalculatedLayout = nil;
|
||||
}
|
||||
|
||||
- (ASLayout *)calculateLayoutYoga:(ASSizeRange)constrainedSize
|
||||
{
|
||||
AS::UniqueLock l(__instanceLock__);
|
||||
|
||||
// There are several cases where Yoga could arrive here:
|
||||
// - This node is not in a Yoga tree: it has neither a yogaParent nor yogaChildren.
|
||||
// - This node is a Yoga tree root: it has no yogaParent, but has yogaChildren.
|
||||
// - This node is a Yoga tree node: it has both a yogaParent and yogaChildren.
|
||||
// - This node is a Yoga tree leaf: it has a yogaParent, but no yogaChidlren.
|
||||
if ([self locked_shouldLayoutFromYogaRoot]) {
|
||||
// If we're a yoga root, tree node, or leaf with no measure func (e.g. spacer), then
|
||||
// initiate a new Yoga calculation pass from root.
|
||||
as_activity_create_for_scope("Yoga layout calculation");
|
||||
if (self.yogaLayoutInProgress == NO) {
|
||||
ASYogaLog("Calculating yoga layout from root %@, %@", self,
|
||||
NSStringFromASSizeRange(constrainedSize));
|
||||
[self calculateLayoutFromYogaRoot:constrainedSize];
|
||||
} else {
|
||||
ASYogaLog("Reusing existing yoga layout %@", _yogaCalculatedLayout);
|
||||
}
|
||||
ASDisplayNodeAssert(_yogaCalculatedLayout,
|
||||
@"Yoga node should have a non-nil layout at this stage: %@", self);
|
||||
return _yogaCalculatedLayout;
|
||||
} else {
|
||||
// If we're a yoga leaf node with custom measurement function, proceed with normal layout so
|
||||
// layoutSpecs can run (e.g. ASButtonNode).
|
||||
ASYogaLog("PROCEEDING past Yoga check to calculate ASLayout for: %@", self);
|
||||
}
|
||||
|
||||
// Delegate to layout spec layout for nodes that do not support Yoga
|
||||
return [self calculateLayoutLayoutSpec:constrainedSize];
|
||||
}
|
||||
|
||||
- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize
|
||||
{
|
||||
ASScopedLockSet lockSet = [self lockToRootIfNeededForLayout];
|
||||
ASDisplayNode *yogaRoot = self.yogaRoot;
|
||||
|
||||
if (self != yogaRoot) {
|
||||
ASYogaLog("ESCALATING to Yoga root: %@", self);
|
||||
// TODO(appleguy): Consider how to get the constrainedSize for the yogaRoot when escalating manually.
|
||||
[yogaRoot calculateLayoutFromYogaRoot:ASSizeRangeUnconstrained];
|
||||
return;
|
||||
}
|
||||
|
||||
if (ASSizeRangeEqualToSizeRange(rootConstrainedSize, ASSizeRangeUnconstrained)) {
|
||||
rootConstrainedSize = [self _locked_constrainedSizeForLayoutPass];
|
||||
}
|
||||
|
||||
[self willCalculateLayout:rootConstrainedSize];
|
||||
[self enumerateInterfaceStateDelegates:^(id<ASInterfaceStateDelegate> _Nonnull delegate) {
|
||||
if ([delegate respondsToSelector:@selector(nodeWillCalculateLayout:)]) {
|
||||
[delegate nodeWillCalculateLayout:rootConstrainedSize];
|
||||
}
|
||||
}];
|
||||
|
||||
// Prepare all children for the layout pass with the current Yoga tree configuration.
|
||||
ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode *_Nonnull node) {
|
||||
node.yogaLayoutInProgress = YES;
|
||||
ASDisplayNode *yogaParent = node.yogaParent;
|
||||
if (yogaParent) {
|
||||
node.style.parentAlignStyle = yogaParent.style.alignItems;
|
||||
} else {
|
||||
node.style.parentAlignStyle = ASStackLayoutAlignItemsNotSet;
|
||||
};
|
||||
});
|
||||
|
||||
ASYogaLog("CALCULATING at Yoga root with constraint = {%@, %@}: %@",
|
||||
NSStringFromCGSize(rootConstrainedSize.min), NSStringFromCGSize(rootConstrainedSize.max), self);
|
||||
|
||||
YGNodeRef rootYogaNode = self.style.yogaNode;
|
||||
|
||||
// Apply the constrainedSize as a base, known frame of reference.
|
||||
// If the root node also has style.*Size set, these will be overridden below.
|
||||
// YGNodeCalculateLayout currently doesn't offer the ability to pass a minimum size (max is passed there).
|
||||
|
||||
// TODO(appleguy): Reconcile the self.style.*Size properties with rootConstrainedSize
|
||||
YGNodeStyleSetMinWidth (rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.min.width));
|
||||
YGNodeStyleSetMinHeight(rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.min.height));
|
||||
|
||||
// It is crucial to use yogaFloat... to convert CGFLOAT_MAX into YGUndefined here.
|
||||
YGNodeCalculateLayout(rootYogaNode,
|
||||
yogaFloatForCGFloat(rootConstrainedSize.max.width),
|
||||
yogaFloatForCGFloat(rootConstrainedSize.max.height),
|
||||
YGDirectionInherit);
|
||||
|
||||
// Reset accessible elements, since layout may have changed.
|
||||
ASPerformBlockOnMainThread(^{
|
||||
if (self.nodeLoaded && !self.isSynchronous) {
|
||||
[(_ASDisplayView *)self.view setAccessibilityElements:nil];
|
||||
}
|
||||
});
|
||||
|
||||
ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) {
|
||||
[node setupYogaCalculatedLayout];
|
||||
node.yogaLayoutInProgress = NO;
|
||||
});
|
||||
|
||||
#if YOGA_LAYOUT_LOGGING /* YOGA_LAYOUT_LOGGING */
|
||||
// Concurrent layouts will interleave the NSLog messages unless we serialize.
|
||||
// Use @synchornize rather than trampolining to the main thread so the tree state isn't changed.
|
||||
@synchronized ([ASDisplayNode class]) {
|
||||
NSLog(@"****************************************************************************");
|
||||
NSLog(@"******************** STARTING YOGA -> ASLAYOUT CREATION ********************");
|
||||
NSLog(@"****************************************************************************");
|
||||
ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) {
|
||||
NSLog(@"node = %@", node);
|
||||
YGNodePrint(node.style.yogaNode, (YGPrintOptions)(YGPrintOptionsStyle | YGPrintOptionsLayout));
|
||||
NSCAssert(ASIsCGSizeValidForSize(node.yogaCalculatedLayout.size), @"Yoga layout returned an invalid size");
|
||||
NSLog(@" "); // Newline
|
||||
});
|
||||
}
|
||||
#endif /* YOGA_LAYOUT_LOGGING */
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - ASDisplayNode (YogaLocking)
|
||||
|
||||
@implementation ASDisplayNode (YogaLocking)
|
||||
|
||||
- (ASLockSet)lockToRootIfNeededForLayout {
|
||||
ASLockSet lockSet = ASLockSequence(^BOOL(ASAddLockBlock addLock) {
|
||||
if (!addLock(self)) {
|
||||
return NO;
|
||||
}
|
||||
#if YOGA
|
||||
if (![self locked_shouldLayoutFromYogaRoot]) {
|
||||
return YES;
|
||||
}
|
||||
if (self.nodeController && !addLock(self.nodeController)) {
|
||||
return NO;
|
||||
}
|
||||
ASDisplayNode *parent = _supernode;
|
||||
while (parent) {
|
||||
if (!addLock(parent)) {
|
||||
return NO;
|
||||
}
|
||||
if (parent.nodeController && !addLock(parent.nodeController)) {
|
||||
return NO;
|
||||
}
|
||||
parent = parent->_supernode;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
});
|
||||
return lockSet;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASDisplayNode (YogaDebugging)
|
||||
|
||||
- (NSString *)yogaTreeDescription {
|
||||
return [self _yogaTreeDescription:@""];
|
||||
}
|
||||
|
||||
- (NSString *)_yogaTreeDescription:(NSString *)indent {
|
||||
auto subtree = [NSMutableString stringWithFormat:@"%@%@\n", indent, self.description];
|
||||
for (ASDisplayNode *n in self.yogaChildren) {
|
||||
[subtree appendString:[n _yogaTreeDescription:[indent stringByAppendingString:@"| "]]];
|
||||
}
|
||||
return subtree;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif /* YOGA */
|
File diff suppressed because it is too large
Load Diff
@ -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 <QuartzCore/QuartzCore.h>
|
||||
|
||||
@interface ASDisplayNodeCornerLayerDelegate : NSObject <CALayerDelegate>
|
||||
@end
|
@ -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<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
|
||||
{
|
||||
return (id)kCFNull;
|
||||
}
|
||||
|
||||
@end
|
@ -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 <Foundation/Foundation.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
|
||||
@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
|
@ -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
|
@ -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 <AsyncDisplayKit/ASAvailability.h>
|
||||
|
||||
#if AS_IG_LIST_KIT
|
||||
|
||||
#import <IGListKit/IGListKit.h>
|
||||
#import <AsyncDisplayKit/ASCollectionView.h>
|
||||
#import <AsyncDisplayKit/ASCollectionNode.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
AS_SUBCLASSING_RESTRICTED
|
||||
@interface ASIGListAdapterBasedDataSource : NSObject <ASCollectionDataSourceInterop, ASCollectionDelegateInterop, ASCollectionDelegateFlowLayout>
|
||||
|
||||
- (instancetype)initWithListAdapter:(IGListAdapter *)listAdapter collectionDelegate:(nullable id<ASCollectionDelegate>)collectionDelegate;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -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 <AsyncDisplayKit/ASAvailability.h>
|
||||
|
||||
#if AS_IG_LIST_KIT
|
||||
|
||||
#import "ASIGListAdapterBasedDataSource.h"
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
typedef IGListSectionController<ASSectionController> 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 <IGListSupplementaryViewSource, ASSupplementaryNodeSource>
|
||||
@end
|
||||
|
||||
@interface ASIGListAdapterBasedDataSource ()
|
||||
@property (nonatomic, weak, readonly) IGListAdapter *listAdapter;
|
||||
@property (nonatomic, readonly) id<UICollectionViewDelegateFlowLayout> delegate;
|
||||
@property (nonatomic, readonly) id<UICollectionViewDataSource> dataSource;
|
||||
@property (nonatomic, weak, readonly) id<ASCollectionDelegate> 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<ASCollectionDelegate>)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<UICollectionViewDataSource>)dataSource
|
||||
{
|
||||
return (id<UICollectionViewDataSource>)_listAdapter;
|
||||
}
|
||||
|
||||
- (id<UICollectionViewDelegateFlowLayout>)delegate
|
||||
{
|
||||
return (id<UICollectionViewDelegateFlowLayout>)_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<ASIGSupplementaryNodeSource> 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<ASIGSupplementaryNodeSource> 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<ASSupplementaryNodeSource> 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<ASSupplementaryNodeSource> 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<NSString *> *)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<ASIGSupplementaryNodeSource>)supplementaryElementSourceForSection:(NSInteger)section
|
||||
{
|
||||
ASIGSectionController *ctrl = [self sectionControllerForSection:section];
|
||||
id<ASIGSupplementaryNodeSource> src = (id<ASIGSupplementaryNodeSource>)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<IGListUpdatingDelegate>)updater
|
||||
{
|
||||
// Cast to NSObject will be removed after https://github.com/Instagram/IGListKit/pull/435
|
||||
if ([(id<NSObject>)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<Class, NSValue *> *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<Class, NSValue *> *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
|
@ -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 <AsyncDisplayKit/ASImageNode.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||
#import <AsyncDisplayKit/ASEqualityHelpers.h>
|
||||
#import <AsyncDisplayKit/ASImageNode+Private.h>
|
||||
#import <AsyncDisplayKit/ASImageNode+AnimatedImagePrivate.h>
|
||||
#import <AsyncDisplayKit/ASImageProtocols.h>
|
||||
#import "Private/ASInternalHelpers.h"
|
||||
#import <AsyncDisplayKit/ASNetworkImageNode.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
#import <AsyncDisplayKit/ASWeakProxy.h>
|
||||
|
||||
#define ASAnimatedImageDebug 0
|
||||
|
||||
#ifndef MINIMAL_ASDK
|
||||
@interface ASNetworkImageNode (Private)
|
||||
- (void)_locked_setDefaultImage:(UIImage *)image;
|
||||
@end
|
||||
#endif
|
||||
|
||||
|
||||
@implementation ASImageNode (AnimatedImage)
|
||||
|
||||
#pragma mark - GIF support
|
||||
|
||||
- (void)setAnimatedImage:(id <ASAnimatedImageProtocol>)animatedImage
|
||||
{
|
||||
ASLockScopeSelf();
|
||||
[self _locked_setAnimatedImage:animatedImage];
|
||||
}
|
||||
|
||||
- (void)_locked_setAnimatedImage:(id <ASAnimatedImageProtocol>)animatedImage
|
||||
{
|
||||
ASAssertLocked(__instanceLock__);
|
||||
|
||||
if (ASObjectIsEqual(_animatedImage, animatedImage) && (animatedImage == nil || animatedImage.playbackReady)) {
|
||||
return;
|
||||
}
|
||||
|
||||
__block id <ASAnimatedImageProtocol> previousAnimatedImage = _animatedImage;
|
||||
|
||||
_animatedImage = animatedImage;
|
||||
|
||||
if (animatedImage != nil) {
|
||||
__weak ASImageNode *weakSelf = self;
|
||||
if ([animatedImage respondsToSelector:@selector(setCoverImageReadyCallback:)]) {
|
||||
animatedImage.coverImageReadyCallback = ^(UIImage *coverImage) {
|
||||
// In this case the lock is already gone we have to call the unlocked version therefore
|
||||
[weakSelf setCoverImageCompleted:coverImage];
|
||||
};
|
||||
}
|
||||
|
||||
animatedImage.playbackReadyCallback = ^{
|
||||
// In this case the lock is already gone we have to call the unlocked version therefore
|
||||
[weakSelf setShouldAnimate:YES];
|
||||
};
|
||||
if (animatedImage.playbackReady) {
|
||||
[self _locked_setShouldAnimate:YES];
|
||||
}
|
||||
} else {
|
||||
// Clean up after ourselves.
|
||||
|
||||
// Don't bother using a `_locked` version for setting contnst as it should be pretty safe calling it with
|
||||
// reaquire the lock and would add overhead to introduce this version
|
||||
self.contents = nil;
|
||||
[self _locked_setCoverImage:nil];
|
||||
}
|
||||
|
||||
// Push calling subclass to the next runloop cycle
|
||||
// We have to schedule the block on the common modes otherwise the tracking mode will not be included and it will
|
||||
// not fire e.g. while scrolling down
|
||||
CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopCommonModes, ^(void) {
|
||||
[self animatedImageSet:animatedImage previousAnimatedImage:previousAnimatedImage];
|
||||
|
||||
// Animated image can take while to dealloc, do it off the main queue
|
||||
if (previousAnimatedImage != nil) {
|
||||
ASPerformBackgroundDeallocation(&previousAnimatedImage);
|
||||
}
|
||||
});
|
||||
// Don't need to wakeup the runloop as the current is already running
|
||||
// CFRunLoopWakeUp(runLoop); // Should not be necessary
|
||||
}
|
||||
|
||||
- (void)animatedImageSet:(id <ASAnimatedImageProtocol>)newAnimatedImage previousAnimatedImage:(id <ASAnimatedImageProtocol>)previousAnimatedImage
|
||||
{
|
||||
// Subclass hook should not be called with the lock held
|
||||
ASAssertUnlocked(__instanceLock__);
|
||||
|
||||
// Subclasses may override
|
||||
}
|
||||
|
||||
- (id <ASAnimatedImageProtocol>)animatedImage
|
||||
{
|
||||
ASLockScopeSelf();
|
||||
return _animatedImage;
|
||||
}
|
||||
|
||||
- (void)setAnimatedImagePaused:(BOOL)animatedImagePaused
|
||||
{
|
||||
ASLockScopeSelf();
|
||||
|
||||
_animatedImagePaused = animatedImagePaused;
|
||||
|
||||
[self _locked_setShouldAnimate:!animatedImagePaused];
|
||||
}
|
||||
|
||||
- (BOOL)animatedImagePaused
|
||||
{
|
||||
ASLockScopeSelf();
|
||||
return _animatedImagePaused;
|
||||
}
|
||||
|
||||
- (void)setCoverImageCompleted:(UIImage *)coverImage
|
||||
{
|
||||
if (ASInterfaceStateIncludesDisplay(self.interfaceState)) {
|
||||
ASLockScopeSelf();
|
||||
[self _locked_setCoverImageCompleted:coverImage];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_locked_setCoverImageCompleted:(UIImage *)coverImage
|
||||
{
|
||||
ASAssertLocked(__instanceLock__);
|
||||
|
||||
_displayLinkLock.lock();
|
||||
BOOL setCoverImage = (_displayLink == nil) || _displayLink.paused;
|
||||
_displayLinkLock.unlock();
|
||||
|
||||
if (setCoverImage) {
|
||||
[self _locked_setCoverImage:coverImage];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setCoverImage:(UIImage *)coverImage
|
||||
{
|
||||
ASLockScopeSelf();
|
||||
[self _locked_setCoverImage:coverImage];
|
||||
}
|
||||
|
||||
- (void)_locked_setCoverImage:(UIImage *)coverImage
|
||||
{
|
||||
ASAssertLocked(__instanceLock__);
|
||||
|
||||
//If we're a network image node, we want to set the default image so
|
||||
//that it will correctly be restored if it exits the range.
|
||||
#ifndef MINIMAL_ASDK
|
||||
if ([self isKindOfClass:[ASNetworkImageNode class]]) {
|
||||
[(ASNetworkImageNode *)self _locked_setDefaultImage:coverImage];
|
||||
} else if (_displayLink == nil || _displayLink.paused == YES) {
|
||||
[self _locked_setImage:coverImage];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (NSString *)animatedImageRunLoopMode
|
||||
{
|
||||
AS::MutexLocker l(_displayLinkLock);
|
||||
return _animatedImageRunLoopMode;
|
||||
}
|
||||
|
||||
- (void)setAnimatedImageRunLoopMode:(NSString *)runLoopMode
|
||||
{
|
||||
AS::MutexLocker l(_displayLinkLock);
|
||||
|
||||
if (runLoopMode == nil) {
|
||||
runLoopMode = ASAnimatedImageDefaultRunLoopMode;
|
||||
}
|
||||
|
||||
if (_displayLink != nil) {
|
||||
[_displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:_animatedImageRunLoopMode];
|
||||
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:runLoopMode];
|
||||
}
|
||||
_animatedImageRunLoopMode = [runLoopMode copy];
|
||||
}
|
||||
|
||||
- (void)setShouldAnimate:(BOOL)shouldAnimate
|
||||
{
|
||||
ASLockScopeSelf();
|
||||
[self _locked_setShouldAnimate:shouldAnimate];
|
||||
}
|
||||
|
||||
- (void)_locked_setShouldAnimate:(BOOL)shouldAnimate
|
||||
{
|
||||
ASAssertLocked(__instanceLock__);
|
||||
|
||||
// This test is explicitly done and not ASPerformBlockOnMainThread as this would perform the block immediately
|
||||
// on main if called on main thread and we have to call methods locked or unlocked based on which thread we are on
|
||||
if (ASDisplayNodeThreadIsMain()) {
|
||||
if (shouldAnimate) {
|
||||
[self _locked_startAnimating];
|
||||
} else {
|
||||
[self _locked_stopAnimating];
|
||||
}
|
||||
} else {
|
||||
// We have to dispatch to the main thread and call the regular methods as the lock is already gone if the
|
||||
// block is called
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (shouldAnimate) {
|
||||
[self startAnimating];
|
||||
} else {
|
||||
[self stopAnimating];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Animating
|
||||
|
||||
- (void)startAnimating
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
ASLockScopeSelf();
|
||||
[self _locked_startAnimating];
|
||||
}
|
||||
|
||||
- (void)_locked_startAnimating
|
||||
{
|
||||
ASAssertLocked(__instanceLock__);
|
||||
|
||||
// It should be safe to call self.interfaceState in this case as it will only grab the lock of the superclass
|
||||
if (!ASInterfaceStateIncludesVisible(self.interfaceState)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_animatedImagePaused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_animatedImage.playbackReady == NO) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if ASAnimatedImageDebug
|
||||
NSLog(@"starting animation: %p", self);
|
||||
#endif
|
||||
|
||||
// Get frame interval before holding display link lock to avoid deadlock
|
||||
NSUInteger frameInterval = self.animatedImage.frameInterval;
|
||||
AS::MutexLocker l(_displayLinkLock);
|
||||
if (_displayLink == nil) {
|
||||
_playHead = 0;
|
||||
_displayLink = [CADisplayLink displayLinkWithTarget:[ASWeakProxy weakProxyWithTarget:self] selector:@selector(displayLinkFired:)];
|
||||
_displayLink.frameInterval = frameInterval;
|
||||
_lastSuccessfulFrameIndex = NSUIntegerMax;
|
||||
|
||||
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:_animatedImageRunLoopMode];
|
||||
} else {
|
||||
_displayLink.paused = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopAnimating
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
ASLockScopeSelf();
|
||||
[self _locked_stopAnimating];
|
||||
}
|
||||
|
||||
- (void)_locked_stopAnimating
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASAssertLocked(__instanceLock__);
|
||||
|
||||
#if ASAnimatedImageDebug
|
||||
NSLog(@"stopping animation: %p", self);
|
||||
#endif
|
||||
ASDisplayNodeAssertMainThread();
|
||||
AS::MutexLocker l(_displayLinkLock);
|
||||
_displayLink.paused = YES;
|
||||
self.lastDisplayLinkFire = 0;
|
||||
|
||||
[_animatedImage clearAnimatedImageCache];
|
||||
}
|
||||
|
||||
#pragma mark - ASDisplayNode
|
||||
|
||||
- (void)didEnterVisibleState
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[super didEnterVisibleState];
|
||||
|
||||
if (self.animatedImage.coverImageReady) {
|
||||
[self setCoverImage:self.animatedImage.coverImage];
|
||||
}
|
||||
if (self.animatedImage.playbackReady) {
|
||||
[self startAnimating];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didExitVisibleState
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[super didExitVisibleState];
|
||||
|
||||
[self stopAnimating];
|
||||
}
|
||||
|
||||
- (void)didExitDisplayState
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
#if ASAnimatedImageDebug
|
||||
NSLog(@"exiting display state: %p", self);
|
||||
#endif
|
||||
|
||||
// Check to see if we're an animated image before calling super in case someone
|
||||
// decides they want to clear out the animatedImage itself on exiting the display
|
||||
// state
|
||||
BOOL isAnimatedImage = self.animatedImage != nil;
|
||||
[super didExitDisplayState];
|
||||
|
||||
// Also clear out the contents we've set to be good citizens, we'll put it back in when we become visible.
|
||||
if (isAnimatedImage) {
|
||||
self.contents = nil;
|
||||
[self setCoverImage:nil];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Display Link Callbacks
|
||||
|
||||
- (void)displayLinkFired:(CADisplayLink *)displayLink
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
CFTimeInterval timeBetweenLastFire;
|
||||
if (self.lastDisplayLinkFire == 0) {
|
||||
timeBetweenLastFire = 0;
|
||||
} else if (AS_AVAILABLE_IOS_TVOS(10, 10)) {
|
||||
timeBetweenLastFire = displayLink.targetTimestamp - displayLink.timestamp;
|
||||
} else {
|
||||
timeBetweenLastFire = CACurrentMediaTime() - self.lastDisplayLinkFire;
|
||||
}
|
||||
self.lastDisplayLinkFire = CACurrentMediaTime();
|
||||
|
||||
_playHead += timeBetweenLastFire;
|
||||
|
||||
while (_playHead > self.animatedImage.totalDuration) {
|
||||
// Set playhead to zero to keep from showing different frames on different playthroughs
|
||||
_playHead = 0;
|
||||
_playedLoops++;
|
||||
}
|
||||
|
||||
if (self.animatedImage.loopCount > 0 && _playedLoops >= self.animatedImage.loopCount) {
|
||||
[self stopAnimating];
|
||||
return;
|
||||
}
|
||||
|
||||
NSUInteger frameIndex = [self frameIndexAtPlayHeadPosition:_playHead];
|
||||
if (frameIndex == _lastSuccessfulFrameIndex) {
|
||||
return;
|
||||
}
|
||||
CGImageRef frameImage = [self.animatedImage imageAtIndex:frameIndex];
|
||||
|
||||
if (frameImage == nil) {
|
||||
//Pause the display link until we get a file ready notification
|
||||
displayLink.paused = YES;
|
||||
self.lastDisplayLinkFire = 0;
|
||||
} else {
|
||||
self.contents = (__bridge id)frameImage;
|
||||
_lastSuccessfulFrameIndex = frameIndex;
|
||||
[self displayDidFinish];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSUInteger)frameIndexAtPlayHeadPosition:(CFTimeInterval)playHead
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
NSUInteger frameIndex = 0;
|
||||
for (NSUInteger durationIndex = 0; durationIndex < self.animatedImage.frameCount; durationIndex++) {
|
||||
playHead -= [self.animatedImage durationAtIndex:durationIndex];
|
||||
if (playHead < 0) {
|
||||
return frameIndex;
|
||||
}
|
||||
frameIndex++;
|
||||
}
|
||||
|
||||
return frameIndex;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - ASImageNode(AnimatedImageInvalidation)
|
||||
|
||||
@implementation ASImageNode(AnimatedImageInvalidation)
|
||||
|
||||
- (void)invalidateAnimatedImage
|
||||
{
|
||||
AS::MutexLocker l(_displayLinkLock);
|
||||
#if ASAnimatedImageDebug
|
||||
if (_displayLink) {
|
||||
NSLog(@"invalidating display link");
|
||||
}
|
||||
#endif
|
||||
[_displayLink invalidate];
|
||||
_displayLink = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user