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

[iOS] Fabric: Fixes crash of dynamic color when light/dark mode changed #48496

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ struct Color {
Color(const ColorComponents& components);
Color(std::shared_ptr<void> uiColor);
int32_t getColor() const;
int32_t getUIColorHash() const;

std::shared_ptr<void> getUIColor() const {
return uiColor_;
}
Expand Down Expand Up @@ -104,7 +106,7 @@ template <>
struct std::hash<facebook::react::Color> {
size_t operator()(const facebook::react::Color& color) const {
auto seed = size_t{0};
facebook::react::hash_combine(seed, color.getColor());
facebook::react::hash_combine(seed, color.getUIColorHash());
return seed;
Copy link
Contributor

Choose a reason for hiding this comment

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

@javache No value in calling hash_combine again with seed 0.

Suggested change
auto seed = size_t{0};
facebook::react::hash_combine(seed, color.getColor());
facebook::react::hash_combine(seed, color.getUIColorHash());
return seed;
return color.getUIColorHash();

}
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

#import "HostPlatformColor.h"
#import "UIColor+Graphics.h"

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
Expand All @@ -19,13 +20,32 @@
namespace facebook::react {

namespace {

bool UIColorIsP3ColorSpace(const std::shared_ptr<void> &uiColor)
{
UIColor *color = unwrapManagedObject(uiColor);
CGColorSpaceRef colorSpace = CGColorGetColorSpace(color.CGColor);

if (CGColorSpaceGetModel(colorSpace) == kCGColorSpaceModelRGB) {
CFStringRef name = CGColorSpaceGetName(colorSpace);
if (name != NULL && CFEqual(name, kCGColorSpaceDisplayP3)) {
return true;
}
}
return false;
}

UIColor *_Nullable UIColorFromInt32(int32_t intColor)
{
CGFloat a = CGFloat((intColor >> 24) & 0xFF) / 255.0;
CGFloat r = CGFloat((intColor >> 16) & 0xFF) / 255.0;
CGFloat g = CGFloat((intColor >> 8) & 0xFF) / 255.0;
CGFloat b = CGFloat(intColor & 0xFF) / 255.0;
return [UIColor colorWithRed:r green:g blue:b alpha:a];

UIColor *color = [UIColor colorWithRed:r green:g blue:b alpha:a];
auto colorHash = facebook::react::hash_combine(intColor, 0);
color.reactHash = colorHash;
Copy link
Contributor

Choose a reason for hiding this comment

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

@javache

Suggested change
auto colorHash = facebook::react::hash_combine(intColor, 0);
color.reactHash = colorHash;
color.reactHash = [colorHash](facebook::react::hash_combine(intColor, 0));

return color;
}

UIColor *_Nullable UIColorFromDynamicColor(const facebook::react::DynamicColor &dynamicColor)
Expand Down Expand Up @@ -56,6 +76,8 @@
}
}
}];
auto colorHash = facebook::react::hash_combine(dark, light, highContrastDark, highContrastLight, 0);
color.reactHash = colorHash;
Copy link
Contributor

Choose a reason for hiding this comment

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

@javache

Suggested change
auto colorHash = facebook::react::hash_combine(dark, light, highContrastDark, highContrastLight, 0);
color.reactHash = colorHash;
color.reactHash = [colorHash;](acebook::react::hash_combine(dark, light, highContrastDark, highContrastLight, 0);)

return color;
} else {
return nil;
Expand All @@ -64,37 +86,96 @@
return nil;
}

int32_t ColorFromUIColor(UIColor *color)
int32_t ColorFromColorComponents(const facebook::react::ColorComponents &components)
{
float ratio = 255;
auto color = ((int32_t)round((float)components.alpha * ratio) & 0xff) << 24 |
((int)round((float)components.red * ratio) & 0xff) << 16 |
((int)round((float)components.green * ratio) & 0xff) << 8 | ((int)round((float)components.blue * ratio) & 0xff);
return color;
}

int32_t ColorFromUIColor(UIColor *color)
{
CGFloat rgba[4];
[color getRed:&rgba[0] green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]];
return ((int32_t)round((float)rgba[3] * ratio) & 0xff) << 24 | ((int)round((float)rgba[0] * ratio) & 0xff) << 16 |
((int)round((float)rgba[1] * ratio) & 0xff) << 8 | ((int)round((float)rgba[2] * ratio) & 0xff);
return ColorFromColorComponents({(float)rgba[0], (float)rgba[1], (float)rgba[2], (float)rgba[3]});
}

int32_t ColorFromUIColor(const std::shared_ptr<void> &uiColor)
int32_t ColorFromUIColorForSpecificTraitCollection(
const std::shared_ptr<void> &uiColor,
UITraitCollection *traitCollection)
{
UIColor *color = (UIColor *)unwrapManagedObject(uiColor);
if (color) {
UITraitCollection *currentTraitCollection = [UITraitCollection currentTraitCollection];
color = [color resolvedColorWithTraitCollection:currentTraitCollection];
color = [color resolvedColorWithTraitCollection:traitCollection];
return ColorFromUIColor(color);
}

return 0;
}

int32_t ColorFromUIColor(const std::shared_ptr<void> &uiColor)
{
return ColorFromUIColorForSpecificTraitCollection(uiColor, [UITraitCollection currentTraitCollection]);
}

UIColor *_Nullable UIColorFromComponentsColor(const facebook::react::ColorComponents &components)
{
UIColor *uiColor = nil;
if (components.colorSpace == ColorSpace::DisplayP3) {
return [UIColor colorWithDisplayP3Red:components.red
green:components.green
blue:components.blue
alpha:components.alpha];
uiColor = [UIColor colorWithDisplayP3Red:components.red
green:components.green
blue:components.blue
alpha:components.alpha];
} else {
uiColor = [UIColor colorWithRed:components.red green:components.green blue:components.blue alpha:components.alpha];
}

auto color = ColorFromColorComponents(components);
auto colorHash = facebook::react::hash_combine(color, components.colorSpace == ColorSpace::DisplayP3);
uiColor.reactHash = colorHash;
Copy link
Contributor

Choose a reason for hiding this comment

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

@javache

Suggested change
auto colorHash = facebook::react::hash_combine(color, components.colorSpace == ColorSpace::DisplayP3);
uiColor.reactHash = colorHash;
uiColor.reactHash = facebook::react::hash_combine(color, components.colorSpace == ColorSpace::DisplayP3);


return uiColor;
}

int32_t hashFromUIColor(const std::shared_ptr<void> &uiColor)
{
if (uiColor == nullptr) {
return 0;
}
return [UIColor colorWithRed:components.red green:components.green blue:components.blue alpha:components.alpha];

static UITraitCollection *darkModeTraitCollection =
[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
auto darkColor = ColorFromUIColorForSpecificTraitCollection(uiColor, darkModeTraitCollection);

static UITraitCollection *lightModeTraitCollection =
[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight];
auto lightColor = ColorFromUIColorForSpecificTraitCollection(uiColor, lightModeTraitCollection);

static UITraitCollection *darkModeAccessibilityContrastTraitCollection =
[UITraitCollection traitCollectionWithTraitsFromCollections:@[
darkModeTraitCollection,
[UITraitCollection traitCollectionWithAccessibilityContrast:UIAccessibilityContrastHigh]
]];
auto darkAccessibilityContrastColor =
ColorFromUIColorForSpecificTraitCollection(uiColor, darkModeAccessibilityContrastTraitCollection);

static UITraitCollection *lightModeAccessibilityContrastTraitCollection =
[UITraitCollection traitCollectionWithTraitsFromCollections:@[
lightModeTraitCollection,
[UITraitCollection traitCollectionWithAccessibilityContrast:UIAccessibilityContrastHigh]
]];
auto lightAccessibilityContrastColor =
ColorFromUIColorForSpecificTraitCollection(uiColor, lightModeAccessibilityContrastTraitCollection);
return facebook::react::hash_combine(
darkColor,
lightColor,
darkAccessibilityContrastColor,
lightAccessibilityContrastColor,
UIColorIsP3ColorSpace(uiColor));
}

} // anonymous namespace

Color::Color(int32_t color)
Expand All @@ -114,14 +195,20 @@ int32_t ColorFromUIColor(const std::shared_ptr<void> &uiColor)

Color::Color(std::shared_ptr<void> uiColor)
Copy link
Contributor

Choose a reason for hiding this comment

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

@javache

When would this still get called? Can we make this constructor private so we can guarantee all UIColor objects are created with the hash in place?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Seems UndefinedColor and semantic color used it, I mark it private and add a static method createSemanticColor to create semantic color.

{
UIColor *color = ((UIColor *)unwrapManagedObject(uiColor));
if (color && color.reactHash == 0) {
auto colorHash = hashFromUIColor(uiColor);
color.reactHash = colorHash;
}
uiColor_ = std::move(uiColor);
}

bool Color::operator==(const Color &other) const
{
return (!uiColor_ && !other.uiColor_) ||
(uiColor_ && other.uiColor_ &&
[unwrapManagedObject(getUIColor()) isEqual:unwrapManagedObject(other.getUIColor())]);
((UIColor *)unwrapManagedObject(getUIColor())).reactHash ==
((UIColor *)unwrapManagedObject(other.getUIColor())).reactHash);
}

bool Color::operator!=(const Color &other) const
Expand All @@ -142,6 +229,11 @@ int32_t ColorFromUIColor(const std::shared_ptr<void> &uiColor)
return static_cast<float>(rgba[channelId]);
}

int32_t Color::getUIColorHash() const
{
return [(UIColor *)unwrapManagedObject(uiColor_) reactHash];
}

} // namespace facebook::react

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#pragma once

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIColor (Graphics)
@property (nonatomic, assign) int32_t reactHash;
@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#import <objc/runtime.h>
#import "UIColor+Graphics.h"

@implementation UIColor (Graphics)

- (int32_t)reactHash
{
return [objc_getAssociatedObject(self, _cmd) intValue];
}

- (void)setReactHash:(int32_t)reactHash
{
objc_setAssociatedObject(self, @selector(reactHash), @(reactHash), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end
Loading