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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
#import <React/UIView+ComponentViewProtocol.h>
#import <React/View.h>

#include <memory>

namespace facebook::react {
class ImageManager;
} // namespace facebook::react

NS_ASSUME_NONNULL_BEGIN

/**
Expand Down Expand Up @@ -60,6 +66,12 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;

/**
* Used to load the `url()` entries of `backgroundImage`.
* Set by `RCTComponentViewRegistry` when it creates the view.
*/
- (void)setBackgroundImageManager:(std::shared_ptr<facebook::react::ImageManager>)imageManager;

/**
* Enforcing `call super` semantic for overridden methods from `RCTComponentViewProtocol`.
* The methods update the instance variables.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@
#import <CoreGraphics/CoreGraphics.h>
#import <QuartzCore/QuartzCore.h>
#import <objc/runtime.h>
#import <algorithm>
#import <ranges>
#import <string_view>
#import <unordered_set>

#import <RCTSwiftUIWrapper/RCTSwiftUIContainerViewWrapper.h>
#import <React/RCTAssert.h>
#import <React/RCTBackgroundImageURLLoader.h>
#import <React/RCTBackgroundImageUtils.h>
#import <React/RCTBorderDrawing.h>
#import <React/RCTBoxShadow.h>
Expand Down Expand Up @@ -123,6 +125,8 @@ @implementation RCTViewComponentView {
NSMutableSet<NSString *> *_accessibilityOrderNativeIDs;
RCTSwiftUIContainerViewWrapper *_swiftUIWrapper;
BOOL _focusable;
std::shared_ptr<ImageManager> _imageManager;
RCTBackgroundImageURLLoader *_backgroundImageURLLoader;
}

#ifdef RCT_DYNAMIC_FRAMEWORKS
Expand Down Expand Up @@ -842,6 +846,9 @@ - (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask
{
[super finalizeUpdates:updateMask];
_useCustomContainerView = [self styleWouldClipOverflowInk];
if ((updateMask & RNComponentViewUpdateMaskProps) != 0) {
[self updateBackgroundImageURLRequests];
}
if (!_needsInvalidateLayer) {
return;
}
Expand Down Expand Up @@ -882,6 +889,7 @@ - (void)prepareForRecycle
[_filterLayer removeFromSuperlayer];
_filterLayer = nil;
[self clearExistingBackgroundImageLayers];
_backgroundImageURLLoader = nil;

[self _setLastSentSafeAreaInsets:nil];
_propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN = nil;
Expand Down Expand Up @@ -1419,29 +1427,48 @@ - (void)invalidateLayer
backgroundRepeat = _props->backgroundRepeat[imageIndex % _props->backgroundRepeat.size()];
}

CGSize itemIntrinsicSize = backgroundPositioningArea.size;
BOOL itemHasIntrinsicRatio = NO;
UIImage *urlImage = nil;
if (const auto *urlBackgroundImage = std::get_if<URLBackgroundImage>(&backgroundImage)) {
urlImage = [_backgroundImageURLLoader imageForURI:urlBackgroundImage->uri];
// Bundled assets send their natural size in layout units. A plain url
// has none, so use the size of the decoded image.
if (urlBackgroundImage->intrinsicWidth > 0 && urlBackgroundImage->intrinsicHeight > 0) {
itemIntrinsicSize = CGSizeMake(urlBackgroundImage->intrinsicWidth, urlBackgroundImage->intrinsicHeight);
itemHasIntrinsicRatio = YES;
} else if (urlImage != nil) {
itemIntrinsicSize = urlImage.size;
itemHasIntrinsicRatio = YES;
}
}

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

CALayer *gradientLayer;
CALayer *itemLayer;

if (std::holds_alternative<LinearGradient>(backgroundImage)) {
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)) {
const auto &radialGradient = std::get<RadialGradient>(backgroundImage);
gradientLayer = [RCTRadialGradient gradientLayerWithSize:backgroundImageSize gradient:radialGradient];
itemLayer = [RCTRadialGradient gradientLayerWithSize:backgroundImageSize gradient:radialGradient];
} else if (urlImage != nil) {
itemLayer = [RCTBackgroundImageUtils createURLImageLayerWithImage:urlImage size:backgroundImageSize];
}

if (gradientLayer != nil) {
if (itemLayer != nil) {
CALayer *backgroundImageLayer =
[RCTBackgroundImageUtils createBackgroundImageLayerWithSize:backgroundPositioningArea
paintingArea:backgroundPaintingArea
itemSize:backgroundImageSize
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 @@ -1543,6 +1570,35 @@ - (CAShapeLayer *)createMaskLayer:(CGRect)bounds cornerInsets:(RCTCornerInsets)c
return maskLayer;
}

- (void)setBackgroundImageManager:(std::shared_ptr<ImageManager>)imageManager
{
_imageManager = std::move(imageManager);
}

- (void)updateBackgroundImageURLRequests
{
bool hasURLBackgroundImage = std::ranges::any_of(_props->backgroundImage, [](const auto &backgroundImage) {
return std::holds_alternative<URLBackgroundImage>(backgroundImage);
});
if (!hasURLBackgroundImage || !_imageManager) {
// Releasing it cancels all loads
_backgroundImageURLLoader = nil;
return;
}

if (_backgroundImageURLLoader == nil) {
__weak RCTViewComponentView *weakSelf = self;
_backgroundImageURLLoader = [[RCTBackgroundImageURLLoader alloc] initWithImageManager:_imageManager
onLoad:^{
[weakSelf invalidateLayer];
}];
}

const auto &eventTarget = _eventEmitter ? _eventEmitter->getEventTarget() : nullptr;
[_backgroundImageURLLoader loadBackgroundImages:_props->backgroundImage
surfaceId:eventTarget ? eventTarget->getSurfaceId() : -1];
}

- (void)clearExistingBackgroundImageLayers
{
if (_backgroundImageLayers == nil) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
#import <React/RCTComponentViewProtocol.h>
#import <React/RendererCore.h>

#include <memory>

namespace facebook::react {
class ImageManager;
} // namespace facebook::react

NS_ASSUME_NONNULL_BEGIN

/**
Expand Down Expand Up @@ -50,6 +56,12 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (nullable UIView<RCTComponentViewProtocol> *)findComponentViewWithTag:(facebook::react::Tag)tag;

/**
* Passed to every `RCTViewComponentView` the registry creates, to load
* `url()` background images.
*/
- (void)setImageManager:(std::shared_ptr<facebook::react::ImageManager>)imageManager;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
@implementation RCTComponentViewRegistry {
std::unordered_map<Tag, RCTComponentViewDescriptor> _registry;
std::unordered_map<ComponentHandle, std::vector<RCTComponentViewDescriptor>> _recyclePool;
std::shared_ptr<ImageManager> _imageManager;
}

- (instancetype)init
Expand Down Expand Up @@ -93,7 +94,11 @@ - (RCTComponentViewDescriptor)_dequeueComponentViewWithComponentHandle:(Componen
auto &recycledViews = _recyclePool[componentHandle];

if (recycledViews.empty()) {
return [self.componentViewFactory createComponentViewWithComponentHandle:componentHandle];
auto componentViewDescriptor = [self.componentViewFactory createComponentViewWithComponentHandle:componentHandle];
if (_imageManager && [componentViewDescriptor.view isKindOfClass:[RCTViewComponentView class]]) {
[(RCTViewComponentView *)componentViewDescriptor.view setBackgroundImageManager:_imageManager];
}
return componentViewDescriptor;
}

auto componentViewDescriptor = recycledViews.back();
Expand All @@ -119,6 +124,11 @@ - (void)_enqueueComponentViewWithComponentHandle:(ComponentHandle)componentHandl
recycledViews.push_back(componentViewDescriptor);
}

- (void)setImageManager:(std::shared_ptr<ImageManager>)imageManager
{
_imageManager = std::move(imageManager);
}

- (void)handleApplicationDidReceiveMemoryWarningNotification
{
_recyclePool.clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#import <React/RCTUtils.h>
#import <cxxreact/TraceSection.h>
#import <react/featureflags/ReactNativeFeatureFlags.h>
#import <react/renderer/components/image/ImageComponentDescriptor.h>
#import <react/renderer/components/root/RootShadowNode.h>
#import <react/renderer/core/LayoutableShadowNode.h>
#import <react/renderer/core/RawProps.h>
Expand Down Expand Up @@ -159,6 +160,12 @@ - (instancetype)init
- (void)setContextContainer:(std::shared_ptr<const ContextContainer>)contextContainer
{
_contextContainer = contextContainer;

if (_contextContainer) {
if (auto imageManager = _contextContainer->find<std::shared_ptr<ImageManager>>(ImageManagerKey)) {
[_componentViewRegistry setImageManager:*imageManager];
}
}
}

- (void)attachSurfaceToView:(UIView *)view surfaceId:(SurfaceId)surfaceId
Expand Down
9 changes: 9 additions & 0 deletions packages/react-native/React/Fabric/RCTSurfacePresenter.mm
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@

#import <react/featureflags/ReactNativeFeatureFlags.h>
#import <react/renderer/componentregistry/ComponentDescriptorFactory.h>
#import <react/renderer/components/image/ImageComponentDescriptor.h>
#import <react/renderer/components/text/BaseTextProps.h>
#import <react/renderer/imagemanager/ImageManager.h>
#import <react/renderer/runtimescheduler/RuntimeScheduler.h>
#import <react/renderer/scheduler/SchedulerToolbox.h>
#import <react/utils/ContextContainer.h>
Expand Down Expand Up @@ -96,6 +98,13 @@ - (instancetype)initWithContextContainer:(std::shared_ptr<const ContextContainer
_bridgelessBindingsExecutor = bridgelessBindingsExecutor;
_contextContainer = contextContainer;

// One `ImageManager` for `<Image>` and `url()` background images, because `RCTImageLoader` must not get
// requests from more than one queue. Its constructor requires the image loader.
if (!contextContainer->find<std::shared_ptr<ImageManager>>(ImageManagerKey).has_value() &&
contextContainer->find<std::shared_ptr<void>>("RCTImageLoader").has_value()) {
contextContainer->insert(ImageManagerKey, std::make_shared<ImageManager>(contextContainer));
}

_surfaceRegistry = [RCTSurfaceRegistry new];
_mountingManager = [RCTMountingManager new];
_mountingManager.contextContainer = contextContainer;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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 <React/Graphics.h>
#import <React/RendererCore.h>
#import <UIKit/UIKit.h>

#include <memory>
#include <string>
#include <vector>

namespace facebook::react {
class ImageManager;
} // namespace facebook::react

NS_ASSUME_NONNULL_BEGIN

/*
* Loads the `url()` entries of a view's `backgroundImage`.
* Releasing the loader cancels its requests.
*/
@interface RCTBackgroundImageURLLoader : NSObject

/*
* `onLoad` is called on the main queue each time an image finishes loading.
*/
- (instancetype)initWithImageManager:(std::shared_ptr<facebook::react::ImageManager>)imageManager
onLoad:(void (^)(void))onLoad;

/*
* Requests every `url()` entry in `backgroundImages`. Does nothing if the
* urls did not change since the last call.
*/
- (void)loadBackgroundImages:(const std::vector<facebook::react::BackgroundImage> &)backgroundImages
surfaceId:(facebook::react::SurfaceId)surfaceId;

/*
* Returns nil until the image for `uri` has loaded.
*/
- (nullable UIImage *)imageForURI:(const std::string &)uri;

@end

NS_ASSUME_NONNULL_END
Loading
Loading