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 all 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 @@ -24,8 +24,12 @@ struct Color {
Color(int32_t color);
Color(const DynamicColor& dynamicColor);
Color(const ColorComponents& components);
Color(std::shared_ptr<void> uiColor);
Color() : uiColor_(nullptr) {};
int32_t getColor() const;
int32_t getUIColorHash() const;

static Color createSemanticColor(std::vector<std::string>& semanticItems);

std::shared_ptr<void> getUIColor() const {
return uiColor_;
}
Expand All @@ -48,7 +52,9 @@ struct Color {
}

private:
Color(std::shared_ptr<void> uiColor);
std::shared_ptr<void> uiColor_;
int32_t uiColorHashValue_;
};

namespace HostPlatformColor {
Expand All @@ -59,7 +65,7 @@ namespace HostPlatformColor {
#define NO_DESTROY
#endif

NO_DESTROY static const facebook::react::Color UndefinedColor = Color(nullptr);
NO_DESTROY static const facebook::react::Color UndefinedColor = Color();
} // namespace HostPlatformColor

inline Color
Expand Down Expand Up @@ -103,8 +109,6 @@ inline float blueFromHostPlatformColor(Color color) {
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());
return seed;
return color.getUIColorHash();
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#import <react/renderer/graphics/RCTPlatformColorUtils.h>
#import <react/utils/ManagedObjectWrapper.h>
#import <string>

Expand All @@ -19,13 +21,30 @@
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];
return color;
}

UIColor *_Nullable UIColorFromDynamicColor(const facebook::react::DynamicColor &dynamicColor)
Expand Down Expand Up @@ -64,64 +83,132 @@
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];
}

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)
{
uiColor_ = wrapManagedObject(UIColorFromInt32(color));
uiColorHashValue_ = facebook::react::hash_combine(color, 0);
}

Color::Color(const DynamicColor &dynamicColor)
{
uiColor_ = wrapManagedObject(UIColorFromDynamicColor(dynamicColor));
uiColorHashValue_ = facebook::react::hash_combine(
dynamicColor.darkColor,
dynamicColor.lightColor,
dynamicColor.highContrastDarkColor,
dynamicColor.highContrastLightColor,
0);
}

Color::Color(const ColorComponents &components)
{
uiColor_ = wrapManagedObject(UIColorFromComponentsColor(components));
uiColorHashValue_ = facebook::react::hash_combine(
ColorFromColorComponents(components), components.colorSpace == ColorSpace::DisplayP3);
}

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) {
auto colorHash = hashFromUIColor(uiColor);
uiColorHashValue_ = 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_ && other.uiColor_ && (uiColorHashValue_ == other.uiColorHashValue_));
}

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

int32_t Color::getUIColorHash() const
{
return uiColorHashValue_;
}

Color Color::createSemanticColor(std::vector<std::string> &semanticItems)
{
auto semanticColor = RCTPlatformColorFromSemanticItems(semanticItems);
return Color(wrapManagedObject(semanticColor));
}

} // namespace facebook::react

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ SharedColor parsePlatformColor(const ContextContainer &contextContainer, int32_t
auto items = (std::unordered_map<std::string, RawValue>)value;
if (items.find("semantic") != items.end() && items.at("semantic").hasType<std::vector<std::string>>()) {
auto semanticItems = (std::vector<std::string>)items.at("semantic");
return {wrapManagedObject(RCTPlatformColorFromSemanticItems(semanticItems))};
return SharedColor(Color::createSemanticColor(semanticItems));
} else if (
items.find("dynamic") != items.end() &&
items.at("dynamic").hasType<std::unordered_map<std::string, RawValue>>()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@
#pragma once

#import <UIKit/UIKit.h>
#import <react/renderer/graphics/HostPlatformColor.h>
#import <vector>

namespace facebook {
namespace react {
class ColorComponents;
class Color;
} // namespace react
} // namespace facebook

facebook::react::ColorComponents RCTPlatformColorComponentsFromSemanticItems(
std::vector<std::string>& semanticItems);
UIColor* RCTPlatformColorFromSemanticItems(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <react/renderer/graphics/HostPlatformColor.h>
#import <react/utils/ManagedObjectWrapper.h>

#include <string>
Expand Down
Loading