Skip to content
Open
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
1 change: 1 addition & 0 deletions packages/react-native/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ let reactCore = RNTarget(
let reactFabric = RNTarget(
name: .reactFabric,
path: "ReactCommon/react/renderer",
searchPaths: ["ReactCommon/react/renderer/imagemanager/platform/ios"],
excludedPaths: [
"animations/tests",
"attributedstring/tests",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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 <UIKit/UIKit.h>
#import <React/RCTImageResponseDelegate.h>
#import <react/renderer/components/view/ViewShadowNode.h>

NS_ASSUME_NONNULL_BEGIN

@protocol RCTBackgroundImageURLLoaderDelegate <RCTImageResponseDelegate>

- (void)backgroundImagesDidLoad;

@end

@interface RCTBackgroundImageURLLoader : NSObject

@property (nonatomic, weak) id<RCTBackgroundImageURLLoaderDelegate> delegate;

- (void)updateStateWithNewState:(facebook::react::ViewShadowNode::ConcreteState::Shared)state oldState:(facebook::react::ViewShadowNode::ConcreteState::Shared)oldState;
- (nullable UIImage *)loadedImageForUri:(NSString *)uri;
- (void)reset;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* 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 "RCTBackgroundImageURLLoader.h"

#import <React/RCTLog.h>
#import <React/RCTImageResponseObserverProxy.h>
#import <react/renderer/components/view/ViewState.h>

#include <map>
#include <string>

using namespace facebook::react;

@implementation RCTBackgroundImageURLLoader {
ViewShadowNode::ConcreteState::Shared _state;
std::map<std::string, RCTImageResponseObserverProxy> _uriToObserver;
NSMutableDictionary<NSString *, UIImage *> *_loadedImages;
NSMutableSet<NSString *> *_completedUris;
}

- (instancetype)init
{
if (self = [super init]) {
_loadedImages = [NSMutableDictionary new];
_completedUris = [NSMutableSet new];
}
return self;
}

- (void)updateStateWithNewState:(ViewShadowNode::ConcreteState::Shared)state
oldState:(ViewShadowNode::ConcreteState::Shared)oldState
{
const auto* oldRequests = oldState ? &oldState->getData().getBackgroundImageRequests() : nullptr;
const auto* newRequests = state ? &state->getData().getBackgroundImageRequests() : nullptr;

if (oldRequests && newRequests && *oldRequests == *newRequests) {
return;
}

if (oldRequests) {
for (const auto& request : *oldRequests) {
if (request.imageRequest) {
auto it = _uriToObserver.find(request.imageSource.uri);
if (it != _uriToObserver.end()) {
auto& observerCoordinator = request.imageRequest->getObserverCoordinator();
observerCoordinator.removeObserver(it->second);
}
}
}
}

_state = state;
_uriToObserver.clear();
[_loadedImages removeAllObjects];
[_completedUris removeAllObjects];

if (newRequests) {
for (const auto &request : *newRequests) {
if (request.imageRequest) {
const std::string &uri = request.imageSource.uri;
auto [it, inserted] = _uriToObserver.emplace(uri, self);
if (inserted) {
auto& observerCoordinator = request.imageRequest->getObserverCoordinator();
observerCoordinator.addObserver(it->second);
}
}
}
}
}

- (UIImage *)loadedImageForUri:(NSString *)uri
{
return _loadedImages[uri];
}

- (void)reset
{
if (_state) {
const auto &requests = _state->getData().getBackgroundImageRequests();
for (const auto &request : requests) {
if (request.imageRequest) {
auto it = _uriToObserver.find(request.imageSource.uri);
if (it != _uriToObserver.end()) {
auto& observerCoordinator = request.imageRequest->getObserverCoordinator();
observerCoordinator.removeObserver(it->second);
}
}
}
}

_state = nullptr;
_uriToObserver.clear();
[_loadedImages removeAllObjects];
[_completedUris removeAllObjects];
}

#pragma mark - RCTImageResponseDelegate

- (void)didReceiveImage:(UIImage *)image metadata:(id)metadata fromObserver:(const void *)observer
{
for (const auto& [uri, observerProxy] : _uriToObserver) {
if (&observerProxy == observer) {
NSString *nsUri = [NSString stringWithUTF8String:uri.c_str()];
_loadedImages[nsUri] = image;
[_completedUris addObject:nsUri];
break;
}
}

[self notifyDelegateIfAllImagesLoaded];
}

- (void)didReceiveProgress:(float)progress
loaded:(int64_t)loaded
total:(int64_t)total
fromObserver:(const void *)observer
{
// Progress tracking not needed for background images
}

- (void)didReceiveFailure:(NSError *)error fromObserver:(const void *)observer
{
for (const auto& [uri, observerProxy] : _uriToObserver) {
if (&observerProxy == observer) {
NSString *nsUri = [NSString stringWithUTF8String:uri.c_str()];
RCTLogWarn(@"Failed to load background image: %@ - %@", nsUri, error);
[_completedUris addObject:nsUri];
break;
}
}

[self notifyDelegateIfAllImagesLoaded];
}

- (void)notifyDelegateIfAllImagesLoaded
{
if (_completedUris.count == _uriToObserver.size()) {
[_delegate backgroundImagesDidLoad];
}
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
#import <react/renderer/core/LayoutMetrics.h>
#import <react/renderer/core/Props.h>

#import "RCTBackgroundImageURLLoader.h"

NS_ASSUME_NONNULL_BEGIN

/**
* UIView class for <View> component.
*/
@interface RCTViewComponentView : UIView <RCTComponentViewProtocol, RCTTouchableComponentViewProtocol> {
@interface RCTViewComponentView : UIView <RCTComponentViewProtocol, RCTTouchableComponentViewProtocol, RCTBackgroundImageURLLoaderDelegate> {
@protected
facebook::react::LayoutMetrics _layoutMetrics;
facebook::react::SharedViewProps _props;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ @implementation RCTViewComponentView {
BOOL _useCustomContainerView;
NSMutableSet<NSString *> *_accessibilityOrderNativeIDs;
RCTSwiftUIContainerViewWrapper *_swiftUIWrapper;
RCTBackgroundImageURLLoader *_backgroundImageLoader;
}

#ifdef RCT_DYNAMIC_FRAMEWORKS
Expand All @@ -67,6 +68,8 @@ - (instancetype)initWithFrame:(CGRect)frame
if (self = [super initWithFrame:frame]) {
_props = ViewShadowNode::defaultSharedProps();
_reactSubviews = [NSMutableArray new];
_backgroundImageLoader = [RCTBackgroundImageURLLoader new];
_backgroundImageLoader.delegate = self;
self.multipleTouchEnabled = YES;
_useCustomContainerView = NO;
_removeClippedSubviews = NO;
Expand Down Expand Up @@ -553,6 +556,13 @@ - (void)updateEventEmitter:(const EventEmitter::Shared &)eventEmitter
_eventEmitter = std::static_pointer_cast<const ViewEventEmitter>(eventEmitter);
}

- (void)updateState:(const State::Shared &)state oldState:(const State::Shared &)oldState
{
auto newViewState = std::static_pointer_cast<const ViewShadowNode::ConcreteState>(state);
auto oldViewState = oldState ? std::static_pointer_cast<const ViewShadowNode::ConcreteState>(oldState) : nullptr;
[_backgroundImageLoader updateStateWithNewState:newViewState oldState:oldViewState];
}

- (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics
{
Expand Down Expand Up @@ -643,6 +653,9 @@ - (void)prepareForRecycle
_filterLayer = nil;
[self clearExistingBackgroundImageLayers];

// Clean up background image observers
[_backgroundImageLoader reset];

_propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN = nil;
_eventEmitter.reset();
_isJSResponder = NO;
Expand Down Expand Up @@ -1166,29 +1179,49 @@ - (void)invalidateLayer
backgroundRepeat = _props->backgroundRepeat[imageIndex % _props->backgroundRepeat.size()];
}

CGSize backgroundImageSize = [RCTBackgroundImageUtils calculateBackgroundImageSize:backgroundPositioningArea
itemIntrinsicSize:backgroundPositioningArea.size
backgroundSize:backgroundSize
backgroundRepeat:backgroundRepeat];

CALayer *gradientLayer;
CALayer *itemLayer = nil;

if (std::holds_alternative<LinearGradient>(backgroundImage)) {
CGSize backgroundImageSize = [RCTBackgroundImageUtils calculateBackgroundImageSize:backgroundPositioningArea
itemIntrinsicSize:backgroundPositioningArea.size
backgroundSize:backgroundSize
backgroundRepeat:backgroundRepeat];
const auto &linearGradient = std::get<LinearGradient>(backgroundImage);
gradientLayer = [RCTLinearGradient gradientLayerWithSize:backgroundImageSize gradient:linearGradient];
itemLayer = [RCTLinearGradient gradientLayerWithSize:backgroundImageSize gradient:linearGradient];
} else if (std::holds_alternative<RadialGradient>(backgroundImage)) {
CGSize backgroundImageSize = [RCTBackgroundImageUtils calculateBackgroundImageSize:backgroundPositioningArea
itemIntrinsicSize:backgroundPositioningArea.size
backgroundSize:backgroundSize
backgroundRepeat:backgroundRepeat];
const auto &radialGradient = std::get<RadialGradient>(backgroundImage);
gradientLayer = [RCTRadialGradient gradientLayerWithSize:backgroundImageSize gradient:radialGradient];
itemLayer = [RCTRadialGradient gradientLayerWithSize:backgroundImageSize gradient:radialGradient];
} else if (std::holds_alternative<URLBackgroundImage>(backgroundImage)) {
const auto &urlBgImage = std::get<URLBackgroundImage>(backgroundImage);
NSString *uri = [NSString stringWithUTF8String:urlBgImage.uri.c_str()];
UIImage *loadedImage = [_backgroundImageLoader loadedImageForUri:uri];
if (loadedImage != nil) {
CGSize intrinsicSize = loadedImage.size;
CGSize backgroundImageSize = [RCTBackgroundImageUtils calculateBackgroundImageSize:backgroundPositioningArea
itemIntrinsicSize:intrinsicSize
backgroundSize:backgroundSize
backgroundRepeat:backgroundRepeat];
CALayer *imageLayer = [CALayer layer];
imageLayer.frame = CGRectMake(0, 0, backgroundImageSize.width, backgroundImageSize.height);
imageLayer.contents = (__bridge id)loadedImage.CGImage;
imageLayer.contentsGravity = kCAGravityResizeAspectFill;
itemLayer = imageLayer;
}
}

if (gradientLayer != nil) {
if (itemLayer != nil) {
CGSize itemSize = itemLayer.frame.size;
CALayer *backgroundImageLayer =
[RCTBackgroundImageUtils createBackgroundImageLayerWithSize:backgroundPositioningArea
paintingArea:backgroundPaintingArea
itemSize:backgroundImageSize
itemSize:itemSize
backgroundPosition:backgroundPosition
backgroundRepeat:backgroundRepeat
itemLayer:gradientLayer];
itemLayer:itemLayer];
[self shapeLayerToMatchView:backgroundImageLayer borderMetrics:borderMetricsBI];
backgroundImageLayer.masksToBounds = YES;
backgroundImageLayer.zPosition = BACKGROUND_COLOR_ZPOSITION;
Expand Down Expand Up @@ -1302,6 +1335,13 @@ - (void)clearExistingBackgroundImageLayers
[_backgroundImageLayers removeAllObjects];
}

#pragma mark - RCTBackgroundImageURLLoaderDelegate

- (void)backgroundImagesDidLoad
{
[self invalidateLayer];
}

#pragma mark - Accessibility

- (NSObject *)accessibilityElement
Expand Down
3 changes: 3 additions & 0 deletions packages/react-native/ReactCommon/React-Fabric.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ Pod::Spec.new do |s|
sss.dependency "Yoga"
sss.source_files = podspec_sources(["react/renderer/components/view/*.{m,mm,cpp,h}", "react/renderer/components/view/platform/cxx/**/*.{m,mm,cpp,h}"], ["react/renderer/components/view/*.{h}", "react/renderer/components/view/platform/cxx/**/*.{h}"])
sss.header_dir = "react/renderer/components/view"
sss.pod_target_xcconfig = {
"HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/react/renderer/imagemanager/platform/ios\""
}
end

ss.subspec "scrollview" do |sss|
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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.
*/

#include "ViewComponentDescriptor.h"
#include <react/renderer/imagemanager/ImageManager.h>

namespace facebook::react {

extern const char ImageManagerKey[];

ViewComponentDescriptor::ViewComponentDescriptor(
const ComponentDescriptorParameters& parameters)
: ConcreteComponentDescriptor(parameters),
imageManager_(
getManagerByName<ImageManager>(contextContainer_, ImageManagerKey)) {}

void ViewComponentDescriptor::adopt(ShadowNode& shadowNode) const {
ConcreteComponentDescriptor::adopt(shadowNode);

auto& viewShadowNode = static_cast<ViewShadowNode&>(shadowNode);
viewShadowNode.setImageManager(imageManager_);
}

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@

namespace facebook::react {

class ViewComponentDescriptor : public ConcreteComponentDescriptor<ViewShadowNode> {
class ImageManager;

class ViewComponentDescriptor
: public ConcreteComponentDescriptor<ViewShadowNode> {
public:
ViewComponentDescriptor(const ComponentDescriptorParameters &parameters)
: ConcreteComponentDescriptor<ViewShadowNode>(parameters)
{
}
ViewComponentDescriptor(const ComponentDescriptorParameters &parameters);

void adopt(ShadowNode &shadowNode) const override;

private:
const std::shared_ptr<ImageManager> imageManager_;
};

} // namespace facebook::react
Loading
Loading