mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-03 21:16:35 +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 "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
|
||||
@ -69,6 +71,22 @@
|
||||
*/
|
||||
- (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 */
|
||||
|
||||
|
||||
@ -181,6 +181,35 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)())
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
@ -263,18 +334,11 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)())
|
||||
|
||||
if (isLayerBacked) {
|
||||
TIME_SCOPED(_debugTimeToCreateView);
|
||||
if (!_layerClass) {
|
||||
_layerClass = [self.class layerClass];
|
||||
}
|
||||
|
||||
_layer = [[_layerClass alloc] init];
|
||||
_layer = [self _layerToLoad];
|
||||
_layer.delegate = self;
|
||||
} else {
|
||||
TIME_SCOPED(_debugTimeToCreateView);
|
||||
if (!_viewClass) {
|
||||
_viewClass = [self.class viewClass];
|
||||
}
|
||||
_view = [[_viewClass alloc] init];
|
||||
_view = [self _viewToLoad];
|
||||
_view.asyncdisplaykit_node = self;
|
||||
_layer = _view.layer;
|
||||
}
|
||||
@ -363,6 +427,9 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)())
|
||||
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
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) {
|
||||
_flags.layerBacked = isLayerBacked;
|
||||
}
|
||||
@ -1616,6 +1683,10 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer,
|
||||
notableTargetDesc = [NSString stringWithFormat:@" [%@]", _viewClass];
|
||||
} else if (_layerClass) { // Nonstandard layer class unloaded
|
||||
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) {
|
||||
return [NSString stringWithFormat:@"<%@ %p name = %@%@>", self.class, self, self.name, notableTargetDesc];
|
||||
|
||||
@ -56,6 +56,8 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) {
|
||||
UIEdgeInsets _hitTestSlop;
|
||||
NSMutableArray *_subnodes;
|
||||
|
||||
ASDisplayNodeViewBlock _viewBlock;
|
||||
ASDisplayNodeLayerBlock _layerBlock;
|
||||
Class _viewClass;
|
||||
Class _layerClass;
|
||||
UIView *_view;
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "_ASDisplayLayer.h"
|
||||
#import "_ASDisplayView.h"
|
||||
#import "ASDisplayNode+Subclasses.h"
|
||||
#import "ASDisplayNodeTestsHelper.h"
|
||||
#import "UIView+ASConvenience.h"
|
||||
@ -87,6 +88,12 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
||||
|
||||
@end
|
||||
|
||||
@interface UIDisplayNodeTestView : UIView
|
||||
@end
|
||||
|
||||
@implementation UIDisplayNodeTestView
|
||||
@end
|
||||
|
||||
@interface ASDisplayNodeTests : XCTestCase
|
||||
@end
|
||||
|
||||
@ -118,6 +125,53 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
||||
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
|
||||
{
|
||||
NSString *targetName = isLayerBacked ? @"layer" : @"view";
|
||||
@ -350,6 +404,92 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
||||
[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.
|
||||
- (void)testDeriveFrameFromBoundsPositionAnchorPoint
|
||||
@ -1355,6 +1495,46 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
|
||||
[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
|
||||
{
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user