Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set background color directly to view if not layer backed #1588

Merged
merged 8 commits into from
Jul 20, 2019

Conversation

rahul-malik
Copy link
Contributor

@rahul-malik rahul-malik commented Jul 17, 2019

UIColor can now be a dynamic color provider to support capabilities like Dark Mode in applications. UIKit is able to re-render views when a user enters dark mode but this dynamic capabilities is not handled at the layer level.

Currently this takes the recommendation in facebookarchive/AsyncDisplayKit#1537 (comment) to set background color on the view directly or layer depending on if the node is layer backed. This change allows UIKit to track these colors and update the UI when the UITraitCollection.userInterfaceStyle changes.

@rahul-malik rahul-malik changed the title Set background color directly to view or layer depending on if the node Set background color directly to view if not layer backed Jul 17, 2019
@ghost
Copy link

ghost commented Jul 17, 2019

🚫 CI failed with log

Copy link
Member

@nguyenhuy nguyenhuy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me to always use a UIColor whenever the node is view-backed now that UIColor has (light & dark) magic!

@nguyenhuy nguyenhuy requested a review from maicki July 17, 2019 22:32
@ghost
Copy link

ghost commented Jul 17, 2019

🚫 CI failed with log

@ghost
Copy link

ghost commented Jul 17, 2019

🚫 CI failed with log

@ghost
Copy link

ghost commented Jul 17, 2019

🚫 CI failed with log

is layerBacked.

Summary:
UIColor can now be a dynamic color provider to support capabilities like Dark Mode in applications. UIKit is able to re-render views when a user enters dark mode but this dynamic capabilities is not handled at the layer level.

Currently this takes the recommendation in facebookarchive/AsyncDisplayKit#1537 (comment) to set background color on the view directly or layer depending on if the node is layer backed. This change allows UIKit to track these colors and update the UI when the UITraitCollection.userInterfaceStyle changes.
@rahul-malik rahul-malik force-pushed the rmalik/fix-uiview-bridge-backgroundcolor branch from e56ebc0 to ebc8831 Compare July 18, 2019 03:59
@ghost
Copy link

ghost commented Jul 18, 2019

🚫 CI failed with log

@ghost
Copy link

ghost commented Jul 18, 2019

🚫 CI failed with log

@linlinyao1
Copy link

Dynamic capability is lost during conversion between UIColor and CGColor. Thus a UIColor reference is required in this case

@@ -727,7 +734,14 @@ - (void)setContentMode:(UIViewContentMode)contentMode
- (UIColor *)backgroundColor
{
_bridge_prologue_read;
return [UIColor colorWithCGColor:_getFromLayer(backgroundColor)];
if (_loaded(self)) {
if (_view != nil) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use _flags.layerBacked to be consistent with other methods?

Source/Private/ASDisplayNode+UIViewBridge.mm Show resolved Hide resolved
@ghost
Copy link

ghost commented Jul 18, 2019

🚫 CI failed with log

@ghost
Copy link

ghost commented Jul 18, 2019

🚫 CI failed with log

Podfile.lock Outdated
@@ -52,4 +52,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: 445046ac151568c694ff286684322273f0b597d6

COCOAPODS: 1.6.0
COCOAPODS: 1.6.1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not update the cocoapods version with this commit imho

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll undo this

BOOL shouldApply = ASDisplayNodeShouldApplyBridgedWriteToView(self);

if (shouldApply) {
BOOL oldOpaque = _layer.opaque;
BOOL oldOpaque;
oldOpaque = _layer.opaque;
Copy link
Contributor

@maicki maicki Jul 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please elaborate why we are making this change here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The values of opaque differ depending on if it's a view or layer backed.

@@ -1924,16 +1925,11 @@ - (void)checkBackgroundColorOpaqueRelationshipWithViewLoaded:(BOOL)loaded layerB
XCTAssertTrue(node.opaque, @"Node should start opaque");
XCTAssertTrue(node.layer.opaque, @"Node should start opaque");

node.backgroundColor = [UIColor clearColor];
node.backgroundColor = [UIColor blackColor];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate why this test is failing now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was just for testing to see if clearColor was changing the interpretation of isOpaque on CALayer

// Set the background color to the layer as in the UIView bridge we use this value as background color
layer.backgroundColor = backgroundColor;
}
view.backgroundColor = [UIColor colorWithCGColor:backgroundColor];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we set the background color to the view the layer should automatically be updated. Could you double cbeck that this is not the case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maicki - We tested this today and surprisingly, the layer is not updated (at least not immediately)

Copy link
Member

@nguyenhuy nguyenhuy Jul 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirm what Rahul said: the view doesn't propagate the background color immediately which causes many of our tests to fail because they use the layer to render snapshots. For example this test failed because the parent node has a nil background color by the time the test takes a snapshot using ASSnapshotVerifyNode.

An alternative approach would be updating ASSnapshotVerifyNode to use the view if the node is view-backed. But by doing so, we're accepting the risk that there is a time window in which the view and the layer disagree. And this time window is undocumented by UIKit.

Given that now UIColor and UIView has a new dynamic capability, and that we should rely on UIView more to take advantage of this new capability, we should update the framework to set to both the view and the layer and thus eliminate the time window and its associated risks.

} else {
_layer.backgroundColor = newBackgroundCGColor;
_view.backgroundColor = newBackgroundColor;
_layer.backgroundColor = _view.backgroundColor.CGColor;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as below if you update the view I don't think you need to update the layer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nguyenhuy and I were debugging and realized we needed to set both of these. UIView seems to be setting the _layer.backgroundColor to nil when setting the views background color.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We assume these values are bridged but when debugging it seems like the relationship between view and layer is more complex. We're only hitting this now since we are setting backgroundColor directly on the view itself

@nguyenhuy nguyenhuy self-requested a review July 19, 2019 01:57
Source/Private/ASDisplayNode+UIViewBridge.mm Show resolved Hide resolved
Source/Private/ASDisplayNode+UIViewBridge.mm Show resolved Hide resolved
XCTAssertTrue(node.layer.opaque, @"Set background color should not have made this layer not opaque");
} else {
XCTAssertTrue(node.view.opaque, @"Set background color should not have made this view not opaque");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary? At the beginning the view and the layer should agree with each other?

Nit: indentation is off.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gah 2 spaces

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so they don't agree with each other in practice. i'll add a comment here


node.backgroundColor = [UIColor clearColor];
// [node.view setNeedsDisplay];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering the reason behind having this line in the first place. Would that be because doing this causes the view to sync its states (opaque and background color) to the layer? Even if that's true, I think we still shouldn't rely on it because of what I said in the preview comment.

Plus, we should just remove this line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i meant to remove this!

// Set the background color to the layer as in the UIView bridge we use this value as background color
layer.backgroundColor = backgroundColor;
}
view.backgroundColor = [UIColor colorWithCGColor:backgroundColor];
Copy link
Member

@nguyenhuy nguyenhuy Jul 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirm what Rahul said: the view doesn't propagate the background color immediately which causes many of our tests to fail because they use the layer to render snapshots. For example this test failed because the parent node has a nil background color by the time the test takes a snapshot using ASSnapshotVerifyNode.

An alternative approach would be updating ASSnapshotVerifyNode to use the view if the node is view-backed. But by doing so, we're accepting the risk that there is a time window in which the view and the layer disagree. And this time window is undocumented by UIKit.

Given that now UIColor and UIView has a new dynamic capability, and that we should rely on UIView more to take advantage of this new capability, we should update the framework to set to both the view and the layer and thus eliminate the time window and its associated risks.

Copy link
Member

@nguyenhuy nguyenhuy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@nguyenhuy
Copy link
Member

I'm going to merge this PR once CI passes. We want to get this in before our sync this week so we can test it immediately.

@maicki @Adlai-Holler @appleguy Feel free to do post-reviews. @rahul-malik and I will follow up on any of your concerns in new diffs.

@ghost
Copy link

ghost commented Jul 19, 2019

🚫 CI failed with log

@ghost
Copy link

ghost commented Jul 19, 2019

🚫 CI failed with log

@nguyenhuy nguyenhuy merged commit d4d6897 into master Jul 20, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants