mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-10 16:29:55 +00:00
[Layout] Extract layout implementation code into it's own subcategories (#272)
* Extract layout code into ASDisplayNode categories * Category renaming and documentation * Changelog * Change header
This commit is contained in:
parent
299df0aa8c
commit
b32e69d64b
@ -157,6 +157,7 @@
|
|||||||
698DFF441E36B6C9002891F1 /* ASStackLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 698DFF431E36B6C9002891F1 /* ASStackLayoutSpecUtilities.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
698DFF441E36B6C9002891F1 /* ASStackLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 698DFF431E36B6C9002891F1 /* ASStackLayoutSpecUtilities.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||||
698DFF471E36B7E9002891F1 /* ASLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 698DFF461E36B7E9002891F1 /* ASLayoutSpecUtilities.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
698DFF471E36B7E9002891F1 /* ASLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 698DFF461E36B7E9002891F1 /* ASLayoutSpecUtilities.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||||
69B225671D72535E00B25B22 /* ASDisplayNodeLayoutTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69B225661D72535E00B25B22 /* ASDisplayNodeLayoutTests.mm */; };
|
69B225671D72535E00B25B22 /* ASDisplayNodeLayoutTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69B225661D72535E00B25B22 /* ASDisplayNodeLayoutTests.mm */; };
|
||||||
|
69BCE3D91EC6513B007DCCAD /* ASDisplayNode+Layout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69BCE3D71EC6513B007DCCAD /* ASDisplayNode+Layout.mm */; };
|
||||||
69CB62AC1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */ = {isa = PBXBuildFile; fileRef = 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
69CB62AC1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */ = {isa = PBXBuildFile; fileRef = 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||||
69CB62AE1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */; };
|
69CB62AE1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */; };
|
||||||
69E0E8A71D356C9400627613 /* ASEqualityHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
69E0E8A71D356C9400627613 /* ASEqualityHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
@ -649,6 +650,7 @@
|
|||||||
699B83501E3C1BA500433FA4 /* ASLayoutSpecTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASLayoutSpecTests.m; sourceTree = "<group>"; };
|
699B83501E3C1BA500433FA4 /* ASLayoutSpecTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASLayoutSpecTests.m; sourceTree = "<group>"; };
|
||||||
69B225661D72535E00B25B22 /* ASDisplayNodeLayoutTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNodeLayoutTests.mm; sourceTree = "<group>"; };
|
69B225661D72535E00B25B22 /* ASDisplayNodeLayoutTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNodeLayoutTests.mm; sourceTree = "<group>"; };
|
||||||
69B225681D7265DA00B25B22 /* ASXCTExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASXCTExtensions.h; sourceTree = "<group>"; };
|
69B225681D7265DA00B25B22 /* ASXCTExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASXCTExtensions.h; sourceTree = "<group>"; };
|
||||||
|
69BCE3D71EC6513B007DCCAD /* ASDisplayNode+Layout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASDisplayNode+Layout.mm"; sourceTree = "<group>"; };
|
||||||
69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASDisplayViewAccessiblity.h; sourceTree = "<group>"; };
|
69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASDisplayViewAccessiblity.h; sourceTree = "<group>"; };
|
||||||
69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASDisplayViewAccessiblity.mm; sourceTree = "<group>"; };
|
69CB62AA1CB8165900024920 /* _ASDisplayViewAccessiblity.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASDisplayViewAccessiblity.mm; sourceTree = "<group>"; };
|
||||||
69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASRangeControllerUpdateRangeProtocol+Beta.h"; sourceTree = "<group>"; };
|
69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASRangeControllerUpdateRangeProtocol+Beta.h"; sourceTree = "<group>"; };
|
||||||
@ -1017,6 +1019,7 @@
|
|||||||
058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */,
|
058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */,
|
||||||
CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */,
|
CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */,
|
||||||
CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */,
|
CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */,
|
||||||
|
69BCE3D71EC6513B007DCCAD /* ASDisplayNode+Layout.mm */,
|
||||||
058D09DB195D050800B7D73C /* ASDisplayNodeExtras.h */,
|
058D09DB195D050800B7D73C /* ASDisplayNodeExtras.h */,
|
||||||
058D09DC195D050800B7D73C /* ASDisplayNodeExtras.mm */,
|
058D09DC195D050800B7D73C /* ASDisplayNodeExtras.mm */,
|
||||||
0587F9BB1A7309ED00AFF0BA /* ASEditableTextNode.h */,
|
0587F9BB1A7309ED00AFF0BA /* ASEditableTextNode.h */,
|
||||||
@ -2171,6 +2174,7 @@
|
|||||||
CCA282C91E9EB64B0037E8B7 /* ASDisplayNodeTipState.m in Sources */,
|
CCA282C91E9EB64B0037E8B7 /* ASDisplayNodeTipState.m in Sources */,
|
||||||
509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */,
|
509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */,
|
||||||
B35062091B010EFD0018CF92 /* ASScrollNode.mm in Sources */,
|
B35062091B010EFD0018CF92 /* ASScrollNode.mm in Sources */,
|
||||||
|
69BCE3D91EC6513B007DCCAD /* ASDisplayNode+Layout.mm in Sources */,
|
||||||
8BDA5FC81CDBDF95007D13B2 /* ASVideoPlayerNode.mm in Sources */,
|
8BDA5FC81CDBDF95007D13B2 /* ASVideoPlayerNode.mm in Sources */,
|
||||||
E54E81FD1EB357BD00FFE8E1 /* ASPageTable.m in Sources */,
|
E54E81FD1EB357BD00FFE8E1 /* ASPageTable.m in Sources */,
|
||||||
34EFC7721B701D0300AD841F /* ASStackLayoutSpec.mm in Sources */,
|
34EFC7721B701D0300AD841F /* ASStackLayoutSpec.mm in Sources */,
|
||||||
|
|||||||
@ -21,3 +21,4 @@
|
|||||||
- [ASTextNode] Add an experimental new implementation. See `+[ASTextNode setExperimentOptions:]`. [Adlai Holler](https://github.com/Adlai-Holler)[#259](https://github.com/TextureGroup/Texture/pull/259)
|
- [ASTextNode] Add an experimental new implementation. See `+[ASTextNode setExperimentOptions:]`. [Adlai Holler](https://github.com/Adlai-Holler)[#259](https://github.com/TextureGroup/Texture/pull/259)
|
||||||
- [ASVideoNode] Added error reporing to ASVideoNode and it's delegate [#260](https://github.com/TextureGroup/Texture/pull/260)
|
- [ASVideoNode] Added error reporing to ASVideoNode and it's delegate [#260](https://github.com/TextureGroup/Texture/pull/260)
|
||||||
- [ASCollectionNode] Fixed conversion of item index paths between node & view. [Adlai Holler](https://github.com/Adlai-Holler) [#262](https://github.com/TextureGroup/Texture/pull/262)
|
- [ASCollectionNode] Fixed conversion of item index paths between node & view. [Adlai Holler](https://github.com/Adlai-Holler) [#262](https://github.com/TextureGroup/Texture/pull/262)
|
||||||
|
- [Layout] Extract layout implementation code into it's own subcategories [Michael Schneider] (https://github.com/maicki)[#272](https://github.com/TextureGroup/Texture/pull/272)
|
||||||
|
|||||||
908
Source/ASDisplayNode+Layout.mm
Normal file
908
Source/ASDisplayNode+Layout.mm
Normal file
@ -0,0 +1,908 @@
|
|||||||
|
//
|
||||||
|
// ASDisplayNode+Layout.mm
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||||
|
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||||
|
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||||
|
#import <AsyncDisplayKit/ASLayout.h>
|
||||||
|
#import <AsyncDisplayKit/ASLayoutElementStylePrivate.h>
|
||||||
|
|
||||||
|
#import <AsyncDisplayKit/ASDisplayNode+FrameworkSubclasses.h>
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark - ASDisplayNode (ASLayoutElement)
|
||||||
|
|
||||||
|
@implementation ASDisplayNode (ASLayoutElement)
|
||||||
|
|
||||||
|
#pragma mark <ASLayoutElement>
|
||||||
|
|
||||||
|
- (ASLayoutElementStyle *)style
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
if (_style == nil) {
|
||||||
|
_style = [[ASLayoutElementStyle alloc] init];
|
||||||
|
}
|
||||||
|
return _style;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (ASLayoutElementType)layoutElementType
|
||||||
|
{
|
||||||
|
return ASLayoutElementTypeDisplayNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSArray<id<ASLayoutElement>> *)sublayoutElements
|
||||||
|
{
|
||||||
|
return self.subnodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark Measurement Pass
|
||||||
|
|
||||||
|
- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize
|
||||||
|
{
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
|
// For now we just call the deprecated measureWithSizeRange: method to not break old API
|
||||||
|
return [self measureWithSizeRange:constrainedSize];
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
}
|
||||||
|
|
||||||
|
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize
|
||||||
|
{
|
||||||
|
return [self layoutThatFits:constrainedSize parentSize:constrainedSize.max];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
|
||||||
|
// If one or multiple layout transitions are in flight it still can happen that layout information is requested
|
||||||
|
// on other threads. As the pending and calculated layout to be updated in the layout transition in here just a
|
||||||
|
// layout calculation wil be performed without side effect
|
||||||
|
if ([self _isLayoutTransitionInvalid]) {
|
||||||
|
return [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_calculatedDisplayNodeLayout->isValidForConstrainedSizeParentSize(constrainedSize, parentSize)) {
|
||||||
|
ASDisplayNodeAssertNotNil(_calculatedDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _calculatedDisplayNodeLayout->layout should not be nil! %@", self);
|
||||||
|
// Our calculated layout is suitable for this constrainedSize, so keep using it and
|
||||||
|
// invalidate any pending layout that has been generated in the past.
|
||||||
|
_pendingDisplayNodeLayout = nullptr;
|
||||||
|
return _calculatedDisplayNodeLayout->layout ?: [ASLayout layoutWithLayoutElement:self size:{0, 0}];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a pending display node layout for the layout pass
|
||||||
|
_pendingDisplayNodeLayout = std::make_shared<ASDisplayNodeLayout>(
|
||||||
|
[self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize],
|
||||||
|
constrainedSize,
|
||||||
|
parentSize
|
||||||
|
);
|
||||||
|
|
||||||
|
ASDisplayNodeAssertNotNil(_pendingDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _pendingDisplayNodeLayout->layout should not be nil! %@", self);
|
||||||
|
return _pendingDisplayNodeLayout->layout ?: [ASLayout layoutWithLayoutElement:self size:{0, 0}];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark ASLayoutElementStyleExtensibility
|
||||||
|
|
||||||
|
ASLayoutElementStyleExtensibilityForwarding
|
||||||
|
|
||||||
|
#pragma mark ASPrimitiveTraitCollection
|
||||||
|
|
||||||
|
- (ASPrimitiveTraitCollection)primitiveTraitCollection
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
return _primitiveTraitCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traitCollection
|
||||||
|
{
|
||||||
|
__instanceLock__.lock();
|
||||||
|
if (ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(traitCollection, _primitiveTraitCollection) == NO) {
|
||||||
|
_primitiveTraitCollection = traitCollection;
|
||||||
|
ASDisplayNodeLogEvent(self, @"asyncTraitCollectionDidChange: %@", NSStringFromASPrimitiveTraitCollection(traitCollection));
|
||||||
|
__instanceLock__.unlock();
|
||||||
|
|
||||||
|
[self asyncTraitCollectionDidChange];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
__instanceLock__.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
- (ASTraitCollection *)asyncTraitCollection
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
return [ASTraitCollection traitCollectionWithASPrimitiveTraitCollection:self.primitiveTraitCollection];
|
||||||
|
}
|
||||||
|
|
||||||
|
ASPrimitiveTraitCollectionDeprecatedImplementation
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark - ASLayoutElementAsciiArtProtocol
|
||||||
|
|
||||||
|
@implementation ASDisplayNode (ASLayoutElementAsciiArtProtocol)
|
||||||
|
|
||||||
|
- (NSString *)asciiArtString
|
||||||
|
{
|
||||||
|
return [ASLayoutSpec asciiArtStringForChildren:@[] parentName:[self asciiArtName]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)asciiArtName
|
||||||
|
{
|
||||||
|
NSString *string = NSStringFromClass([self class]);
|
||||||
|
if (_debugName) {
|
||||||
|
string = [string stringByAppendingString:[NSString stringWithFormat:@"\"%@\"",_debugName]];
|
||||||
|
}
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark - ASDisplayNode (ASLayout)
|
||||||
|
|
||||||
|
@implementation ASDisplayNode (ASLayout)
|
||||||
|
|
||||||
|
- (void)setLayoutSpecBlock:(ASLayoutSpecBlock)layoutSpecBlock
|
||||||
|
{
|
||||||
|
// For now there should never be an override of layoutSpecThatFits: / layoutElementThatFits: and a layoutSpecBlock
|
||||||
|
ASDisplayNodeAssert(!(_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits), @"Overwriting layoutSpecThatFits: and providing a layoutSpecBlock block is currently not supported");
|
||||||
|
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
_layoutSpecBlock = layoutSpecBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (ASLayoutSpecBlock)layoutSpecBlock
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
return _layoutSpecBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (ASLayout *)calculatedLayout
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
return _calculatedDisplayNodeLayout->layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CGSize)calculatedSize
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
if (_pendingDisplayNodeLayout != nullptr) {
|
||||||
|
return _pendingDisplayNodeLayout->layout.size;
|
||||||
|
}
|
||||||
|
return _calculatedDisplayNodeLayout->layout.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (ASSizeRange)constrainedSizeForCalculatedLayout
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
if (_pendingDisplayNodeLayout != nullptr) {
|
||||||
|
return _pendingDisplayNodeLayout->constrainedSize;
|
||||||
|
}
|
||||||
|
return _calculatedDisplayNodeLayout->constrainedSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark - ASDisplayNode (ASLayoutElementStylability)
|
||||||
|
|
||||||
|
@implementation ASDisplayNode (ASLayoutElementStylability)
|
||||||
|
|
||||||
|
- (instancetype)styledWithBlock:(AS_NOESCAPE void (^)(__kindof ASLayoutElementStyle *style))styleBlock
|
||||||
|
{
|
||||||
|
styleBlock(self.style);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark - ASDisplayNode (ASLayoutInternal)
|
||||||
|
|
||||||
|
@implementation ASDisplayNode (ASLayoutInternal)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Informs the root node that the intrinsic size of the receiver is no longer valid.
|
||||||
|
*
|
||||||
|
* @discussion The size of a root node is determined by each subnode. Calling invalidateSize will let the root node know
|
||||||
|
* that the intrinsic size of the receiver node is no longer valid and a resizing of the root node needs to happen.
|
||||||
|
*/
|
||||||
|
- (void)_setNeedsLayoutFromAbove
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertThreadAffinity(self);
|
||||||
|
|
||||||
|
// Mark the node for layout in the next layout pass
|
||||||
|
[self setNeedsLayout];
|
||||||
|
|
||||||
|
__instanceLock__.lock();
|
||||||
|
// Escalate to the root; entire tree must allow adjustments so the layout fits the new child.
|
||||||
|
// Much of the layout will be re-used as cached (e.g. other items in an unconstrained stack)
|
||||||
|
ASDisplayNode *supernode = _supernode;
|
||||||
|
__instanceLock__.unlock();
|
||||||
|
|
||||||
|
if (supernode) {
|
||||||
|
// Threading model requires that we unlock before calling a method on our parent.
|
||||||
|
[supernode _setNeedsLayoutFromAbove];
|
||||||
|
} else {
|
||||||
|
// Let the root node method know that the size was invalidated
|
||||||
|
[self _rootNodeDidInvalidateSize];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_rootNodeDidInvalidateSize
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertThreadAffinity(self);
|
||||||
|
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__);
|
||||||
|
|
||||||
|
__instanceLock__.lock();
|
||||||
|
|
||||||
|
// We are the root node and need to re-flow the layout; at least one child needs a new size.
|
||||||
|
CGSize boundsSizeForLayout = ASCeilSizeValues(self.bounds.size);
|
||||||
|
|
||||||
|
// Figure out constrainedSize to use
|
||||||
|
ASSizeRange constrainedSize = ASSizeRangeMake(boundsSizeForLayout);
|
||||||
|
if (_pendingDisplayNodeLayout != nullptr) {
|
||||||
|
constrainedSize = _pendingDisplayNodeLayout->constrainedSize;
|
||||||
|
} else if (_calculatedDisplayNodeLayout->layout != nil) {
|
||||||
|
constrainedSize = _calculatedDisplayNodeLayout->constrainedSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
__instanceLock__.unlock();
|
||||||
|
|
||||||
|
// Perform a measurement pass to get the full tree layout, adapting to the child's new size.
|
||||||
|
ASLayout *layout = [self layoutThatFits:constrainedSize];
|
||||||
|
|
||||||
|
// Check if the returned layout has a different size than our current bounds.
|
||||||
|
if (CGSizeEqualToSize(boundsSizeForLayout, layout.size) == NO) {
|
||||||
|
// If so, inform our container we need an update (e.g Table, Collection, ViewController, etc).
|
||||||
|
[self displayNodeDidInvalidateSizeNewSize:layout.size];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)displayNodeDidInvalidateSizeNewSize:(CGSize)size
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertThreadAffinity(self);
|
||||||
|
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__);
|
||||||
|
|
||||||
|
// The default implementation of display node changes the size of itself to the new size
|
||||||
|
CGRect oldBounds = self.bounds;
|
||||||
|
CGSize oldSize = oldBounds.size;
|
||||||
|
CGSize newSize = size;
|
||||||
|
|
||||||
|
if (! CGSizeEqualToSize(oldSize, newSize)) {
|
||||||
|
self.bounds = (CGRect){ oldBounds.origin, newSize };
|
||||||
|
|
||||||
|
// Frame's origin must be preserved. Since it is computed from bounds size, anchorPoint
|
||||||
|
// and position (see frame setter in ASDisplayNode+UIViewBridge), position needs to be adjusted.
|
||||||
|
CGPoint anchorPoint = self.anchorPoint;
|
||||||
|
CGPoint oldPosition = self.position;
|
||||||
|
CGFloat xDelta = (newSize.width - oldSize.width) * anchorPoint.x;
|
||||||
|
CGFloat yDelta = (newSize.height - oldSize.height) * anchorPoint.y;
|
||||||
|
self.position = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Needs to be called with lock held
|
||||||
|
- (void)_locked_measureNodeWithBoundsIfNecessary:(CGRect)bounds
|
||||||
|
{
|
||||||
|
// Check if we are a subnode in a layout transition.
|
||||||
|
// In this case no measurement is needed as it's part of the layout transition
|
||||||
|
if ([self _isLayoutTransitionInvalid]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGSize boundsSizeForLayout = ASCeilSizeValues(bounds.size);
|
||||||
|
|
||||||
|
// Prefer _pendingDisplayNodeLayout over _calculatedDisplayNodeLayout (if exists, it's the newest)
|
||||||
|
// If there is no _pending, check if _calculated is valid to reuse (avoiding recalculation below).
|
||||||
|
if (_pendingDisplayNodeLayout == nullptr) {
|
||||||
|
if (_calculatedDisplayNodeLayout->isDirty() == NO
|
||||||
|
&& (_calculatedDisplayNodeLayout->requestedLayoutFromAbove == YES
|
||||||
|
|| CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// _calculatedDisplayNodeLayout is not reusable we need to transition to a new one
|
||||||
|
[self cancelLayoutTransition];
|
||||||
|
|
||||||
|
BOOL didCreateNewContext = NO;
|
||||||
|
ASLayoutElementContext context = ASLayoutElementGetCurrentContext();
|
||||||
|
if (ASLayoutElementContextIsNull(context)) {
|
||||||
|
context = ASLayoutElementContextMake(ASLayoutElementContextDefaultTransitionID);
|
||||||
|
ASLayoutElementSetCurrentContext(context);
|
||||||
|
didCreateNewContext = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Figure out previous and pending layouts for layout transition
|
||||||
|
std::shared_ptr<ASDisplayNodeLayout> nextLayout = _pendingDisplayNodeLayout;
|
||||||
|
#define layoutSizeDifferentFromBounds !CGSizeEqualToSize(nextLayout->layout.size, boundsSizeForLayout)
|
||||||
|
|
||||||
|
// nextLayout was likely created by a call to layoutThatFits:, check if it is valid and can be applied.
|
||||||
|
// If our bounds size is different than it, or invalid, recalculate. Use #define to avoid nullptr->
|
||||||
|
if (nextLayout == nullptr || nextLayout->isDirty() == YES || layoutSizeDifferentFromBounds) {
|
||||||
|
// Use the last known constrainedSize passed from a parent during layout (if never, use bounds).
|
||||||
|
ASSizeRange constrainedSize = [self _locked_constrainedSizeForLayoutPass];
|
||||||
|
ASLayout *layout = [self calculateLayoutThatFits:constrainedSize
|
||||||
|
restrictedToSize:self.style.size
|
||||||
|
relativeToParentSize:boundsSizeForLayout];
|
||||||
|
|
||||||
|
nextLayout = std::make_shared<ASDisplayNodeLayout>(layout, constrainedSize, boundsSizeForLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (didCreateNewContext) {
|
||||||
|
ASLayoutElementClearCurrentContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If our new layout's desired size for self doesn't match current size, ask our parent to update it.
|
||||||
|
// This can occur for either pre-calculated or newly-calculated layouts.
|
||||||
|
if (nextLayout->requestedLayoutFromAbove == NO
|
||||||
|
&& CGSizeEqualToSize(boundsSizeForLayout, nextLayout->layout.size) == NO) {
|
||||||
|
// The layout that we have specifies that this node (self) would like to be a different size
|
||||||
|
// than it currently is. Because that size has been computed within the constrainedSize, we
|
||||||
|
// expect that calling setNeedsLayoutFromAbove will result in our parent resizing us to this.
|
||||||
|
// However, in some cases apps may manually interfere with this (setting a different bounds).
|
||||||
|
// In this case, we need to detect that we've already asked to be resized to match this
|
||||||
|
// particular ASLayout object, and shouldn't loop asking again unless we have a different ASLayout.
|
||||||
|
nextLayout->requestedLayoutFromAbove = YES;
|
||||||
|
[self _setNeedsLayoutFromAbove];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare to transition to nextLayout
|
||||||
|
ASDisplayNodeAssertNotNil(nextLayout->layout, @"nextLayout->layout should not be nil! %@", self);
|
||||||
|
_pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self
|
||||||
|
pendingLayout:nextLayout
|
||||||
|
previousLayout:_calculatedDisplayNodeLayout];
|
||||||
|
|
||||||
|
// If a parent is currently executing a layout transition, perform our layout application after it.
|
||||||
|
if (ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO) {
|
||||||
|
// If no transition, apply our new layout immediately (common case).
|
||||||
|
[self _completePendingLayoutTransition];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (ASSizeRange)_locked_constrainedSizeForLayoutPass
|
||||||
|
{
|
||||||
|
// TODO: The logic in -_setNeedsLayoutFromAbove seems correct and doesn't use this method.
|
||||||
|
// logic seems correct. For what case does -this method need to do the CGSizeEqual checks?
|
||||||
|
// IF WE CAN REMOVE BOUNDS CHECKS HERE, THEN WE CAN ALSO REMOVE "REQUESTED FROM ABOVE" CHECK
|
||||||
|
|
||||||
|
CGSize boundsSizeForLayout = ASCeilSizeValues(self.threadSafeBounds.size);
|
||||||
|
|
||||||
|
// Checkout if constrained size of pending or calculated display node layout can be used
|
||||||
|
if (_pendingDisplayNodeLayout != nullptr
|
||||||
|
&& (_pendingDisplayNodeLayout->requestedLayoutFromAbove
|
||||||
|
|| CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, boundsSizeForLayout))) {
|
||||||
|
// We assume the size from the last returned layoutThatFits: layout was applied so use the pending display node
|
||||||
|
// layout constrained size
|
||||||
|
return _pendingDisplayNodeLayout->constrainedSize;
|
||||||
|
} else if (_calculatedDisplayNodeLayout->layout != nil
|
||||||
|
&& (_calculatedDisplayNodeLayout->requestedLayoutFromAbove
|
||||||
|
|| CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout))) {
|
||||||
|
// We assume the _calculatedDisplayNodeLayout is still valid and the frame is not different
|
||||||
|
return _calculatedDisplayNodeLayout->constrainedSize;
|
||||||
|
} else {
|
||||||
|
// In this case neither the _pendingDisplayNodeLayout or the _calculatedDisplayNodeLayout constrained size can
|
||||||
|
// be reused, so the current bounds is used. This is usual the case if a frame was set manually that differs to
|
||||||
|
// the one returned from layoutThatFits: or layoutThatFits: was never called
|
||||||
|
return ASSizeRangeMake(boundsSizeForLayout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_layoutSublayouts
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertThreadAffinity(self);
|
||||||
|
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__);
|
||||||
|
|
||||||
|
ASLayout *layout;
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
if (_calculatedDisplayNodeLayout->isDirty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
layout = _calculatedDisplayNodeLayout->layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ASDisplayNode *node in self.subnodes) {
|
||||||
|
CGRect frame = [layout frameForElement:node];
|
||||||
|
if (CGRectIsNull(frame)) {
|
||||||
|
// There is no frame for this node in our layout.
|
||||||
|
// This currently can happen if we get a CA layout pass
|
||||||
|
// while waiting for the client to run animateLayoutTransition:
|
||||||
|
} else {
|
||||||
|
node.frame = frame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark - ASDisplayNode (ASAutomatic Subnode Management)
|
||||||
|
|
||||||
|
@implementation ASDisplayNode (ASAutomaticSubnodeManagement)
|
||||||
|
|
||||||
|
#pragma mark Automatically Manages Subnodes
|
||||||
|
|
||||||
|
- (BOOL)automaticallyManagesSubnodes
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
return _automaticallyManagesSubnodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setAutomaticallyManagesSubnodes:(BOOL)automaticallyManagesSubnodes
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
_automaticallyManagesSubnodes = automaticallyManagesSubnodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark - ASDisplayNode (ASLayoutTransition)
|
||||||
|
|
||||||
|
@implementation ASDisplayNode (ASLayoutTransition)
|
||||||
|
|
||||||
|
- (BOOL)_isTransitionInProgress
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
return _transitionInProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)_isLayoutTransitionInvalid
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) {
|
||||||
|
ASLayoutElementContext context = ASLayoutElementGetCurrentContext();
|
||||||
|
if (ASLayoutElementContextIsNull(context) || _pendingTransitionID != context.transitionID) {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Starts a new transition and returns the transition id
|
||||||
|
- (int32_t)_startNewTransition
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
_transitionInProgress = YES;
|
||||||
|
_transitionID = OSAtomicAdd32(1, &_transitionID);
|
||||||
|
return _transitionID;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_finishOrCancelTransition
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
_transitionInProgress = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setPendingTransitionID:(int32_t)pendingTransitionID
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
ASDisplayNodeAssertTrue(_pendingTransitionID < pendingTransitionID);
|
||||||
|
_pendingTransitionID = pendingTransitionID;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (int32_t)pendingTransitionID
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
return _pendingTransitionID;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)_shouldAbortTransitionWithID:(int32_t)transitionID
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
return [self _locked_shouldAbortTransitionWithID:transitionID];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)_locked_shouldAbortTransitionWithID:(int32_t)transitionID
|
||||||
|
{
|
||||||
|
return (!_transitionInProgress || _transitionID != transitionID);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark Layout Transition
|
||||||
|
|
||||||
|
- (void)transitionLayoutWithAnimation:(BOOL)animated
|
||||||
|
shouldMeasureAsync:(BOOL)shouldMeasureAsync
|
||||||
|
measurementCompletion:(void(^)())completion
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertMainThread();
|
||||||
|
|
||||||
|
[self setNeedsLayout];
|
||||||
|
|
||||||
|
[self transitionLayoutWithSizeRange:[self _locked_constrainedSizeForLayoutPass]
|
||||||
|
animated:animated
|
||||||
|
shouldMeasureAsync:shouldMeasureAsync
|
||||||
|
measurementCompletion:completion];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize
|
||||||
|
animated:(BOOL)animated
|
||||||
|
shouldMeasureAsync:(BOOL)shouldMeasureAsync
|
||||||
|
measurementCompletion:(void(^)())completion
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertMainThread();
|
||||||
|
|
||||||
|
if (constrainedSize.max.width <= 0.0 || constrainedSize.max.height <= 0.0) {
|
||||||
|
// Using CGSizeZero for the sizeRange can cause negative values in client layout code.
|
||||||
|
// Most likely called transitionLayout: without providing a size, before first layout pass.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we are a subnode in a layout transition.
|
||||||
|
// In this case no measurement is needed as we're part of the layout transition.
|
||||||
|
if ([self _isLayoutTransitionInvalid]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
ASDisplayNodeAssert(ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO, @"Can't start a transition when one of the supernodes is performing one.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every new layout transition has a transition id associated to check in subsequent transitions for cancelling
|
||||||
|
int32_t transitionID = [self _startNewTransition];
|
||||||
|
|
||||||
|
// Move all subnodes in layout pending state for this transition
|
||||||
|
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) {
|
||||||
|
ASDisplayNodeAssert([node _isTransitionInProgress] == NO, @"Can't start a transition when one of the subnodes is performing one.");
|
||||||
|
node.hierarchyState |= ASHierarchyStateLayoutPending;
|
||||||
|
node.pendingTransitionID = transitionID;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Transition block that executes the layout transition
|
||||||
|
void (^transitionBlock)(void) = ^{
|
||||||
|
if ([self _shouldAbortTransitionWithID:transitionID]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform a full layout creation pass with passed in constrained size to create the new layout for the transition
|
||||||
|
ASLayout *newLayout;
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
|
||||||
|
ASLayoutElementSetCurrentContext(ASLayoutElementContextMake(transitionID));
|
||||||
|
|
||||||
|
BOOL automaticallyManagesSubnodesDisabled = (self.automaticallyManagesSubnodes == NO);
|
||||||
|
self.automaticallyManagesSubnodes = YES; // Temporary flag for 1.9.x
|
||||||
|
newLayout = [self calculateLayoutThatFits:constrainedSize
|
||||||
|
restrictedToSize:self.style.size
|
||||||
|
relativeToParentSize:constrainedSize.max];
|
||||||
|
if (automaticallyManagesSubnodesDisabled) {
|
||||||
|
self.automaticallyManagesSubnodes = NO; // Temporary flag for 1.9.x
|
||||||
|
}
|
||||||
|
|
||||||
|
ASLayoutElementClearCurrentContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([self _shouldAbortTransitionWithID:transitionID]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASPerformBlockOnMainThread(^{
|
||||||
|
ASLayoutTransition *pendingLayoutTransition;
|
||||||
|
_ASTransitionContext *pendingLayoutTransitionContext;
|
||||||
|
{
|
||||||
|
// Grab __instanceLock__ here to make sure this transition isn't invalidated
|
||||||
|
// right after it passed the validation test and before it proceeds
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
|
||||||
|
if ([self _locked_shouldAbortTransitionWithID:transitionID]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update calculated layout
|
||||||
|
auto previousLayout = _calculatedDisplayNodeLayout;
|
||||||
|
auto pendingLayout = std::make_shared<ASDisplayNodeLayout>(newLayout,
|
||||||
|
constrainedSize,
|
||||||
|
constrainedSize.max);
|
||||||
|
[self _locked_setCalculatedDisplayNodeLayout:pendingLayout];
|
||||||
|
|
||||||
|
// Setup pending layout transition for animation
|
||||||
|
_pendingLayoutTransition = pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self
|
||||||
|
pendingLayout:pendingLayout
|
||||||
|
previousLayout:previousLayout];
|
||||||
|
// Setup context for pending layout transition. we need to hold a strong reference to the context
|
||||||
|
_pendingLayoutTransitionContext = pendingLayoutTransitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated
|
||||||
|
layoutDelegate:_pendingLayoutTransition
|
||||||
|
completionDelegate:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply complete layout transitions for all subnodes
|
||||||
|
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) {
|
||||||
|
[node _completePendingLayoutTransition];
|
||||||
|
node.hierarchyState &= (~ASHierarchyStateLayoutPending);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Measurement pass completion
|
||||||
|
// Give the subclass a change to hook into before calling the completion block
|
||||||
|
[self _layoutTransitionMeasurementDidFinish];
|
||||||
|
if (completion) {
|
||||||
|
completion();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the subnode insertion immediately to be able to animate the nodes
|
||||||
|
[pendingLayoutTransition applySubnodeInsertions];
|
||||||
|
|
||||||
|
// Kick off animating the layout transition
|
||||||
|
[self animateLayoutTransition:pendingLayoutTransitionContext];
|
||||||
|
|
||||||
|
// Mark transaction as finished
|
||||||
|
[self _finishOrCancelTransition];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start transition based on flag on current or background thread
|
||||||
|
if (shouldMeasureAsync) {
|
||||||
|
ASPerformBlockOnBackgroundThread(transitionBlock);
|
||||||
|
} else {
|
||||||
|
transitionBlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)cancelLayoutTransition
|
||||||
|
{
|
||||||
|
__instanceLock__.lock();
|
||||||
|
BOOL transitionInProgress = _transitionInProgress;
|
||||||
|
__instanceLock__.unlock();
|
||||||
|
|
||||||
|
if (transitionInProgress) {
|
||||||
|
// Cancel transition in progress
|
||||||
|
[self _finishOrCancelTransition];
|
||||||
|
|
||||||
|
// Tell subnodes to exit layout pending state and clear related properties
|
||||||
|
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) {
|
||||||
|
node.hierarchyState &= (~ASHierarchyStateLayoutPending);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setDefaultLayoutTransitionDuration:(NSTimeInterval)defaultLayoutTransitionDuration
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
_defaultLayoutTransitionDuration = defaultLayoutTransitionDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSTimeInterval)defaultLayoutTransitionDuration
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
return _defaultLayoutTransitionDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setDefaultLayoutTransitionDelay:(NSTimeInterval)defaultLayoutTransitionDelay
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
_defaultLayoutTransitionDelay = defaultLayoutTransitionDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSTimeInterval)defaultLayoutTransitionDelay
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
return _defaultLayoutTransitionDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setDefaultLayoutTransitionOptions:(UIViewAnimationOptions)defaultLayoutTransitionOptions
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
_defaultLayoutTransitionOptions = defaultLayoutTransitionOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIViewAnimationOptions)defaultLayoutTransitionOptions
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
return _defaultLayoutTransitionOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark <LayoutTransitioning>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hook for subclasses to perform an animation based on the given ASContextTransitioning. By default a fade in and out
|
||||||
|
* animation is provided.
|
||||||
|
*/
|
||||||
|
- (void)animateLayoutTransition:(id<ASContextTransitioning>)context
|
||||||
|
{
|
||||||
|
if ([context isAnimated] == NO) {
|
||||||
|
[self _layoutSublayouts];
|
||||||
|
[context completeTransition:YES];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASDisplayNode *node = self;
|
||||||
|
|
||||||
|
NSAssert(node.isNodeLoaded == YES, @"Invalid node state");
|
||||||
|
|
||||||
|
NSArray<ASDisplayNode *> *removedSubnodes = [context removedSubnodes];
|
||||||
|
NSMutableArray<ASDisplayNode *> *insertedSubnodes = [[context insertedSubnodes] mutableCopy];
|
||||||
|
NSMutableArray<ASDisplayNode *> *movedSubnodes = [NSMutableArray array];
|
||||||
|
|
||||||
|
NSMutableArray<_ASAnimatedTransitionContext *> *insertedSubnodeContexts = [NSMutableArray array];
|
||||||
|
NSMutableArray<_ASAnimatedTransitionContext *> *removedSubnodeContexts = [NSMutableArray array];
|
||||||
|
|
||||||
|
for (ASDisplayNode *subnode in [context subnodesForKey:ASTransitionContextToLayoutKey]) {
|
||||||
|
if ([insertedSubnodes containsObject:subnode] == NO) {
|
||||||
|
// This is an existing subnode, check if it is resized, moved or both
|
||||||
|
CGRect fromFrame = [context initialFrameForNode:subnode];
|
||||||
|
CGRect toFrame = [context finalFrameForNode:subnode];
|
||||||
|
if (CGSizeEqualToSize(fromFrame.size, toFrame.size) == NO) {
|
||||||
|
[insertedSubnodes addObject:subnode];
|
||||||
|
}
|
||||||
|
if (CGPointEqualToPoint(fromFrame.origin, toFrame.origin) == NO) {
|
||||||
|
[movedSubnodes addObject:subnode];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create contexts for inserted and removed subnodes
|
||||||
|
for (ASDisplayNode *insertedSubnode in insertedSubnodes) {
|
||||||
|
[insertedSubnodeContexts addObject:[_ASAnimatedTransitionContext contextForNode:insertedSubnode alpha:insertedSubnode.alpha]];
|
||||||
|
}
|
||||||
|
for (ASDisplayNode *removedSubnode in removedSubnodes) {
|
||||||
|
[removedSubnodeContexts addObject:[_ASAnimatedTransitionContext contextForNode:removedSubnode alpha:removedSubnode.alpha]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fade out inserted subnodes
|
||||||
|
for (ASDisplayNode *insertedSubnode in insertedSubnodes) {
|
||||||
|
insertedSubnode.frame = [context finalFrameForNode:insertedSubnode];
|
||||||
|
insertedSubnode.alpha = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust groupOpacity for animation
|
||||||
|
BOOL originAllowsGroupOpacity = node.allowsGroupOpacity;
|
||||||
|
node.allowsGroupOpacity = YES;
|
||||||
|
|
||||||
|
[UIView animateWithDuration:self.defaultLayoutTransitionDuration delay:self.defaultLayoutTransitionDelay options:self.defaultLayoutTransitionOptions animations:^{
|
||||||
|
// Fade removed subnodes and views out
|
||||||
|
for (ASDisplayNode *removedSubnode in removedSubnodes) {
|
||||||
|
removedSubnode.alpha = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fade inserted subnodes in
|
||||||
|
for (_ASAnimatedTransitionContext *insertedSubnodeContext in insertedSubnodeContexts) {
|
||||||
|
insertedSubnodeContext.node.alpha = insertedSubnodeContext.alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update frame of self and moved subnodes
|
||||||
|
CGSize fromSize = [context layoutForKey:ASTransitionContextFromLayoutKey].size;
|
||||||
|
CGSize toSize = [context layoutForKey:ASTransitionContextToLayoutKey].size;
|
||||||
|
BOOL isResized = (CGSizeEqualToSize(fromSize, toSize) == NO);
|
||||||
|
if (isResized == YES) {
|
||||||
|
CGPoint position = node.frame.origin;
|
||||||
|
node.frame = CGRectMake(position.x, position.y, toSize.width, toSize.height);
|
||||||
|
}
|
||||||
|
for (ASDisplayNode *movedSubnode in movedSubnodes) {
|
||||||
|
movedSubnode.frame = [context finalFrameForNode:movedSubnode];
|
||||||
|
}
|
||||||
|
} completion:^(BOOL finished) {
|
||||||
|
// Restore all removed subnode alpha values
|
||||||
|
for (_ASAnimatedTransitionContext *removedSubnodeContext in removedSubnodeContexts) {
|
||||||
|
removedSubnodeContext.node.alpha = removedSubnodeContext.alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore group opacity
|
||||||
|
node.allowsGroupOpacity = originAllowsGroupOpacity;
|
||||||
|
|
||||||
|
// Subnode removals are automatically performed
|
||||||
|
[context completeTransition:finished];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for subclasses to clean up nodes after the transition happened. Furthermore this can be used from subclasses
|
||||||
|
* to manually perform deletions.
|
||||||
|
*/
|
||||||
|
- (void)didCompleteLayoutTransition:(id<ASContextTransitioning>)context
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertMainThread();
|
||||||
|
|
||||||
|
__instanceLock__.lock();
|
||||||
|
ASLayoutTransition *pendingLayoutTransition = _pendingLayoutTransition;
|
||||||
|
__instanceLock__.unlock();
|
||||||
|
|
||||||
|
[pendingLayoutTransition applySubnodeRemovals];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completes the pending layout transition immediately without going through the the Layout Transition Animation API
|
||||||
|
*/
|
||||||
|
- (void)_completePendingLayoutTransition
|
||||||
|
{
|
||||||
|
__instanceLock__.lock();
|
||||||
|
ASLayoutTransition *pendingLayoutTransition = _pendingLayoutTransition;
|
||||||
|
__instanceLock__.unlock();
|
||||||
|
|
||||||
|
if (pendingLayoutTransition != nil) {
|
||||||
|
[self _setCalculatedDisplayNodeLayout:pendingLayoutTransition.pendingLayout];
|
||||||
|
[self _completeLayoutTransition:pendingLayoutTransition];
|
||||||
|
}
|
||||||
|
[self _pendingLayoutTransitionDidComplete];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be directly called to commit the given layout transition immediately to complete without calling through to the
|
||||||
|
* Layout Transition Animation API
|
||||||
|
*/
|
||||||
|
- (void)_completeLayoutTransition:(ASLayoutTransition *)layoutTransition
|
||||||
|
{
|
||||||
|
// Layout transition is not supported for nodes that are not have automatic subnode management enabled
|
||||||
|
if (layoutTransition == nil || self.automaticallyManagesSubnodes == NO) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trampoline to the main thread if necessary
|
||||||
|
if (ASDisplayNodeThreadIsMain() || layoutTransition.isSynchronous == NO) {
|
||||||
|
[layoutTransition commitTransition];
|
||||||
|
} else {
|
||||||
|
// Subnode insertions and removals need to happen always on the main thread if at least one subnode is already loaded
|
||||||
|
ASPerformBlockOnMainThread(^{
|
||||||
|
[layoutTransition commitTransition];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_pendingLayoutTransitionDidComplete
|
||||||
|
{
|
||||||
|
// Subclass hook
|
||||||
|
[self calculatedLayoutDidChange];
|
||||||
|
|
||||||
|
// Grab lock after calling out to subclass
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
|
||||||
|
// We generate placeholders at measureWithSizeRange: time so that a node is guaranteed to have a placeholder ready to go.
|
||||||
|
// This is also because measurement is usually asynchronous, but placeholders need to be set up synchronously.
|
||||||
|
// First measurement is guaranteed to be before the node is onscreen, so we can create the image async. but still have it appear sync.
|
||||||
|
if (_placeholderEnabled && !_placeholderImage && [self _locked_displaysAsynchronously]) {
|
||||||
|
|
||||||
|
// Zero-sized nodes do not require a placeholder.
|
||||||
|
ASLayout *layout = _calculatedDisplayNodeLayout->layout;
|
||||||
|
CGSize layoutSize = (layout ? layout.size : CGSizeZero);
|
||||||
|
if (layoutSize.width * layoutSize.height <= 0.0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've displayed our contents, we don't need a placeholder.
|
||||||
|
// Contents is a thread-affined property and can't be read off main after loading.
|
||||||
|
if (self.isNodeLoaded) {
|
||||||
|
ASPerformBlockOnMainThread(^{
|
||||||
|
if (self.contents == nil) {
|
||||||
|
_placeholderImage = [self placeholderImage];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (self.contents == nil) {
|
||||||
|
_placeholderImage = [self placeholderImage];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup pending layout transition
|
||||||
|
_pendingLayoutTransition = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_setCalculatedDisplayNodeLayout:(std::shared_ptr<ASDisplayNodeLayout>)displayNodeLayout
|
||||||
|
{
|
||||||
|
ASDN::MutexLocker l(__instanceLock__);
|
||||||
|
[self _locked_setCalculatedDisplayNodeLayout:displayNodeLayout];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_locked_setCalculatedDisplayNodeLayout:(std::shared_ptr<ASDisplayNodeLayout>)displayNodeLayout
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertTrue(displayNodeLayout->layout.layoutElement == self);
|
||||||
|
ASDisplayNodeAssertTrue(displayNodeLayout->layout.size.width >= 0.0);
|
||||||
|
ASDisplayNodeAssertTrue(displayNodeLayout->layout.size.height >= 0.0);
|
||||||
|
|
||||||
|
_calculatedDisplayNodeLayout = displayNodeLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@ -295,44 +295,6 @@ extern NSInteger const ASDefaultDrawingPriority;
|
|||||||
*/
|
*/
|
||||||
@property (nonatomic, class, copy) ASDisplayNodeNonFatalErrorBlock nonFatalErrorBlock;
|
@property (nonatomic, class, copy) ASDisplayNodeNonFatalErrorBlock nonFatalErrorBlock;
|
||||||
|
|
||||||
|
|
||||||
/** @name Managing dimensions */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @abstract Provides a way to declare a block to provide an ASLayoutSpec without having to subclass ASDisplayNode and
|
|
||||||
* implement layoutSpecThatFits:
|
|
||||||
*
|
|
||||||
* @return A block that takes a constrainedSize ASSizeRange argument, and must return an ASLayoutSpec that includes all
|
|
||||||
* of the subnodes to position in the layout. This input-output relationship is identical to the subclass override
|
|
||||||
* method -layoutSpecThatFits:
|
|
||||||
*
|
|
||||||
* @warning Subclasses that implement -layoutSpecThatFits: must not also use .layoutSpecBlock. Doing so will trigger
|
|
||||||
* an exception. A future version of the framework may support using both, calling them serially, with the
|
|
||||||
* .layoutSpecBlock superseding any values set by the method override.
|
|
||||||
*
|
|
||||||
* @code ^ASLayoutSpec *(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {};
|
|
||||||
*/
|
|
||||||
@property (nonatomic, readwrite, copy, nullable) ASLayoutSpecBlock layoutSpecBlock;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @abstract Return the calculated size.
|
|
||||||
*
|
|
||||||
* @discussion Ideal for use by subclasses in -layout, having already prompted their subnodes to calculate their size by
|
|
||||||
* calling -measure: on them in -calculateLayoutThatFits.
|
|
||||||
*
|
|
||||||
* @return Size already calculated by -calculateLayoutThatFits:.
|
|
||||||
*
|
|
||||||
* @warning Subclasses must not override this; it returns the last cached measurement and is never expensive.
|
|
||||||
*/
|
|
||||||
@property (nonatomic, readonly, assign) CGSize calculatedSize;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @abstract Return the constrained size range used for calculating layout.
|
|
||||||
*
|
|
||||||
* @return The minimum and maximum constrained sizes used by calculateLayoutThatFits:.
|
|
||||||
*/
|
|
||||||
@property (nonatomic, readonly, assign) ASSizeRange constrainedSizeForCalculatedLayout;
|
|
||||||
|
|
||||||
/** @name Managing the nodes hierarchy */
|
/** @name Managing the nodes hierarchy */
|
||||||
|
|
||||||
|
|
||||||
@ -589,7 +551,7 @@ extern NSInteger const ASDefaultDrawingPriority;
|
|||||||
/**
|
/**
|
||||||
* Convenience methods for debugging.
|
* Convenience methods for debugging.
|
||||||
*/
|
*/
|
||||||
@interface ASDisplayNode (Debugging) <ASLayoutElementAsciiArtProtocol, ASDebugNameProvider>
|
@interface ASDisplayNode (Debugging) <ASDebugNameProvider>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract Return a description of the node hierarchy.
|
* @abstract Return a description of the node hierarchy.
|
||||||
@ -600,7 +562,6 @@ extern NSInteger const ASDefaultDrawingPriority;
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ## UIView bridge
|
* ## UIView bridge
|
||||||
*
|
*
|
||||||
@ -763,7 +724,52 @@ extern NSInteger const ASDefaultDrawingPriority;
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface ASDisplayNode (LayoutTransitioning)
|
@interface ASDisplayNode (ASLayoutElementAsciiArtProtocol) <ASLayoutElementAsciiArtProtocol>
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface ASDisplayNode (ASLayout)
|
||||||
|
|
||||||
|
/** @name Managing dimensions */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Provides a way to declare a block to provide an ASLayoutSpec without having to subclass ASDisplayNode and
|
||||||
|
* implement layoutSpecThatFits:
|
||||||
|
*
|
||||||
|
* @return A block that takes a constrainedSize ASSizeRange argument, and must return an ASLayoutSpec that includes all
|
||||||
|
* of the subnodes to position in the layout. This input-output relationship is identical to the subclass override
|
||||||
|
* method -layoutSpecThatFits:
|
||||||
|
*
|
||||||
|
* @warning Subclasses that implement -layoutSpecThatFits: must not also use .layoutSpecBlock. Doing so will trigger
|
||||||
|
* an exception. A future version of the framework may support using both, calling them serially, with the
|
||||||
|
* .layoutSpecBlock superseding any values set by the method override.
|
||||||
|
*
|
||||||
|
* @code ^ASLayoutSpec *(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) {};
|
||||||
|
*/
|
||||||
|
@property (nonatomic, readwrite, copy, nullable) ASLayoutSpecBlock layoutSpecBlock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Return the calculated size.
|
||||||
|
*
|
||||||
|
* @discussion Ideal for use by subclasses in -layout, having already prompted their subnodes to calculate their size by
|
||||||
|
* calling -measure: on them in -calculateLayoutThatFits.
|
||||||
|
*
|
||||||
|
* @return Size already calculated by -calculateLayoutThatFits:.
|
||||||
|
*
|
||||||
|
* @warning Subclasses must not override this; it returns the last cached measurement and is never expensive.
|
||||||
|
*/
|
||||||
|
@property (nonatomic, readonly, assign) CGSize calculatedSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Return the constrained size range used for calculating layout.
|
||||||
|
*
|
||||||
|
* @return The minimum and maximum constrained sizes used by calculateLayoutThatFits:.
|
||||||
|
*/
|
||||||
|
@property (nonatomic, readonly, assign) ASSizeRange constrainedSizeForCalculatedLayout;
|
||||||
|
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface ASDisplayNode (ASLayoutTransitioning)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract The amount of time it takes to complete the default transition animation. Default is 0.2.
|
* @abstract The amount of time it takes to complete the default transition animation. Default is 0.2.
|
||||||
@ -837,7 +843,7 @@ extern NSInteger const ASDefaultDrawingPriority;
|
|||||||
/*
|
/*
|
||||||
* ASDisplayNode support for automatic subnode management.
|
* ASDisplayNode support for automatic subnode management.
|
||||||
*/
|
*/
|
||||||
@interface ASDisplayNode (AutomaticSubnodeManagement)
|
@interface ASDisplayNode (ASAutomaticSubnodeManagement)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract A boolean that shows whether the node automatically inserts and removes nodes based on the presence or
|
* @abstract A boolean that shows whether the node automatically inserts and removes nodes based on the presence or
|
||||||
|
|||||||
@ -67,7 +67,7 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority;
|
|||||||
// We have to forward declare the protocol as this place otherwise it will not compile compiling with an Base SDK < iOS 10
|
// We have to forward declare the protocol as this place otherwise it will not compile compiling with an Base SDK < iOS 10
|
||||||
@protocol CALayerDelegate;
|
@protocol CALayerDelegate;
|
||||||
|
|
||||||
@interface ASDisplayNode () <UIGestureRecognizerDelegate, _ASDisplayLayerDelegate, _ASTransitionContextCompletionDelegate>
|
@interface ASDisplayNode () <UIGestureRecognizerDelegate, _ASDisplayLayerDelegate>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See ASDisplayNodeInternal.h for ivars
|
* See ASDisplayNodeInternal.h for ivars
|
||||||
@ -79,9 +79,7 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority;
|
|||||||
|
|
||||||
@dynamic layoutElementType;
|
@dynamic layoutElementType;
|
||||||
|
|
||||||
@synthesize debugName = _debugName;
|
|
||||||
@synthesize threadSafeBounds = _threadSafeBounds;
|
@synthesize threadSafeBounds = _threadSafeBounds;
|
||||||
@synthesize layoutSpecBlock = _layoutSpecBlock;
|
|
||||||
|
|
||||||
static BOOL suppressesInvalidCollectionUpdateExceptions = NO;
|
static BOOL suppressesInvalidCollectionUpdateExceptions = NO;
|
||||||
|
|
||||||
@ -838,27 +836,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
|||||||
_flags.viewEverHadAGestureRecognizerAttached = YES;
|
_flags.viewEverHadAGestureRecognizerAttached = YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Layout
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
#define AS_DEDUPE_LAYOUT_SPEC_TREE 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// At most a layoutSpecBlock or one of the three layout methods is overridden
|
|
||||||
#define __ASDisplayNodeCheckForLayoutMethodOverrides \
|
|
||||||
ASDisplayNodeAssert(_layoutSpecBlock != NULL || \
|
|
||||||
((ASDisplayNodeSubclassOverridesSelector(self.class, @selector(calculateSizeThatFits:)) ? 1 : 0) \
|
|
||||||
+ (ASDisplayNodeSubclassOverridesSelector(self.class, @selector(layoutSpecThatFits:)) ? 1 : 0) \
|
|
||||||
+ (ASDisplayNodeSubclassOverridesSelector(self.class, @selector(calculateLayoutThatFits:)) ? 1 : 0)) <= 1, \
|
|
||||||
@"Subclass %@ must at least provide a layoutSpecBlock or override at most one of the three layout methods: calculateLayoutThatFits:, layoutSpecThatFits:, or calculateSizeThatFits:", NSStringFromClass(self.class))
|
|
||||||
|
|
||||||
#pragma mark <ASLayoutElementTransition>
|
|
||||||
|
|
||||||
- (BOOL)canLayoutAsynchronous
|
|
||||||
{
|
|
||||||
return !self.isNodeLoaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark <ASDebugNameProvider>
|
#pragma mark <ASDebugNameProvider>
|
||||||
|
|
||||||
- (NSString *)debugName
|
- (NSString *)debugName
|
||||||
@ -875,6 +852,28 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma mark - Layout
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
#define AS_DEDUPE_LAYOUT_SPEC_TREE 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// At most a layoutSpecBlock or one of the three layout methods is overridden
|
||||||
|
#define __ASDisplayNodeCheckForLayoutMethodOverrides \
|
||||||
|
ASDisplayNodeAssert(_layoutSpecBlock != NULL || \
|
||||||
|
((ASDisplayNodeSubclassOverridesSelector(self.class, @selector(calculateSizeThatFits:)) ? 1 : 0) \
|
||||||
|
+ (ASDisplayNodeSubclassOverridesSelector(self.class, @selector(layoutSpecThatFits:)) ? 1 : 0) \
|
||||||
|
+ (ASDisplayNodeSubclassOverridesSelector(self.class, @selector(calculateLayoutThatFits:)) ? 1 : 0)) <= 1, \
|
||||||
|
@"Subclass %@ must at least provide a layoutSpecBlock or override at most one of the three layout methods: calculateLayoutThatFits:, layoutSpecThatFits:, or calculateSizeThatFits:", NSStringFromClass(self.class))
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark <ASLayoutElementTransition>
|
||||||
|
|
||||||
|
- (BOOL)canLayoutAsynchronous
|
||||||
|
{
|
||||||
|
return !self.isNodeLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark Layout Pass
|
#pragma mark Layout Pass
|
||||||
|
|
||||||
- (void)__setNeedsLayout
|
- (void)__setNeedsLayout
|
||||||
@ -937,113 +936,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Needs to be called with lock held
|
|
||||||
- (void)_locked_measureNodeWithBoundsIfNecessary:(CGRect)bounds
|
|
||||||
{
|
|
||||||
// Check if we are a subnode in a layout transition.
|
|
||||||
// In this case no measurement is needed as it's part of the layout transition
|
|
||||||
if ([self _isLayoutTransitionInvalid]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CGSize boundsSizeForLayout = ASCeilSizeValues(bounds.size);
|
|
||||||
|
|
||||||
// Prefer _pendingDisplayNodeLayout over _calculatedDisplayNodeLayout (if exists, it's the newest)
|
|
||||||
// If there is no _pending, check if _calculated is valid to reuse (avoiding recalculation below).
|
|
||||||
if (_pendingDisplayNodeLayout == nullptr) {
|
|
||||||
if (_calculatedDisplayNodeLayout->isDirty() == NO
|
|
||||||
&& (_calculatedDisplayNodeLayout->requestedLayoutFromAbove == YES
|
|
||||||
|| CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// _calculatedDisplayNodeLayout is not reusable we need to transition to a new one
|
|
||||||
[self cancelLayoutTransition];
|
|
||||||
|
|
||||||
BOOL didCreateNewContext = NO;
|
|
||||||
ASLayoutElementContext context = ASLayoutElementGetCurrentContext();
|
|
||||||
if (ASLayoutElementContextIsNull(context)) {
|
|
||||||
context = ASLayoutElementContextMake(ASLayoutElementContextDefaultTransitionID);
|
|
||||||
ASLayoutElementSetCurrentContext(context);
|
|
||||||
didCreateNewContext = YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Figure out previous and pending layouts for layout transition
|
|
||||||
std::shared_ptr<ASDisplayNodeLayout> nextLayout = _pendingDisplayNodeLayout;
|
|
||||||
#define layoutSizeDifferentFromBounds !CGSizeEqualToSize(nextLayout->layout.size, boundsSizeForLayout)
|
|
||||||
|
|
||||||
// nextLayout was likely created by a call to layoutThatFits:, check if it is valid and can be applied.
|
|
||||||
// If our bounds size is different than it, or invalid, recalculate. Use #define to avoid nullptr->
|
|
||||||
if (nextLayout == nullptr || nextLayout->isDirty() == YES || layoutSizeDifferentFromBounds) {
|
|
||||||
// Use the last known constrainedSize passed from a parent during layout (if never, use bounds).
|
|
||||||
ASSizeRange constrainedSize = [self _locked_constrainedSizeForLayoutPass];
|
|
||||||
ASLayout *layout = [self calculateLayoutThatFits:constrainedSize
|
|
||||||
restrictedToSize:self.style.size
|
|
||||||
relativeToParentSize:boundsSizeForLayout];
|
|
||||||
|
|
||||||
nextLayout = std::make_shared<ASDisplayNodeLayout>(layout, constrainedSize, boundsSizeForLayout);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (didCreateNewContext) {
|
|
||||||
ASLayoutElementClearCurrentContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If our new layout's desired size for self doesn't match current size, ask our parent to update it.
|
|
||||||
// This can occur for either pre-calculated or newly-calculated layouts.
|
|
||||||
if (nextLayout->requestedLayoutFromAbove == NO
|
|
||||||
&& CGSizeEqualToSize(boundsSizeForLayout, nextLayout->layout.size) == NO) {
|
|
||||||
// The layout that we have specifies that this node (self) would like to be a different size
|
|
||||||
// than it currently is. Because that size has been computed within the constrainedSize, we
|
|
||||||
// expect that calling setNeedsLayoutFromAbove will result in our parent resizing us to this.
|
|
||||||
// However, in some cases apps may manually interfere with this (setting a different bounds).
|
|
||||||
// In this case, we need to detect that we've already asked to be resized to match this
|
|
||||||
// particular ASLayout object, and shouldn't loop asking again unless we have a different ASLayout.
|
|
||||||
nextLayout->requestedLayoutFromAbove = YES;
|
|
||||||
[self _setNeedsLayoutFromAbove];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare to transition to nextLayout
|
|
||||||
ASDisplayNodeAssertNotNil(nextLayout->layout, @"nextLayout->layout should not be nil! %@", self);
|
|
||||||
_pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self
|
|
||||||
pendingLayout:nextLayout
|
|
||||||
previousLayout:_calculatedDisplayNodeLayout];
|
|
||||||
|
|
||||||
// If a parent is currently executing a layout transition, perform our layout application after it.
|
|
||||||
if (ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO) {
|
|
||||||
// If no transition, apply our new layout immediately (common case).
|
|
||||||
[self _completePendingLayoutTransition];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (ASSizeRange)_locked_constrainedSizeForLayoutPass
|
|
||||||
{
|
|
||||||
// TODO: The logic in -_setNeedsLayoutFromAbove seems correct and doesn't use this method.
|
|
||||||
// logic seems correct. For what case does -this method need to do the CGSizeEqual checks?
|
|
||||||
// IF WE CAN REMOVE BOUNDS CHECKS HERE, THEN WE CAN ALSO REMOVE "REQUESTED FROM ABOVE" CHECK
|
|
||||||
|
|
||||||
CGSize boundsSizeForLayout = ASCeilSizeValues(self.threadSafeBounds.size);
|
|
||||||
|
|
||||||
// Checkout if constrained size of pending or calculated display node layout can be used
|
|
||||||
if (_pendingDisplayNodeLayout != nullptr
|
|
||||||
&& (_pendingDisplayNodeLayout->requestedLayoutFromAbove
|
|
||||||
|| CGSizeEqualToSize(_pendingDisplayNodeLayout->layout.size, boundsSizeForLayout))) {
|
|
||||||
// We assume the size from the last returned layoutThatFits: layout was applied so use the pending display node
|
|
||||||
// layout constrained size
|
|
||||||
return _pendingDisplayNodeLayout->constrainedSize;
|
|
||||||
} else if (_calculatedDisplayNodeLayout->layout != nil
|
|
||||||
&& (_calculatedDisplayNodeLayout->requestedLayoutFromAbove
|
|
||||||
|| CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout))) {
|
|
||||||
// We assume the _calculatedDisplayNodeLayout is still valid and the frame is not different
|
|
||||||
return _calculatedDisplayNodeLayout->constrainedSize;
|
|
||||||
} else {
|
|
||||||
// In this case neither the _pendingDisplayNodeLayout or the _calculatedDisplayNodeLayout constrained size can
|
|
||||||
// be reused, so the current bounds is used. This is usual the case if a frame was set manually that differs to
|
|
||||||
// the one returned from layoutThatFits: or layoutThatFits: was never called
|
|
||||||
return ASSizeRangeMake(boundsSizeForLayout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)layoutDidFinish
|
- (void)layoutDidFinish
|
||||||
{
|
{
|
||||||
// Hook for subclasses
|
// Hook for subclasses
|
||||||
@ -1188,549 +1080,17 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
|||||||
return [[ASLayoutSpec alloc] init];
|
return [[ASLayoutSpec alloc] init];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setLayoutSpecBlock:(ASLayoutSpecBlock)layoutSpecBlock
|
|
||||||
{
|
|
||||||
// For now there should never be an override of layoutSpecThatFits: / layoutElementThatFits: and a layoutSpecBlock
|
|
||||||
ASDisplayNodeAssert(!(_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits), @"Overwriting layoutSpecThatFits: and providing a layoutSpecBlock block is currently not supported");
|
|
||||||
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
_layoutSpecBlock = layoutSpecBlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (ASLayoutSpecBlock)layoutSpecBlock
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
return _layoutSpecBlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (ASLayout *)calculatedLayout
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
return _calculatedDisplayNodeLayout->layout;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_setCalculatedDisplayNodeLayout:(std::shared_ptr<ASDisplayNodeLayout>)displayNodeLayout
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
[self _locked_setCalculatedDisplayNodeLayout:displayNodeLayout];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_locked_setCalculatedDisplayNodeLayout:(std::shared_ptr<ASDisplayNodeLayout>)displayNodeLayout
|
|
||||||
{
|
|
||||||
ASDisplayNodeAssertTrue(displayNodeLayout->layout.layoutElement == self);
|
|
||||||
ASDisplayNodeAssertTrue(displayNodeLayout->layout.size.width >= 0.0);
|
|
||||||
ASDisplayNodeAssertTrue(displayNodeLayout->layout.size.height >= 0.0);
|
|
||||||
|
|
||||||
_calculatedDisplayNodeLayout = displayNodeLayout;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (CGSize)calculatedSize
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
if (_pendingDisplayNodeLayout != nullptr) {
|
|
||||||
return _pendingDisplayNodeLayout->layout.size;
|
|
||||||
}
|
|
||||||
return _calculatedDisplayNodeLayout->layout.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (ASSizeRange)constrainedSizeForCalculatedLayout
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
if (_pendingDisplayNodeLayout != nullptr) {
|
|
||||||
return _pendingDisplayNodeLayout->constrainedSize;
|
|
||||||
}
|
|
||||||
return _calculatedDisplayNodeLayout->constrainedSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @abstract Informs the root node that the intrinsic size of the receiver is no longer valid.
|
|
||||||
*
|
|
||||||
* @discussion The size of a root node is determined by each subnode. Calling invalidateSize will let the root node know
|
|
||||||
* that the intrinsic size of the receiver node is no longer valid and a resizing of the root node needs to happen.
|
|
||||||
*/
|
|
||||||
- (void)_setNeedsLayoutFromAbove
|
|
||||||
{
|
|
||||||
ASDisplayNodeAssertThreadAffinity(self);
|
|
||||||
|
|
||||||
// Mark the node for layout in the next layout pass
|
|
||||||
[self setNeedsLayout];
|
|
||||||
|
|
||||||
__instanceLock__.lock();
|
|
||||||
// Escalate to the root; entire tree must allow adjustments so the layout fits the new child.
|
|
||||||
// Much of the layout will be re-used as cached (e.g. other items in an unconstrained stack)
|
|
||||||
ASDisplayNode *supernode = _supernode;
|
|
||||||
__instanceLock__.unlock();
|
|
||||||
|
|
||||||
if (supernode) {
|
|
||||||
// Threading model requires that we unlock before calling a method on our parent.
|
|
||||||
[supernode _setNeedsLayoutFromAbove];
|
|
||||||
} else {
|
|
||||||
// Let the root node method know that the size was invalidated
|
|
||||||
[self _rootNodeDidInvalidateSize];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_rootNodeDidInvalidateSize
|
|
||||||
{
|
|
||||||
ASDisplayNodeAssertThreadAffinity(self);
|
|
||||||
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__);
|
|
||||||
|
|
||||||
__instanceLock__.lock();
|
|
||||||
|
|
||||||
// We are the root node and need to re-flow the layout; at least one child needs a new size.
|
|
||||||
CGSize boundsSizeForLayout = ASCeilSizeValues(self.bounds.size);
|
|
||||||
|
|
||||||
// Figure out constrainedSize to use
|
|
||||||
ASSizeRange constrainedSize = ASSizeRangeMake(boundsSizeForLayout);
|
|
||||||
if (_pendingDisplayNodeLayout != nullptr) {
|
|
||||||
constrainedSize = _pendingDisplayNodeLayout->constrainedSize;
|
|
||||||
} else if (_calculatedDisplayNodeLayout->layout != nil) {
|
|
||||||
constrainedSize = _calculatedDisplayNodeLayout->constrainedSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
__instanceLock__.unlock();
|
|
||||||
|
|
||||||
// Perform a measurement pass to get the full tree layout, adapting to the child's new size.
|
|
||||||
ASLayout *layout = [self layoutThatFits:constrainedSize];
|
|
||||||
|
|
||||||
// Check if the returned layout has a different size than our current bounds.
|
|
||||||
if (CGSizeEqualToSize(boundsSizeForLayout, layout.size) == NO) {
|
|
||||||
// If so, inform our container we need an update (e.g Table, Collection, ViewController, etc).
|
|
||||||
[self displayNodeDidInvalidateSizeNewSize:layout.size];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)displayNodeDidInvalidateSizeNewSize:(CGSize)size
|
|
||||||
{
|
|
||||||
ASDisplayNodeAssertThreadAffinity(self);
|
|
||||||
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__);
|
|
||||||
|
|
||||||
// The default implementation of display node changes the size of itself to the new size
|
|
||||||
CGRect oldBounds = self.bounds;
|
|
||||||
CGSize oldSize = oldBounds.size;
|
|
||||||
CGSize newSize = size;
|
|
||||||
|
|
||||||
if (! CGSizeEqualToSize(oldSize, newSize)) {
|
|
||||||
self.bounds = (CGRect){ oldBounds.origin, newSize };
|
|
||||||
|
|
||||||
// Frame's origin must be preserved. Since it is computed from bounds size, anchorPoint
|
|
||||||
// and position (see frame setter in ASDisplayNode+UIViewBridge), position needs to be adjusted.
|
|
||||||
CGPoint anchorPoint = self.anchorPoint;
|
|
||||||
CGPoint oldPosition = self.position;
|
|
||||||
CGFloat xDelta = (newSize.width - oldSize.width) * anchorPoint.x;
|
|
||||||
CGFloat yDelta = (newSize.height - oldSize.height) * anchorPoint.y;
|
|
||||||
self.position = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)layout
|
- (void)layout
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssertMainThread();
|
ASDisplayNodeAssertMainThread();
|
||||||
// Subclass hook
|
// Subclass hook
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)_layoutSublayouts
|
|
||||||
{
|
|
||||||
ASDisplayNodeAssertThreadAffinity(self);
|
|
||||||
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__);
|
|
||||||
|
|
||||||
ASLayout *layout;
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
if (_calculatedDisplayNodeLayout->isDirty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
layout = _calculatedDisplayNodeLayout->layout;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (ASDisplayNode *node in self.subnodes) {
|
|
||||||
CGRect frame = [layout frameForElement:node];
|
|
||||||
if (CGRectIsNull(frame)) {
|
|
||||||
// There is no frame for this node in our layout.
|
|
||||||
// This currently can happen if we get a CA layout pass
|
|
||||||
// while waiting for the client to run animateLayoutTransition:
|
|
||||||
} else {
|
|
||||||
node.frame = frame;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark Automatically Manages Subnodes
|
|
||||||
|
|
||||||
- (BOOL)automaticallyManagesSubnodes
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
return _automaticallyManagesSubnodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setAutomaticallyManagesSubnodes:(BOOL)automaticallyManagesSubnodes
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
_automaticallyManagesSubnodes = automaticallyManagesSubnodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark Layout Transition
|
#pragma mark Layout Transition
|
||||||
|
|
||||||
- (void)transitionLayoutWithAnimation:(BOOL)animated
|
|
||||||
shouldMeasureAsync:(BOOL)shouldMeasureAsync
|
|
||||||
measurementCompletion:(void(^)())completion
|
|
||||||
{
|
|
||||||
ASDisplayNodeAssertMainThread();
|
|
||||||
|
|
||||||
[self setNeedsLayout];
|
|
||||||
|
|
||||||
[self transitionLayoutWithSizeRange:[self _locked_constrainedSizeForLayoutPass]
|
|
||||||
animated:animated
|
|
||||||
shouldMeasureAsync:shouldMeasureAsync
|
|
||||||
measurementCompletion:completion];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize
|
|
||||||
animated:(BOOL)animated
|
|
||||||
shouldMeasureAsync:(BOOL)shouldMeasureAsync
|
|
||||||
measurementCompletion:(void(^)())completion
|
|
||||||
{
|
|
||||||
ASDisplayNodeAssertMainThread();
|
|
||||||
|
|
||||||
if (constrainedSize.max.width <= 0.0 || constrainedSize.max.height <= 0.0) {
|
|
||||||
// Using CGSizeZero for the sizeRange can cause negative values in client layout code.
|
|
||||||
// Most likely called transitionLayout: without providing a size, before first layout pass.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we are a subnode in a layout transition.
|
|
||||||
// In this case no measurement is needed as we're part of the layout transition.
|
|
||||||
if ([self _isLayoutTransitionInvalid]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
ASDisplayNodeAssert(ASHierarchyStateIncludesLayoutPending(_hierarchyState) == NO, @"Can't start a transition when one of the supernodes is performing one.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Every new layout transition has a transition id associated to check in subsequent transitions for cancelling
|
|
||||||
int32_t transitionID = [self _startNewTransition];
|
|
||||||
|
|
||||||
// Move all subnodes in layout pending state for this transition
|
|
||||||
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) {
|
|
||||||
ASDisplayNodeAssert([node _isTransitionInProgress] == NO, @"Can't start a transition when one of the subnodes is performing one.");
|
|
||||||
node.hierarchyState |= ASHierarchyStateLayoutPending;
|
|
||||||
node.pendingTransitionID = transitionID;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Transition block that executes the layout transition
|
|
||||||
void (^transitionBlock)(void) = ^{
|
|
||||||
if ([self _shouldAbortTransitionWithID:transitionID]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform a full layout creation pass with passed in constrained size to create the new layout for the transition
|
|
||||||
ASLayout *newLayout;
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
|
|
||||||
ASLayoutElementSetCurrentContext(ASLayoutElementContextMake(transitionID));
|
|
||||||
|
|
||||||
BOOL automaticallyManagesSubnodesDisabled = (self.automaticallyManagesSubnodes == NO);
|
|
||||||
self.automaticallyManagesSubnodes = YES; // Temporary flag for 1.9.x
|
|
||||||
newLayout = [self calculateLayoutThatFits:constrainedSize
|
|
||||||
restrictedToSize:self.style.size
|
|
||||||
relativeToParentSize:constrainedSize.max];
|
|
||||||
if (automaticallyManagesSubnodesDisabled) {
|
|
||||||
self.automaticallyManagesSubnodes = NO; // Temporary flag for 1.9.x
|
|
||||||
}
|
|
||||||
|
|
||||||
ASLayoutElementClearCurrentContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([self _shouldAbortTransitionWithID:transitionID]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ASPerformBlockOnMainThread(^{
|
|
||||||
ASLayoutTransition *pendingLayoutTransition;
|
|
||||||
_ASTransitionContext *pendingLayoutTransitionContext;
|
|
||||||
{
|
|
||||||
// Grab __instanceLock__ here to make sure this transition isn't invalidated
|
|
||||||
// right after it passed the validation test and before it proceeds
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
|
|
||||||
if ([self _locked_shouldAbortTransitionWithID:transitionID]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update calculated layout
|
|
||||||
auto previousLayout = _calculatedDisplayNodeLayout;
|
|
||||||
auto pendingLayout = std::make_shared<ASDisplayNodeLayout>(
|
|
||||||
newLayout,
|
|
||||||
constrainedSize,
|
|
||||||
constrainedSize.max
|
|
||||||
);
|
|
||||||
[self _locked_setCalculatedDisplayNodeLayout:pendingLayout];
|
|
||||||
|
|
||||||
// Setup pending layout transition for animation
|
|
||||||
_pendingLayoutTransition = pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self
|
|
||||||
pendingLayout:pendingLayout
|
|
||||||
previousLayout:previousLayout];
|
|
||||||
// Setup context for pending layout transition. we need to hold a strong reference to the context
|
|
||||||
_pendingLayoutTransitionContext = pendingLayoutTransitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated
|
|
||||||
layoutDelegate:_pendingLayoutTransition
|
|
||||||
completionDelegate:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply complete layout transitions for all subnodes
|
|
||||||
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) {
|
|
||||||
[node _completePendingLayoutTransition];
|
|
||||||
node.hierarchyState &= (~ASHierarchyStateLayoutPending);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Measurement pass completion
|
|
||||||
// Give the subclass a change to hook into before calling the completion block
|
|
||||||
[self _layoutTransitionMeasurementDidFinish];
|
|
||||||
if (completion) {
|
|
||||||
completion();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the subnode insertion immediately to be able to animate the nodes
|
|
||||||
[pendingLayoutTransition applySubnodeInsertions];
|
|
||||||
|
|
||||||
// Kick off animating the layout transition
|
|
||||||
[self animateLayoutTransition:pendingLayoutTransitionContext];
|
|
||||||
|
|
||||||
// Mark transaction as finished
|
|
||||||
[self _finishOrCancelTransition];
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Start transition based on flag on current or background thread
|
|
||||||
if (shouldMeasureAsync) {
|
|
||||||
ASPerformBlockOnBackgroundThread(transitionBlock);
|
|
||||||
} else {
|
|
||||||
transitionBlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)cancelLayoutTransition
|
|
||||||
{
|
|
||||||
__instanceLock__.lock();
|
|
||||||
BOOL transitionInProgress = _transitionInProgress;
|
|
||||||
__instanceLock__.unlock();
|
|
||||||
|
|
||||||
if (transitionInProgress) {
|
|
||||||
// Cancel transition in progress
|
|
||||||
[self _finishOrCancelTransition];
|
|
||||||
|
|
||||||
// Tell subnodes to exit layout pending state and clear related properties
|
|
||||||
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) {
|
|
||||||
node.hierarchyState &= (~ASHierarchyStateLayoutPending);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)_isTransitionInProgress
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
return _transitionInProgress;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)_isLayoutTransitionInvalid
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
if (ASHierarchyStateIncludesLayoutPending(_hierarchyState)) {
|
|
||||||
ASLayoutElementContext context = ASLayoutElementGetCurrentContext();
|
|
||||||
if (ASLayoutElementContextIsNull(context) || _pendingTransitionID != context.transitionID) {
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Starts a new transition and returns the transition id
|
|
||||||
- (int32_t)_startNewTransition
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
_transitionInProgress = YES;
|
|
||||||
_transitionID = OSAtomicAdd32(1, &_transitionID);
|
|
||||||
return _transitionID;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_layoutTransitionMeasurementDidFinish
|
- (void)_layoutTransitionMeasurementDidFinish
|
||||||
{
|
{
|
||||||
// No-Op in ASDisplayNode
|
// Hook for subclasses - No-Op in ASDisplayNode
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_finishOrCancelTransition
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
_transitionInProgress = NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setPendingTransitionID:(int32_t)pendingTransitionID
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
ASDisplayNodeAssertTrue(_pendingTransitionID < pendingTransitionID);
|
|
||||||
_pendingTransitionID = pendingTransitionID;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (int32_t)pendingTransitionID
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
return _pendingTransitionID;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)_shouldAbortTransitionWithID:(int32_t)transitionID
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
return [self _locked_shouldAbortTransitionWithID:transitionID];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)_locked_shouldAbortTransitionWithID:(int32_t)transitionID
|
|
||||||
{
|
|
||||||
return (!_transitionInProgress || _transitionID != transitionID);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setDefaultLayoutTransitionDuration:(NSTimeInterval)defaultLayoutTransitionDuration
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
_defaultLayoutTransitionDuration = defaultLayoutTransitionDuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSTimeInterval)defaultLayoutTransitionDuration
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
return _defaultLayoutTransitionDuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setDefaultLayoutTransitionDelay:(NSTimeInterval)defaultLayoutTransitionDelay
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
_defaultLayoutTransitionDelay = defaultLayoutTransitionDelay;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSTimeInterval)defaultLayoutTransitionDelay
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
return _defaultLayoutTransitionDelay;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setDefaultLayoutTransitionOptions:(UIViewAnimationOptions)defaultLayoutTransitionOptions
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
_defaultLayoutTransitionOptions = defaultLayoutTransitionOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (UIViewAnimationOptions)defaultLayoutTransitionOptions
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
return _defaultLayoutTransitionOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark <LayoutTransitioning>
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Hook for subclasses to perform an animation based on the given ASContextTransitioning. By default a fade in and out
|
|
||||||
* animation is provided.
|
|
||||||
*/
|
|
||||||
- (void)animateLayoutTransition:(id<ASContextTransitioning>)context
|
|
||||||
{
|
|
||||||
if ([context isAnimated] == NO) {
|
|
||||||
[self _layoutSublayouts];
|
|
||||||
[context completeTransition:YES];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ASDisplayNode *node = self;
|
|
||||||
|
|
||||||
NSAssert(node.isNodeLoaded == YES, @"Invalid node state");
|
|
||||||
|
|
||||||
NSArray<ASDisplayNode *> *removedSubnodes = [context removedSubnodes];
|
|
||||||
NSMutableArray<ASDisplayNode *> *insertedSubnodes = [[context insertedSubnodes] mutableCopy];
|
|
||||||
NSMutableArray<ASDisplayNode *> *movedSubnodes = [NSMutableArray array];
|
|
||||||
|
|
||||||
NSMutableArray<_ASAnimatedTransitionContext *> *insertedSubnodeContexts = [NSMutableArray array];
|
|
||||||
NSMutableArray<_ASAnimatedTransitionContext *> *removedSubnodeContexts = [NSMutableArray array];
|
|
||||||
|
|
||||||
for (ASDisplayNode *subnode in [context subnodesForKey:ASTransitionContextToLayoutKey]) {
|
|
||||||
if ([insertedSubnodes containsObject:subnode] == NO) {
|
|
||||||
// This is an existing subnode, check if it is resized, moved or both
|
|
||||||
CGRect fromFrame = [context initialFrameForNode:subnode];
|
|
||||||
CGRect toFrame = [context finalFrameForNode:subnode];
|
|
||||||
if (CGSizeEqualToSize(fromFrame.size, toFrame.size) == NO) {
|
|
||||||
[insertedSubnodes addObject:subnode];
|
|
||||||
}
|
|
||||||
if (CGPointEqualToPoint(fromFrame.origin, toFrame.origin) == NO) {
|
|
||||||
[movedSubnodes addObject:subnode];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create contexts for inserted and removed subnodes
|
|
||||||
for (ASDisplayNode *insertedSubnode in insertedSubnodes) {
|
|
||||||
[insertedSubnodeContexts addObject:[_ASAnimatedTransitionContext contextForNode:insertedSubnode alpha:insertedSubnode.alpha]];
|
|
||||||
}
|
|
||||||
for (ASDisplayNode *removedSubnode in removedSubnodes) {
|
|
||||||
[removedSubnodeContexts addObject:[_ASAnimatedTransitionContext contextForNode:removedSubnode alpha:removedSubnode.alpha]];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fade out inserted subnodes
|
|
||||||
for (ASDisplayNode *insertedSubnode in insertedSubnodes) {
|
|
||||||
insertedSubnode.frame = [context finalFrameForNode:insertedSubnode];
|
|
||||||
insertedSubnode.alpha = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjust groupOpacity for animation
|
|
||||||
BOOL originAllowsGroupOpacity = node.allowsGroupOpacity;
|
|
||||||
node.allowsGroupOpacity = YES;
|
|
||||||
|
|
||||||
[UIView animateWithDuration:self.defaultLayoutTransitionDuration delay:self.defaultLayoutTransitionDelay options:self.defaultLayoutTransitionOptions animations:^{
|
|
||||||
// Fade removed subnodes and views out
|
|
||||||
for (ASDisplayNode *removedSubnode in removedSubnodes) {
|
|
||||||
removedSubnode.alpha = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fade inserted subnodes in
|
|
||||||
for (_ASAnimatedTransitionContext *insertedSubnodeContext in insertedSubnodeContexts) {
|
|
||||||
insertedSubnodeContext.node.alpha = insertedSubnodeContext.alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update frame of self and moved subnodes
|
|
||||||
CGSize fromSize = [context layoutForKey:ASTransitionContextFromLayoutKey].size;
|
|
||||||
CGSize toSize = [context layoutForKey:ASTransitionContextToLayoutKey].size;
|
|
||||||
BOOL isResized = (CGSizeEqualToSize(fromSize, toSize) == NO);
|
|
||||||
if (isResized == YES) {
|
|
||||||
CGPoint position = node.frame.origin;
|
|
||||||
node.frame = CGRectMake(position.x, position.y, toSize.width, toSize.height);
|
|
||||||
}
|
|
||||||
for (ASDisplayNode *movedSubnode in movedSubnodes) {
|
|
||||||
movedSubnode.frame = [context finalFrameForNode:movedSubnode];
|
|
||||||
}
|
|
||||||
} completion:^(BOOL finished) {
|
|
||||||
// Restore all removed subnode alpha values
|
|
||||||
for (_ASAnimatedTransitionContext *removedSubnodeContext in removedSubnodeContexts) {
|
|
||||||
removedSubnodeContext.node.alpha = removedSubnodeContext.alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore group opacity
|
|
||||||
node.allowsGroupOpacity = originAllowsGroupOpacity;
|
|
||||||
|
|
||||||
// Subnode removals are automatically performed
|
|
||||||
[context completeTransition:finished];
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook for subclasses to clean up nodes after the transition happened. Furthermore this can be used from subclasses
|
|
||||||
* to manually perform deletions.
|
|
||||||
*/
|
|
||||||
- (void)didCompleteLayoutTransition:(id<ASContextTransitioning>)context
|
|
||||||
{
|
|
||||||
__instanceLock__.lock();
|
|
||||||
ASLayoutTransition *pendingLayoutTransition = _pendingLayoutTransition;
|
|
||||||
__instanceLock__.unlock();
|
|
||||||
|
|
||||||
[pendingLayoutTransition applySubnodeRemovals];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark <_ASTransitionContextCompletionDelegate>
|
#pragma mark <_ASTransitionContextCompletionDelegate>
|
||||||
@ -1750,86 +1110,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
|||||||
[self _pendingLayoutTransitionDidComplete];
|
[self _pendingLayoutTransitionDidComplete];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Completes the pending layout transition immediately without going through the the Layout Transition Animation API
|
|
||||||
*/
|
|
||||||
- (void)_completePendingLayoutTransition
|
|
||||||
{
|
|
||||||
__instanceLock__.lock();
|
|
||||||
ASLayoutTransition *pendingLayoutTransition = _pendingLayoutTransition;
|
|
||||||
__instanceLock__.unlock();
|
|
||||||
|
|
||||||
if (pendingLayoutTransition != nil) {
|
|
||||||
[self _setCalculatedDisplayNodeLayout:pendingLayoutTransition.pendingLayout];
|
|
||||||
[self _completeLayoutTransition:pendingLayoutTransition];
|
|
||||||
}
|
|
||||||
[self _pendingLayoutTransitionDidComplete];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Can be directly called to commit the given layout transition immediately to complete without calling through to the
|
|
||||||
* Layout Transition Animation API
|
|
||||||
*/
|
|
||||||
- (void)_completeLayoutTransition:(ASLayoutTransition *)layoutTransition
|
|
||||||
{
|
|
||||||
// Layout transition is not supported for nodes that are not have automatic subnode management enabled
|
|
||||||
if (layoutTransition == nil || self.automaticallyManagesSubnodes == NO) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trampoline to the main thread if necessary
|
|
||||||
if (ASDisplayNodeThreadIsMain() || layoutTransition.isSynchronous == NO) {
|
|
||||||
[layoutTransition commitTransition];
|
|
||||||
} else {
|
|
||||||
// Subnode insertions and removals need to happen always on the main thread if at least one subnode is already loaded
|
|
||||||
ASPerformBlockOnMainThread(^{
|
|
||||||
[layoutTransition commitTransition];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_pendingLayoutTransitionDidComplete
|
|
||||||
{
|
|
||||||
// Subclass hook
|
|
||||||
[self calculatedLayoutDidChange];
|
|
||||||
|
|
||||||
// Grab lock after calling out to subclass
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
|
|
||||||
// We generate placeholders at measureWithSizeRange: time so that a node is guaranteed to have a placeholder ready to go.
|
|
||||||
// This is also because measurement is usually asynchronous, but placeholders need to be set up synchronously.
|
|
||||||
// First measurement is guaranteed to be before the node is onscreen, so we can create the image async. but still have it appear sync.
|
|
||||||
if (_placeholderEnabled && !_placeholderImage && [self _locked_displaysAsynchronously]) {
|
|
||||||
|
|
||||||
// Zero-sized nodes do not require a placeholder.
|
|
||||||
ASLayout *layout = _calculatedDisplayNodeLayout->layout;
|
|
||||||
CGSize layoutSize = (layout ? layout.size : CGSizeZero);
|
|
||||||
if (layoutSize.width * layoutSize.height <= 0.0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we've displayed our contents, we don't need a placeholder.
|
|
||||||
// Contents is a thread-affined property and can't be read off main after loading.
|
|
||||||
if (self.isNodeLoaded) {
|
|
||||||
ASPerformBlockOnMainThread(^{
|
|
||||||
if (self.contents == nil) {
|
|
||||||
_placeholderImage = [self placeholderImage];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (self.contents == nil) {
|
|
||||||
_placeholderImage = [self placeholderImage];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup pending layout transition
|
|
||||||
_pendingLayoutTransition = nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)calculatedLayoutDidChange
|
- (void)calculatedLayoutDidChange
|
||||||
{
|
{
|
||||||
// subclass override
|
// Subclass override
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Display
|
#pragma mark - Display
|
||||||
@ -4000,127 +3283,6 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
#pragma mark - ASDisplayNode (ASLayoutElement)
|
|
||||||
|
|
||||||
@implementation ASDisplayNode (ASLayoutElement)
|
|
||||||
|
|
||||||
#pragma mark <ASLayoutElement>
|
|
||||||
|
|
||||||
- (ASLayoutElementStyle *)style
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
if (_style == nil) {
|
|
||||||
_style = [[ASLayoutElementStyle alloc] init];
|
|
||||||
}
|
|
||||||
return _style;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (ASLayoutElementType)layoutElementType
|
|
||||||
{
|
|
||||||
return ASLayoutElementTypeDisplayNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray<id<ASLayoutElement>> *)sublayoutElements
|
|
||||||
{
|
|
||||||
return self.subnodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark Measurement Pass
|
|
||||||
|
|
||||||
- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize
|
|
||||||
{
|
|
||||||
#pragma clang diagnostic push
|
|
||||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
||||||
// For now we just call the deprecated measureWithSizeRange: method to not break old API
|
|
||||||
return [self measureWithSizeRange:constrainedSize];
|
|
||||||
#pragma clang diagnostic pop
|
|
||||||
}
|
|
||||||
|
|
||||||
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize
|
|
||||||
{
|
|
||||||
return [self layoutThatFits:constrainedSize parentSize:constrainedSize.max];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
|
|
||||||
// If one or multiple layout transitions are in flight it still can happen that layout information is requested
|
|
||||||
// on other threads. As the pending and calculated layout to be updated in the layout transition in here just a
|
|
||||||
// layout calculation wil be performed without side effect
|
|
||||||
if ([self _isLayoutTransitionInvalid]) {
|
|
||||||
return [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_calculatedDisplayNodeLayout->isValidForConstrainedSizeParentSize(constrainedSize, parentSize)) {
|
|
||||||
ASDisplayNodeAssertNotNil(_calculatedDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _calculatedDisplayNodeLayout->layout should not be nil! %@", self);
|
|
||||||
// Our calculated layout is suitable for this constrainedSize, so keep using it and
|
|
||||||
// invalidate any pending layout that has been generated in the past.
|
|
||||||
_pendingDisplayNodeLayout = nullptr;
|
|
||||||
return _calculatedDisplayNodeLayout->layout ?: [ASLayout layoutWithLayoutElement:self size:{0, 0}];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a pending display node layout for the layout pass
|
|
||||||
_pendingDisplayNodeLayout = std::make_shared<ASDisplayNodeLayout>(
|
|
||||||
[self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize],
|
|
||||||
constrainedSize,
|
|
||||||
parentSize
|
|
||||||
);
|
|
||||||
|
|
||||||
ASDisplayNodeAssertNotNil(_pendingDisplayNodeLayout->layout, @"-[ASDisplayNode layoutThatFits:parentSize:] _pendingDisplayNodeLayout->layout should not be nil! %@", self);
|
|
||||||
return _pendingDisplayNodeLayout->layout ?: [ASLayout layoutWithLayoutElement:self size:{0, 0}];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark ASLayoutElementStyleExtensibility
|
|
||||||
|
|
||||||
ASLayoutElementStyleExtensibilityForwarding
|
|
||||||
|
|
||||||
#pragma mark ASPrimitiveTraitCollection
|
|
||||||
|
|
||||||
- (ASPrimitiveTraitCollection)primitiveTraitCollection
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
return _primitiveTraitCollection;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traitCollection
|
|
||||||
{
|
|
||||||
__instanceLock__.lock();
|
|
||||||
if (ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(traitCollection, _primitiveTraitCollection) == NO) {
|
|
||||||
_primitiveTraitCollection = traitCollection;
|
|
||||||
ASDisplayNodeLogEvent(self, @"asyncTraitCollectionDidChange: %@", NSStringFromASPrimitiveTraitCollection(traitCollection));
|
|
||||||
__instanceLock__.unlock();
|
|
||||||
|
|
||||||
[self asyncTraitCollectionDidChange];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
__instanceLock__.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
- (ASTraitCollection *)asyncTraitCollection
|
|
||||||
{
|
|
||||||
ASDN::MutexLocker l(__instanceLock__);
|
|
||||||
return [ASTraitCollection traitCollectionWithASPrimitiveTraitCollection:self.primitiveTraitCollection];
|
|
||||||
}
|
|
||||||
|
|
||||||
ASPrimitiveTraitCollectionDeprecatedImplementation
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - ASDisplayNode (ASLayoutElementStylability)
|
|
||||||
|
|
||||||
@implementation ASDisplayNode (ASLayoutElementStylability)
|
|
||||||
|
|
||||||
- (instancetype)styledWithBlock:(AS_NOESCAPE void (^)(__kindof ASLayoutElementStyle *style))styleBlock
|
|
||||||
{
|
|
||||||
styleBlock(self.style);
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
#pragma mark - ASDisplayNode (Debugging)
|
#pragma mark - ASDisplayNode (Debugging)
|
||||||
|
|
||||||
@implementation ASDisplayNode (Debugging)
|
@implementation ASDisplayNode (Debugging)
|
||||||
@ -4139,22 +3301,6 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
|
|||||||
return subtree;
|
return subtree;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - ASLayoutElementAsciiArtProtocol
|
|
||||||
|
|
||||||
- (NSString *)asciiArtString
|
|
||||||
{
|
|
||||||
return [ASLayoutSpec asciiArtStringForChildren:@[] parentName:[self asciiArtName]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)asciiArtName
|
|
||||||
{
|
|
||||||
NSString *string = NSStringFromClass([self class]);
|
|
||||||
if (_debugName) {
|
|
||||||
string = [string stringByAppendingString:[NSString stringWithFormat:@"\"%@\"",_debugName]];
|
|
||||||
}
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
#pragma mark - ASDisplayNode UIKit / CA Categories
|
#pragma mark - ASDisplayNode UIKit / CA Categories
|
||||||
|
|||||||
@ -163,6 +163,8 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyState(ASHierarchyStat
|
|||||||
*/
|
*/
|
||||||
- (BOOL)supportsRangeManagedInterfaceState;
|
- (BOOL)supportsRangeManagedInterfaceState;
|
||||||
|
|
||||||
|
- (BOOL)_locked_displaysAsynchronously;
|
||||||
|
|
||||||
// The two methods below will eventually be exposed, but their names are subject to change.
|
// The two methods below will eventually be exposed, but their names are subject to change.
|
||||||
/**
|
/**
|
||||||
* @abstract Ensure that all rendering is complete for this node and its descendants.
|
* @abstract Ensure that all rendering is complete for this node and its descendants.
|
||||||
@ -224,6 +226,11 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyState(ASHierarchyStat
|
|||||||
*/
|
*/
|
||||||
- (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState;
|
- (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@interface ASDisplayNode (ASsLayoutInternal)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract Informs the root node that the intrinsic size of the receiver is no longer valid.
|
* @abstract Informs the root node that the intrinsic size of the receiver is no longer valid.
|
||||||
*
|
*
|
||||||
@ -239,11 +246,47 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyState(ASHierarchyStat
|
|||||||
- (void)_rootNodeDidInvalidateSize;
|
- (void)_rootNodeDidInvalidateSize;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract Subclass hook for nodes that are acting as root nodes. This method is called after measurement
|
* This method will confirm that the layout is up to date (and update if needed).
|
||||||
* finished in a layout transition but before the measurement completion handler is called
|
* Importantly, it will also APPLY the layout to all of our subnodes if (unless parent is transitioning).
|
||||||
|
*/
|
||||||
|
- (void)_locked_measureNodeWithBoundsIfNecessary:(CGRect)bounds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout all of the subnodes based on the sublayouts
|
||||||
|
*/
|
||||||
|
- (void)_layoutSublayouts;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface ASDisplayNode (ASLayoutTransitionInternal)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sentinel of the current layout transition
|
||||||
|
*/
|
||||||
|
@property (atomic, assign) int32_t pendingTransitionID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If one or multiple layout transitions are in flight this methods returns if the current layout transition that
|
||||||
|
* happens in in this particular thread was invalidated through another thread is starting a transition for this node
|
||||||
|
*/
|
||||||
|
- (BOOL)_isLayoutTransitionInvalid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal method that can be overriden by subclasses to add specific behavior after the measurement of a layout
|
||||||
|
* transition did finish.
|
||||||
*/
|
*/
|
||||||
- (void)_layoutTransitionMeasurementDidFinish;
|
- (void)_layoutTransitionMeasurementDidFinish;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informs the node that hte pending layout transition did complete
|
||||||
|
*/
|
||||||
|
- (void)_completePendingLayoutTransition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called if the pending layout transition did complete
|
||||||
|
*/
|
||||||
|
- (void)_pendingLayoutTransitionDidComplete;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface UIView (ASDisplayNodeInternal)
|
@interface UIView (ASDisplayNodeInternal)
|
||||||
|
|||||||
@ -73,7 +73,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
|
|||||||
|
|
||||||
#define TIME_DISPLAYNODE_OPS 0 // If you're using this information frequently, try: (DEBUG || PROFILE)
|
#define TIME_DISPLAYNODE_OPS 0 // If you're using this information frequently, try: (DEBUG || PROFILE)
|
||||||
|
|
||||||
@interface ASDisplayNode ()
|
@interface ASDisplayNode () <_ASTransitionContextCompletionDelegate>
|
||||||
{
|
{
|
||||||
@package
|
@package
|
||||||
_ASPendingState *_pendingViewState;
|
_ASPendingState *_pendingViewState;
|
||||||
@ -133,6 +133,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
|
|||||||
|
|
||||||
// This is the desired contentsScale, not the scale at which the layer's contents should be displayed
|
// This is the desired contentsScale, not the scale at which the layer's contents should be displayed
|
||||||
CGFloat _contentsScaleForDisplay;
|
CGFloat _contentsScaleForDisplay;
|
||||||
|
ASDisplayNodeMethodOverrides _methodOverrides;
|
||||||
|
|
||||||
UIEdgeInsets _hitTestSlop;
|
UIEdgeInsets _hitTestSlop;
|
||||||
|
|
||||||
@ -146,6 +147,8 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
|
|||||||
NSTimeInterval _defaultLayoutTransitionDuration;
|
NSTimeInterval _defaultLayoutTransitionDuration;
|
||||||
NSTimeInterval _defaultLayoutTransitionDelay;
|
NSTimeInterval _defaultLayoutTransitionDelay;
|
||||||
UIViewAnimationOptions _defaultLayoutTransitionOptions;
|
UIViewAnimationOptions _defaultLayoutTransitionOptions;
|
||||||
|
|
||||||
|
ASLayoutSpecBlock _layoutSpecBlock;
|
||||||
|
|
||||||
int32_t _transitionID;
|
int32_t _transitionID;
|
||||||
BOOL _transitionInProgress;
|
BOOL _transitionInProgress;
|
||||||
@ -162,6 +165,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
|
|||||||
Class _layerClass; // nil -> _ASDisplayLayer
|
Class _layerClass; // nil -> _ASDisplayLayer
|
||||||
|
|
||||||
UIImage *_placeholderImage;
|
UIImage *_placeholderImage;
|
||||||
|
BOOL _placeholderEnabled;
|
||||||
CALayer *_placeholderLayer;
|
CALayer *_placeholderLayer;
|
||||||
|
|
||||||
// keeps track of nodes/subnodes that have not finished display, used with placeholders
|
// keeps track of nodes/subnodes that have not finished display, used with placeholders
|
||||||
@ -200,6 +204,8 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
|
|||||||
NSMutableArray<ASDisplayNode *> *_yogaChildren;
|
NSMutableArray<ASDisplayNode *> *_yogaChildren;
|
||||||
ASLayout *_yogaCalculatedLayout;
|
ASLayout *_yogaCalculatedLayout;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
NSString *_debugName;
|
||||||
|
|
||||||
#if TIME_DISPLAYNODE_OPS
|
#if TIME_DISPLAYNODE_OPS
|
||||||
@public
|
@public
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user