Skip to content

Commit

Permalink
Dark Mode dynamic rendering preparation, adding experiment to propaga…
Browse files Browse the repository at this point in the history
…te older trait collections (TextureGroup#1637)

- Adding experiment to propagate older trait collections when trait
collections change
- Re-render nodes when user interface style changes (dark mode)
- Snapshot testing examples for dark mode
- Fixes for applying trait collection overrides in XCTest
- Create new define for gating Xcode 11 / iOS 13 features until CI is running it
  • Loading branch information
rahul-malik authored and matthewd1234 committed Aug 25, 2019
1 parent d6c27d4 commit 88c5d03
Show file tree
Hide file tree
Showing 17 changed files with 181 additions and 16 deletions.
8 changes: 7 additions & 1 deletion Source/ASDisplayNode+Layout.mm
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,16 @@ - (void)setPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traitCollection
{
AS::UniqueLock l(__instanceLock__);
if (ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(traitCollection, _primitiveTraitCollection) == NO) {
ASPrimitiveTraitCollection previousTraitCollection = _primitiveTraitCollection;
_primitiveTraitCollection = traitCollection;

l.unlock();
[self asyncTraitCollectionDidChange];
if (ASActivateExperimentalFeature(ASExperimentalTraitCollectionDidChangeWithPreviousCollection)) {
[self asyncTraitCollectionDidChangeWithPreviousTraitCollection:previousTraitCollection];
} else {
[self asyncTraitCollectionDidChange];
}

}
}

Expand Down
14 changes: 13 additions & 1 deletion Source/ASDisplayNode+Subclasses.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#import <AsyncDisplayKit/ASBlockTypes.h>
#import <AsyncDisplayKit/ASDisplayNode.h>
#import <AsyncDisplayKit/ASDisplayNode+LayoutSpec.h>
#import <AsyncDisplayKit/ASTraitCollection.h>

@class ASLayoutSpec, _ASDisplayLayer;

Expand Down Expand Up @@ -206,7 +207,18 @@ AS_CATEGORY_IMPLEMENTABLE
* @discussion Subclasses can override this method to react to a trait collection change.
*/
AS_CATEGORY_IMPLEMENTABLE
- (void)asyncTraitCollectionDidChange;
- (void)asyncTraitCollectionDidChange ASDISPLAYNODE_REQUIRES_SUPER;


/**
* @abstract Called when the node's ASTraitCollection changes
*
* @discussion Subclasses can override this method to react to a trait collection change. Use `ASExperimentalTraitCollectionDidChangeWithPreviousCollection` to have this method called instead of `asyncTraitCollectionDidChange`.
*
* @param previousTraitCollection The ASPrimitiveTraitCollection object before the interface environment changed.
*/
AS_CATEGORY_IMPLEMENTABLE
- (void)asyncTraitCollectionDidChangeWithPreviousTraitCollection:(ASPrimitiveTraitCollection)previousTraitCollection ASDISPLAYNODE_REQUIRES_SUPER;

#pragma mark - Drawing
/** @name Drawing */
Expand Down
16 changes: 16 additions & 0 deletions Source/ASDisplayNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,22 @@ - (void)onDidLoad:(ASDisplayNodeDidLoadBlock)body
}
}


- (void)asyncTraitCollectionDidChangeWithPreviousTraitCollection:(ASPrimitiveTraitCollection)previousTraitCollection
{
if (@available(iOS 13.0, *)) {
__instanceLock__.lock();
if (self.primitiveTraitCollection.userInterfaceStyle != previousTraitCollection.userInterfaceStyle) {
// When changing between light and dark mode, often the entire node needs to re-render.
// This change doesn't happen frequently so it's fairly safe to render nodes again
__instanceLock__.unlock();
[self setNeedsDisplay];
return;
}
__instanceLock__.unlock();
}
}

- (void)dealloc
{
_flags.isDeallocating = YES;
Expand Down
27 changes: 14 additions & 13 deletions Source/ASExperimentalFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,20 @@ NS_ASSUME_NONNULL_BEGIN
typedef NS_OPTIONS(NSUInteger, ASExperimentalFeatures) {
// If AS_ENABLE_TEXTNODE=0 or TextNode2 subspec is used this setting is a no op and ASTextNode2
// will be used in all cases
ASExperimentalTextNode = 1 << 0, // exp_text_node
ASExperimentalInterfaceStateCoalescing = 1 << 1, // exp_interface_state_coalesce
ASExperimentalUnfairLock = 1 << 2, // exp_unfair_lock
ASExperimentalLayerDefaults = 1 << 3, // exp_infer_layer_defaults
ASExperimentalCollectionTeardown = 1 << 4, // exp_collection_teardown
ASExperimentalFramesetterCache = 1 << 5, // exp_framesetter_cache
ASExperimentalSkipClearData = 1 << 6, // exp_skip_clear_data
ASExperimentalDidEnterPreloadSkipASMLayout = 1 << 7, // exp_did_enter_preload_skip_asm_layout
ASExperimentalDispatchApply = 1 << 8, // exp_dispatch_apply
ASExperimentalOOMBackgroundDeallocDisable = 1 << 9, // exp_oom_bg_dealloc_disable
ASExperimentalRemoveTextKitInitialisingLock = 1 << 10, // exp_remove_textkit_initialising_lock
ASExperimentalDrawingGlobal = 1 << 11, // exp_drawing_global
ASExperimentalOptimizeDataControllerPipeline = 1 << 12, // exp_optimize_data_controller_pipeline
ASExperimentalTextNode = 1 << 0, // exp_text_node
ASExperimentalInterfaceStateCoalescing = 1 << 1, // exp_interface_state_coalesce
ASExperimentalUnfairLock = 1 << 2, // exp_unfair_lock
ASExperimentalLayerDefaults = 1 << 3, // exp_infer_layer_defaults
ASExperimentalCollectionTeardown = 1 << 4, // exp_collection_teardown
ASExperimentalFramesetterCache = 1 << 5, // exp_framesetter_cache
ASExperimentalSkipClearData = 1 << 6, // exp_skip_clear_data
ASExperimentalDidEnterPreloadSkipASMLayout = 1 << 7, // exp_did_enter_preload_skip_asm_layout
ASExperimentalDispatchApply = 1 << 8, // exp_dispatch_apply
ASExperimentalOOMBackgroundDeallocDisable = 1 << 9, // exp_oom_bg_dealloc_disable
ASExperimentalRemoveTextKitInitialisingLock = 1 << 10, // exp_remove_textkit_initialising_lock
ASExperimentalDrawingGlobal = 1 << 11, // exp_drawing_global
ASExperimentalOptimizeDataControllerPipeline = 1 << 12, // exp_optimize_data_controller_pipeline
ASExperimentalTraitCollectionDidChangeWithPreviousCollection = 1 << 13, // exp_trait_collection_did_change_with_previous_collection
ASExperimentalFeatureAll = 0xFFFFFFFF
};

Expand Down
3 changes: 2 additions & 1 deletion Source/ASExperimentalFeatures.mm
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
@"exp_oom_bg_dealloc_disable",
@"exp_remove_textkit_initialising_lock",
@"exp_drawing_global",
@"exp_optimize_data_controller_pipeline"]));
@"exp_optimize_data_controller_pipeline",
@"exp_trait_collection_did_change_with_previous_collection"]));
if (flags == ASExperimentalFeatureAll) {
return allNames;
}
Expand Down
5 changes: 5 additions & 0 deletions Source/Base/ASAvailability.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,13 @@
#define __IPHONE_11_0 110000
#endif

#ifndef __IPHONE_13_0
#define __IPHONE_13_0 130000
#endif

#define AS_AT_LEAST_IOS10 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_10_0)
#define AS_AT_LEAST_IOS11 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_11_0)
#define AS_AT_LEAST_IOS13 (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)

// Use __builtin_available if we're on Xcode >= 9, AS_AT_LEAST otherwise.
#if __has_builtin(__builtin_available)
Expand Down
24 changes: 24 additions & 0 deletions Tests/ASDisplayNodeSnapshotTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,28 @@ - (void)testClippingCornerRounding
}
}

// Enable test when background color on ASDisplayNode PR merges
//- (void)testUserInterfaceStyleSnapshotTesting
//{
// if (@available(iOS 13.0, *)) {
// UITraitCollection.currentTraitCollection = [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight];
//
// ASDisplayNode *node = [[ASDisplayNode alloc] init];
// [node setLayerBacked:YES];
//
// node.backgroundColor = [UIColor systemBackgroundColor];
//
// node.style.preferredSize = CGSizeMake(100, 100);
// ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY)));
//
// [[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight] performAsCurrentTraitCollection:^{
// ASSnapshotVerifyNode(node, @"user_interface_style_light");
// }];
//
// [[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark] performAsCurrentTraitCollection:^{
// ASSnapshotVerifyNode(node, @"user_interface_style_dark");
// }];
// }
//}

@end
9 changes: 9 additions & 0 deletions Tests/ASSnapshotTestCase.mm
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ @implementation ASSnapshotTestCase

+ (void)hackilySynchronouslyRecursivelyRenderNode:(ASDisplayNode *)node
{
// Disable asynchronous display for rendering snapshots since things like UITraitCollection are thread-local
// so changes to them (`-[UITraitCollection performAsCurrentTraitCollection]`) aren't preserved across threads.
// Since the goal of this method is to just to ensure a node is rendered before snapshotting, this should be reasonable default for all callers.
#if AS_AT_LEAST_IOS13
if (@available(iOS 13.0, *)) {
node.primitiveTraitCollection = ASPrimitiveTraitCollectionFromUITraitCollection(UITraitCollection.currentTraitCollection);
}
#endif // #if AS_AT_LEAST_IOS13
node.displaysAsynchronously = NO;
ASDisplayNodePerformBlockOnEveryNode(nil, node, YES, ^(ASDisplayNode * _Nonnull node) {
[node.layer setNeedsDisplay];
});
Expand Down
91 changes: 91 additions & 0 deletions Tests/ASTextNodeSnapshotTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,95 @@ - (void)testFontPointSizeScaling
ASSnapshotVerifyNode(textNode, nil);
}




#if AS_AT_LEAST_IOS13

- (void)testUserInterfaceStyleSnapshotTesting
{
if (@available(iOS 13.0, *)) {
UITraitCollection.currentTraitCollection = [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight];

ASTextNode *node = [[ASTextNode alloc] init];

[node setLayerBacked:YES];
node.primitiveTraitCollection = ASPrimitiveTraitCollectionFromUITraitCollection(UITraitCollection.currentTraitCollection);

UIColor *labelColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return [UIColor whiteColor];
} else {
return [UIColor blackColor];
}
}];

UIColor *backgroundColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return [UIColor blackColor];
} else {
return [UIColor whiteColor];
}
}];

node.attributedText = [[NSAttributedString alloc] initWithString:@"Hello" attributes:@{ NSForegroundColorAttributeName : labelColor,
NSBackgroundColorAttributeName : backgroundColor
}];
ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY)));

[[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight] performAsCurrentTraitCollection:^{
node.primitiveTraitCollection = ASPrimitiveTraitCollectionFromUITraitCollection(UITraitCollection.currentTraitCollection);
ASSnapshotVerifyNode(node, @"user_interface_style_light");
}];


[[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark] performAsCurrentTraitCollection:^{
ASSnapshotVerifyNode(node, @"user_interface_style_dark");
}];
}
}

- (void)testUserInterfaceStyleSnapshotTestingTintColor
{
if (@available(iOS 13.0, *)) {
UITraitCollection.currentTraitCollection = [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight];

ASTextNode *node = [[ASTextNode alloc] init];

[node setLayerBacked:YES];
node.primitiveTraitCollection = ASPrimitiveTraitCollectionFromUITraitCollection(UITraitCollection.currentTraitCollection);

UIColor *tintColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return [UIColor whiteColor];
} else {
return [UIColor blackColor];
}
}];

UIColor *backgroundColor = [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return [UIColor blackColor];
} else {
return [UIColor whiteColor];
}
}];
node.tintColor = tintColor;
node.textColorFollowsTintColor = YES;
node.attributedText = [[NSAttributedString alloc] initWithString:@"Hello" attributes:@{ NSBackgroundColorAttributeName : backgroundColor
}];
ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY)));

[[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight] performAsCurrentTraitCollection:^{
ASSnapshotVerifyNode(node, @"user_interface_style_light");
}];

[[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark] performAsCurrentTraitCollection:^{
ASSnapshotVerifyNode(node, @"user_interface_style_dark");
}];
}
}

#endif // #if AS_AT_LEAST_IOS13

@end
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 88c5d03

Please sign in to comment.