Reland: [iOS] [Fabric] Fixes crash of dynamic color when light/dark mode changed (#49265)

Summary:
Reland https://github.com/facebook/react-native/issues/48496 .

## Changelog:

[IOS] [FIXED] - Fabric: Fixes crash of dynamic color when light/dark mode changed

Pull Request resolved: https://github.com/facebook/react-native/pull/49265

Test Plan: RNTester -> PlatformColor example -> changed the dark/light mode in the system settings -> go back to App and pop and push the PlatformColor example, it would crash:

Reviewed By: javache

Differential Revision: D69309825

Pulled By: cipolleschi

fbshipit-source-id: 7a533a73ef343b071000388b653b2d1d0c54ae88
This commit is contained in:
zhongwuzw
2025-02-10 06:44:43 -08:00
committed by Facebook GitHub Bot
parent b1735bc593
commit f2c78af56a
5 changed files with 132 additions and 23 deletions
@@ -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_;
}
@@ -48,7 +52,9 @@ struct Color {
}
private:
Color(std::shared_ptr<void> uiColor);
std::shared_ptr<void> uiColor_;
int32_t uiColorHashValue_;
};
namespace HostPlatformColor {
@@ -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
@@ -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();
}
};
@@ -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>
@@ -19,13 +21,30 @@ NS_ASSUME_NONNULL_BEGIN
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)
@@ -64,64 +83,132 @@ UIColor *_Nullable UIColorFromDynamicColor(const facebook::react::DynamicColor &
return nil;
}
int32_t ColorFromUIColor(UIColor *color)
int32_t ColorFromColorComponents(const facebook::react::ColorComponents &components)
{
float ratio = 255;
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);
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(const std::shared_ptr<void> &uiColor)
int32_t ColorFromUIColor(UIColor *color)
{
CGFloat rgba[4];
[color getRed:&rgba[0] green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]];
return ColorFromColorComponents({(float)rgba[0], (float)rgba[1], (float)rgba[2], (float)rgba[3]});
}
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 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;
}
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)
{
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
@@ -142,6 +229,17 @@ float Color::getChannel(int channelId) const
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
@@ -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>>()) {
@@ -8,9 +8,15 @@
#pragma once
#import <UIKit/UIKit.h>
#import <react/renderer/graphics/HostPlatformColor.h>
#import <vector>
namespace facebook {
namespace react {
struct ColorComponents;
struct Color;
} // namespace react
} // namespace facebook
facebook::react::ColorComponents RCTPlatformColorComponentsFromSemanticItems(
std::vector<std::string>& semanticItems);
UIColor* RCTPlatformColorFromSemanticItems(
@@ -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>