mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
Let ASDisplayNode take a block that returns the backing view/layer
This adds new initializer methods to ASDisplayNode:
```objc
initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock
initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock
```
Sometimes a view can't be constructed with `-[initWithViewClass:]` but you want to use it with ASDK, so these new methods provide a way to wrap an existing view in a node.
The API is meant to preserve ASDisplayNode's behavior, so you can still construct and set properties on the node on a background queue before its view is loaded; even though the view was created a priori, it is not considered to be loaded until `node.view` is accessed.
Using the API looks like this:
dispatch_async(backgroundQueue, ^{
ASDisplayNode *node = [ASDisplayNode alloc] initWithViewBlock:^{
// Guaranteed to run on the main queue
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
[button sizeToFit];
node.frame = button.frame;
return button;
}];
// Use `node` as you normally would...
node.backgroundColor = [UIColor redColor];
});
The main thing this bridging API doesn't do (can't do?) is layout. Methods like `-[ASDisplayNode calculateSizeThatFits:]` and `-[ASDisplayNode layout]` cannot delegate to `[UIView sizeThatFits:]` and `[UIView layoutSubviews]` since the UIView methods must run on the main thread. If ASDK were internally asynchronous and could dispatch its layout methods to different threads (sort of like how ASTableView computes its cells' layouts) then we could mark nodes with externally provided views/layers as having "main-queue affinity" and delegate its layout to UIKit.
Test cases are included and all existing tests pass.
This commit is contained in:
@@ -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];
|
||||||
|
|||||||
Reference in New Issue
Block a user