mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-04 13:38:21 +00:00
Merge pull request #235 from ide/bridge-node
ASBridgeNode: wrapper around an existing UIView with ASDisplayNode semantics
This commit is contained in:
commit
46f2d723f2
@ -12,6 +12,8 @@
|
|||||||
#import "ASBaseDefines.h"
|
#import "ASBaseDefines.h"
|
||||||
#import "ASDealloc2MainObject.h"
|
#import "ASDealloc2MainObject.h"
|
||||||
|
|
||||||
|
typedef UIView *(^ASDisplayNodeViewBlock)();
|
||||||
|
typedef CALayer *(^ASDisplayNodeLayerBlock)();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An `ASDisplayNode` is an abstraction over `UIView` and `CALayer` that allows you to perform calculations about a view
|
* An `ASDisplayNode` is an abstraction over `UIView` and `CALayer` that allows you to perform calculations about a view
|
||||||
@ -69,6 +71,22 @@
|
|||||||
*/
|
*/
|
||||||
- (id)initWithLayerClass:(Class)layerClass;
|
- (id)initWithLayerClass:(Class)layerClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Alternative initializer with a block to create the backing view.
|
||||||
|
*
|
||||||
|
* @return An ASDisplayNode instance that loads its view with the given block that is guaranteed to run on the main
|
||||||
|
* queue. The view will render synchronously and -layout and touch handling methods on the node will not be called.
|
||||||
|
*/
|
||||||
|
- (id)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Alternative initializer with a block to create the backing layer.
|
||||||
|
*
|
||||||
|
* @return An ASDisplayNode instance that loads its layer with the given block that is guaranteed to run on the main
|
||||||
|
* queue. The layer will render synchronously and -layout and touch handling methods on the node will not be called.
|
||||||
|
*/
|
||||||
|
- (id)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock;
|
||||||
|
|
||||||
|
|
||||||
/** @name Properties */
|
/** @name Properties */
|
||||||
|
|
||||||
|
|||||||
@ -181,6 +181,35 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)())
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (id)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock
|
||||||
|
{
|
||||||
|
if (!(self = [super init]))
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
ASDisplayNodeAssertNotNil(viewBlock, @"should initialize with a valid block that returns a UIView");
|
||||||
|
|
||||||
|
[self _initializeInstance];
|
||||||
|
_viewBlock = viewBlock;
|
||||||
|
_flags.synchronous = YES;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock
|
||||||
|
{
|
||||||
|
if (!(self = [super init]))
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
ASDisplayNodeAssertNotNil(layerBlock, @"should initialize with a valid block that returns a CALayer");
|
||||||
|
|
||||||
|
[self _initializeInstance];
|
||||||
|
_layerBlock = layerBlock;
|
||||||
|
_flags.synchronous = YES;
|
||||||
|
_flags.layerBacked = YES;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
- (void)dealloc
|
- (void)dealloc
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssertMainThread();
|
ASDisplayNodeAssertMainThread();
|
||||||
@ -249,6 +278,48 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)())
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (UIView *)_viewToLoad
|
||||||
|
{
|
||||||
|
UIView *view;
|
||||||
|
ASDN::MutexLocker l(_propertyLock);
|
||||||
|
|
||||||
|
if (_viewBlock) {
|
||||||
|
view = _viewBlock();
|
||||||
|
ASDisplayNodeAssertNotNil(view, @"View block returned nil");
|
||||||
|
ASDisplayNodeAssert(![view isKindOfClass:[_ASDisplayView class]], @"View block should return a synchronously displayed view");
|
||||||
|
_viewBlock = nil;
|
||||||
|
_viewClass = [view class];
|
||||||
|
} else {
|
||||||
|
if (!_viewClass) {
|
||||||
|
_viewClass = [self.class viewClass];
|
||||||
|
}
|
||||||
|
view = [[_viewClass alloc] init];
|
||||||
|
}
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CALayer *)_layerToLoad
|
||||||
|
{
|
||||||
|
CALayer *layer;
|
||||||
|
ASDN::MutexLocker l(_propertyLock);
|
||||||
|
|
||||||
|
if (_layerBlock) {
|
||||||
|
layer = _layerBlock();
|
||||||
|
ASDisplayNodeAssertNotNil(layer, @"Layer block returned nil");
|
||||||
|
ASDisplayNodeAssert(![layer isKindOfClass:[_ASDisplayLayer class]], @"Layer block should return a synchronously displayed layer");
|
||||||
|
_layerBlock = nil;
|
||||||
|
_layerClass = [layer class];
|
||||||
|
} else {
|
||||||
|
if (!_layerClass) {
|
||||||
|
_layerClass = [self.class layerClass];
|
||||||
|
}
|
||||||
|
layer = [[_layerClass alloc] init];
|
||||||
|
}
|
||||||
|
|
||||||
|
return layer;
|
||||||
|
}
|
||||||
|
|
||||||
- (void)_loadViewOrLayerIsLayerBacked:(BOOL)isLayerBacked
|
- (void)_loadViewOrLayerIsLayerBacked:(BOOL)isLayerBacked
|
||||||
{
|
{
|
||||||
ASDN::MutexLocker l(_propertyLock);
|
ASDN::MutexLocker l(_propertyLock);
|
||||||
@ -263,18 +334,11 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)())
|
|||||||
|
|
||||||
if (isLayerBacked) {
|
if (isLayerBacked) {
|
||||||
TIME_SCOPED(_debugTimeToCreateView);
|
TIME_SCOPED(_debugTimeToCreateView);
|
||||||
if (!_layerClass) {
|
_layer = [self _layerToLoad];
|
||||||
_layerClass = [self.class layerClass];
|
|
||||||
}
|
|
||||||
|
|
||||||
_layer = [[_layerClass alloc] init];
|
|
||||||
_layer.delegate = self;
|
_layer.delegate = self;
|
||||||
} else {
|
} else {
|
||||||
TIME_SCOPED(_debugTimeToCreateView);
|
TIME_SCOPED(_debugTimeToCreateView);
|
||||||
if (!_viewClass) {
|
_view = [self _viewToLoad];
|
||||||
_viewClass = [self.class viewClass];
|
|
||||||
}
|
|
||||||
_view = [[_viewClass alloc] init];
|
|
||||||
_view.asyncdisplaykit_node = self;
|
_view.asyncdisplaykit_node = self;
|
||||||
_layer = _view.layer;
|
_layer = _view.layer;
|
||||||
}
|
}
|
||||||
@ -363,6 +427,9 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)())
|
|||||||
|
|
||||||
ASDN::MutexLocker l(_propertyLock);
|
ASDN::MutexLocker l(_propertyLock);
|
||||||
ASDisplayNodeAssert(!_view && !_layer, @"Cannot change isLayerBacked after layer or view has loaded");
|
ASDisplayNodeAssert(!_view && !_layer, @"Cannot change isLayerBacked after layer or view has loaded");
|
||||||
|
ASDisplayNodeAssert(!_viewBlock && !_layerBlock, @"Cannot change isLayerBacked when a layer or view block is provided");
|
||||||
|
ASDisplayNodeAssert(!_viewClass && !_layerClass, @"Cannot change isLayerBacked when a layer or view class is provided");
|
||||||
|
|
||||||
if (isLayerBacked != _flags.layerBacked && !_view && !_layer) {
|
if (isLayerBacked != _flags.layerBacked && !_view && !_layer) {
|
||||||
_flags.layerBacked = isLayerBacked;
|
_flags.layerBacked = isLayerBacked;
|
||||||
}
|
}
|
||||||
@ -1616,6 +1683,10 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer,
|
|||||||
notableTargetDesc = [NSString stringWithFormat:@" [%@]", _viewClass];
|
notableTargetDesc = [NSString stringWithFormat:@" [%@]", _viewClass];
|
||||||
} else if (_layerClass) { // Nonstandard layer class unloaded
|
} else if (_layerClass) { // Nonstandard layer class unloaded
|
||||||
notableTargetDesc = [NSString stringWithFormat:@" [%@]", _layerClass];
|
notableTargetDesc = [NSString stringWithFormat:@" [%@]", _layerClass];
|
||||||
|
} else if (_viewBlock) { // Nonstandard lazy view unloaded
|
||||||
|
notableTargetDesc = @" [block]";
|
||||||
|
} else if (_layerBlock) { // Nonstandard lazy layer unloaded
|
||||||
|
notableTargetDesc = @" [block]";
|
||||||
}
|
}
|
||||||
if (self.name) {
|
if (self.name) {
|
||||||
return [NSString stringWithFormat:@"<%@ %p name = %@%@>", self.class, self, self.name, notableTargetDesc];
|
return [NSString stringWithFormat:@"<%@ %p name = %@%@>", self.class, self, self.name, notableTargetDesc];
|
||||||
|
|||||||
@ -56,6 +56,8 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) {
|
|||||||
UIEdgeInsets _hitTestSlop;
|
UIEdgeInsets _hitTestSlop;
|
||||||
NSMutableArray *_subnodes;
|
NSMutableArray *_subnodes;
|
||||||
|
|
||||||
|
ASDisplayNodeViewBlock _viewBlock;
|
||||||
|
ASDisplayNodeLayerBlock _layerBlock;
|
||||||
Class _viewClass;
|
Class _viewClass;
|
||||||
Class _layerClass;
|
Class _layerClass;
|
||||||
UIView *_view;
|
UIView *_view;
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
#import <XCTest/XCTest.h>
|
#import <XCTest/XCTest.h>
|
||||||
|
|
||||||
#import "_ASDisplayLayer.h"
|
#import "_ASDisplayLayer.h"
|
||||||
|
#import "_ASDisplayView.h"
|
||||||
#import "ASDisplayNode+Subclasses.h"
|
#import "ASDisplayNode+Subclasses.h"
|
||||||
#import "ASDisplayNodeTestsHelper.h"
|
#import "ASDisplayNodeTestsHelper.h"
|
||||||
#import "UIView+ASConvenience.h"
|
#import "UIView+ASConvenience.h"
|
||||||
@ -87,6 +88,12 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@interface UIDisplayNodeTestView : UIView
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation UIDisplayNodeTestView
|
||||||
|
@end
|
||||||
|
|
||||||
@interface ASDisplayNodeTests : XCTestCase
|
@interface ASDisplayNodeTests : XCTestCase
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@ -118,6 +125,53 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
|||||||
XCTAssertNotNil(view, @"Getting node's view on-thread should succeed.");
|
XCTAssertNotNil(view, @"Getting node's view on-thread should succeed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)testNodeCreatedOffThreadWithExistingView
|
||||||
|
{
|
||||||
|
UIView *view = [[UIDisplayNodeTestView alloc] init];
|
||||||
|
|
||||||
|
__block ASDisplayNode *node = nil;
|
||||||
|
[self executeOffThread:^{
|
||||||
|
node = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{
|
||||||
|
return view;
|
||||||
|
}];
|
||||||
|
}];
|
||||||
|
|
||||||
|
XCTAssertFalse(node.layerBacked, @"Can't be layer backed");
|
||||||
|
XCTAssertTrue(node.synchronous, @"Node with plain view should be synchronous");
|
||||||
|
XCTAssertFalse(node.nodeLoaded, @"Shouldn't have a view yet");
|
||||||
|
XCTAssertEqual(view, node.view, @"Getting node's view on-thread should succeed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testNodeCreatedOffThreadWithLazyView
|
||||||
|
{
|
||||||
|
__block UIView *view = nil;
|
||||||
|
__block ASDisplayNode *node = nil;
|
||||||
|
[self executeOffThread:^{
|
||||||
|
node = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{
|
||||||
|
XCTAssertTrue([NSThread isMainThread], @"View block must run on the main queue");
|
||||||
|
view = [[UIDisplayNodeTestView alloc] init];
|
||||||
|
return view;
|
||||||
|
}];
|
||||||
|
}];
|
||||||
|
|
||||||
|
XCTAssertNil(view, @"View block should not be invoked yet");
|
||||||
|
[node view];
|
||||||
|
XCTAssertNotNil(view, @"View block should have been invoked");
|
||||||
|
XCTAssertEqual(view, node.view, @"Getting node's view on-thread should succeed.");
|
||||||
|
XCTAssertTrue(node.synchronous, @"Node with plain view should be synchronous");
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testNodeCreatedWithLazyAsyncView
|
||||||
|
{
|
||||||
|
ASDisplayNode *node = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{
|
||||||
|
XCTAssertTrue([NSThread isMainThread], @"View block must run on the main queue");
|
||||||
|
return [[_ASDisplayView alloc] init];
|
||||||
|
}];
|
||||||
|
|
||||||
|
XCTAssertThrows([node view], @"Externally provided views should be synchronous");
|
||||||
|
XCTAssertTrue(node.synchronous, @"Node with externally provided view should be synchronous");
|
||||||
|
}
|
||||||
|
|
||||||
- (void)checkValuesMatchDefaults:(ASDisplayNode *)node isLayerBacked:(BOOL)isLayerBacked
|
- (void)checkValuesMatchDefaults:(ASDisplayNode *)node isLayerBacked:(BOOL)isLayerBacked
|
||||||
{
|
{
|
||||||
NSString *targetName = isLayerBacked ? @"layer" : @"view";
|
NSString *targetName = isLayerBacked ? @"layer" : @"view";
|
||||||
@ -350,6 +404,92 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
|||||||
[self checkSimpleBridgePropertiesSetPropagate:YES];
|
[self checkSimpleBridgePropertiesSetPropagate:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)testPropertiesSetOffThreadBeforeLoadingExternalView
|
||||||
|
{
|
||||||
|
UIView *view = [[UIDisplayNodeTestView alloc] init];
|
||||||
|
|
||||||
|
__block ASDisplayNode *node = nil;
|
||||||
|
[self executeOffThread:^{
|
||||||
|
node = [[ASDisplayNode alloc] initWithViewBlock:^{
|
||||||
|
return view;
|
||||||
|
}];
|
||||||
|
node.backgroundColor = [UIColor blueColor];
|
||||||
|
node.frame = CGRectMake(10, 20, 30, 40);
|
||||||
|
node.autoresizingMask = UIViewAutoresizingFlexibleWidth;
|
||||||
|
node.userInteractionEnabled = YES;
|
||||||
|
}];
|
||||||
|
|
||||||
|
[self checkExternalViewAppliedPropertiesMatch:node];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testPropertiesSetOnThreadAfterLoadingExternalView
|
||||||
|
{
|
||||||
|
UIView *view = [[UIDisplayNodeTestView alloc] init];
|
||||||
|
ASDisplayNode *node = [[ASDisplayNode alloc] initWithViewBlock:^{
|
||||||
|
return view;
|
||||||
|
}];
|
||||||
|
|
||||||
|
// Load the backing view first
|
||||||
|
[node view];
|
||||||
|
|
||||||
|
node.backgroundColor = [UIColor blueColor];
|
||||||
|
node.frame = CGRectMake(10, 20, 30, 40);
|
||||||
|
node.autoresizingMask = UIViewAutoresizingFlexibleWidth;
|
||||||
|
node.userInteractionEnabled = YES;
|
||||||
|
|
||||||
|
[self checkExternalViewAppliedPropertiesMatch:node];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)checkExternalViewAppliedPropertiesMatch:(ASDisplayNode *)node
|
||||||
|
{
|
||||||
|
UIView *view = node.view;
|
||||||
|
|
||||||
|
XCTAssertEqualObjects([UIColor blueColor], view.backgroundColor, @"backgroundColor not propagated to view");
|
||||||
|
XCTAssertTrue(CGRectEqualToRect(CGRectMake(10, 20, 30, 40), view.frame), @"frame not propagated to view");
|
||||||
|
XCTAssertEqual(UIViewAutoresizingFlexibleWidth, view.autoresizingMask, @"autoresizingMask not propagated to view");
|
||||||
|
XCTAssertEqual(YES, view.userInteractionEnabled, @"userInteractionEnabled not propagated to view");
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testPropertiesSetOffThreadBeforeLoadingExternalLayer
|
||||||
|
{
|
||||||
|
CALayer *layer = [[CAShapeLayer alloc] init];
|
||||||
|
|
||||||
|
__block ASDisplayNode *node = nil;
|
||||||
|
[self executeOffThread:^{
|
||||||
|
node = [[ASDisplayNode alloc] initWithLayerBlock:^{
|
||||||
|
return layer;
|
||||||
|
}];
|
||||||
|
node.backgroundColor = [UIColor blueColor];
|
||||||
|
node.frame = CGRectMake(10, 20, 30, 40);
|
||||||
|
}];
|
||||||
|
|
||||||
|
[self checkExternalLayerAppliedPropertiesMatch:node];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testPropertiesSetOnThreadAfterLoadingExternalLayer
|
||||||
|
{
|
||||||
|
CALayer *layer = [[CAShapeLayer alloc] init];
|
||||||
|
ASDisplayNode *node = [[ASDisplayNode alloc] initWithLayerBlock:^{
|
||||||
|
return layer;
|
||||||
|
}];
|
||||||
|
|
||||||
|
// Load the backing layer first
|
||||||
|
[node layer];
|
||||||
|
|
||||||
|
node.backgroundColor = [UIColor blueColor];
|
||||||
|
node.frame = CGRectMake(10, 20, 30, 40);
|
||||||
|
|
||||||
|
[self checkExternalLayerAppliedPropertiesMatch:node];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)checkExternalLayerAppliedPropertiesMatch:(ASDisplayNode *)node
|
||||||
|
{
|
||||||
|
CALayer *layer = node.layer;
|
||||||
|
|
||||||
|
XCTAssertTrue(CGColorEqualToColor([UIColor blueColor].CGColor, layer.backgroundColor), @"backgroundColor not propagated to layer");
|
||||||
|
XCTAssertTrue(CGRectEqualToRect(CGRectMake(10, 20, 30, 40), layer.frame), @"frame not propagated to layer");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Perform parallel updates of a standard UIView/CALayer and an ASDisplayNode and ensure they are equivalent.
|
// Perform parallel updates of a standard UIView/CALayer and an ASDisplayNode and ensure they are equivalent.
|
||||||
- (void)testDeriveFrameFromBoundsPositionAnchorPoint
|
- (void)testDeriveFrameFromBoundsPositionAnchorPoint
|
||||||
@ -1355,6 +1495,46 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
|
|||||||
[c release];
|
[c release];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)testSubnodeAddedBeforeLoadingExternalView
|
||||||
|
{
|
||||||
|
UIView *view = [[UIDisplayNodeTestView alloc] init];
|
||||||
|
|
||||||
|
__block ASDisplayNode *parent = nil;
|
||||||
|
__block ASDisplayNode *child = nil;
|
||||||
|
[self executeOffThread:^{
|
||||||
|
parent = [[ASDisplayNode alloc] initWithViewBlock:^{
|
||||||
|
return view;
|
||||||
|
}];
|
||||||
|
child = [[ASDisplayNode alloc] init];
|
||||||
|
[parent addSubnode:child];
|
||||||
|
}];
|
||||||
|
|
||||||
|
XCTAssertEqual(1, parent.subnodes.count, @"Parent should have 1 subnode");
|
||||||
|
XCTAssertEqualObjects(parent, child.supernode, @"Child has the wrong parent");
|
||||||
|
XCTAssertEqual(0, view.subviews.count, @"View shouldn't have any subviews");
|
||||||
|
|
||||||
|
[parent view];
|
||||||
|
|
||||||
|
XCTAssertEqual(1, view.subviews.count, @"View should have 1 subview");
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testSubnodeAddedAfterLoadingExternalView
|
||||||
|
{
|
||||||
|
UIView *view = [[UIDisplayNodeTestView alloc] init];
|
||||||
|
ASDisplayNode *parent = [[ASDisplayNode alloc] initWithViewBlock:^{
|
||||||
|
return view;
|
||||||
|
}];
|
||||||
|
|
||||||
|
[parent view];
|
||||||
|
|
||||||
|
ASDisplayNode *child = [[ASDisplayNode alloc] init];
|
||||||
|
[parent addSubnode:child];
|
||||||
|
|
||||||
|
XCTAssertEqual(1, parent.subnodes.count, @"Parent should have 1 subnode");
|
||||||
|
XCTAssertEqualObjects(parent, child.supernode, @"Child has the wrong parent");
|
||||||
|
XCTAssertEqual(1, view.subviews.count, @"View should have 1 subview");
|
||||||
|
}
|
||||||
|
|
||||||
- (void)checkBackgroundColorOpaqueRelationshipWithViewLoaded:(BOOL)loaded layerBacked:(BOOL)isLayerBacked
|
- (void)checkBackgroundColorOpaqueRelationshipWithViewLoaded:(BOOL)loaded layerBacked:(BOOL)isLayerBacked
|
||||||
{
|
{
|
||||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user