/* * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "RCTImageComponentView.h" #import #import #import #import #import #import #import #import #import #import #import "RCTConversions.h" #import "RCTFabricComponentsPlugins.h" using namespace facebook::react; @interface RCTImageComponentView () @end @implementation RCTImageComponentView { UIImageView *_imageView; ImageShadowNode::ConcreteState::Shared _state; ImageResponseObserverCoordinator const *_coordinator; RCTImageResponseObserverProxy _imageResponseObserverProxy; } - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { static auto const defaultProps = std::make_shared(); _props = defaultProps; _imageView = [[UIImageView alloc] initWithFrame:self.bounds]; _imageView.clipsToBounds = YES; _imageView.contentMode = (UIViewContentMode)RCTResizeModeFromImageResizeMode(defaultProps->resizeMode); _imageView.layer.minificationFilter = kCAFilterTrilinear; _imageView.layer.magnificationFilter = kCAFilterTrilinear; _imageResponseObserverProxy = RCTImageResponseObserverProxy(self); self.contentView = _imageView; } return self; } #pragma mark - RCTComponentViewProtocol + (ComponentDescriptorProvider)componentDescriptorProvider { return concreteComponentDescriptorProvider(); } - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps { auto const &oldImageProps = *std::static_pointer_cast(_props); auto const &newImageProps = *std::static_pointer_cast(props); // `resizeMode` if (oldImageProps.resizeMode != newImageProps.resizeMode) { if (newImageProps.resizeMode == ImageResizeMode::Repeat) { // Repeat resize mode is handled by the UIImage. Use scale to fill // so the repeated image fills the UIImageView. _imageView.contentMode = UIViewContentModeScaleToFill; } else { _imageView.contentMode = (UIViewContentMode)RCTResizeModeFromImageResizeMode(newImageProps.resizeMode); } } // `tintColor` if (oldImageProps.tintColor != newImageProps.tintColor) { _imageView.tintColor = [UIColor colorWithCGColor:newImageProps.tintColor.get()]; } [super updateProps:props oldProps:oldProps]; } - (void)updateState:(State::Shared const &)state oldState:(State::Shared const &)oldState { _state = std::static_pointer_cast(state); auto _oldState = std::static_pointer_cast(oldState); auto data = _state->getData(); // This call (setting `coordinator`) must be unconditional (at the same block as setting `State`) // because the setter stores a raw pointer to object that `State` owns. self.coordinator = &data.getImageRequest().getObserverCoordinator(); bool havePreviousData = _oldState && _oldState->getData().getImageSource() != ImageSource{}; if (!havePreviousData || data.getImageSource() != _oldState->getData().getImageSource()) { // Loading actually starts a little before this, but this is the first time we know // the image is loading and can fire an event from this component std::static_pointer_cast(_eventEmitter)->onLoadStart(); // TODO (T58941612): Tracking for visibility should be done directly on this class. // For now, we consolidate instrumentation logic in the image loader, so that pre-Fabric gets the same treatment. auto instrumentation = std::static_pointer_cast( data.getImageRequest().getSharedImageInstrumentation()); if (instrumentation) { instrumentation->trackNativeImageView(self); } } } - (void)setCoordinator:(ImageResponseObserverCoordinator const *)coordinator { if (_coordinator) { _coordinator->removeObserver(_imageResponseObserverProxy); } _coordinator = coordinator; if (_coordinator != nullptr) { _coordinator->addObserver(_imageResponseObserverProxy); } } - (void)prepareForRecycle { [super prepareForRecycle]; self.coordinator = nullptr; _imageView.image = nil; _state.reset(); } - (void)dealloc { self.coordinator = nullptr; } #pragma mark - RCTImageResponseDelegate - (void)didReceiveImage:(UIImage *)image fromObserver:(void const *)observer { if (!_eventEmitter || !_state) { // Notifications are delivered asynchronously and might arrive after the view is already recycled. // In the future, we should incorporate an `EventEmitter` into a separate object owned by `ImageRequest` or `State`. // See for more info: T46311063. return; } std::static_pointer_cast(_eventEmitter)->onLoad(); std::static_pointer_cast(_eventEmitter)->onLoadEnd(); const auto &imageProps = *std::static_pointer_cast(_props); if (imageProps.tintColor) { image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; } if (imageProps.resizeMode == ImageResizeMode::Repeat) { image = [image resizableImageWithCapInsets:RCTUIEdgeInsetsFromEdgeInsets(imageProps.capInsets) resizingMode:UIImageResizingModeTile]; } else if (imageProps.capInsets != EdgeInsets()) { // Applying capInsets of 0 will switch the "resizingMode" of the image to "tile" which is undesired. image = [image resizableImageWithCapInsets:RCTUIEdgeInsetsFromEdgeInsets(imageProps.capInsets) resizingMode:UIImageResizingModeStretch]; } void (^didSetImage)() = ^() { if (!self->_state) { return; } auto data = self->_state->getData(); auto instrumentation = std::static_pointer_cast( data.getImageRequest().getSharedImageInstrumentation()); if (instrumentation) { instrumentation->didSetImage(); } }; if (imageProps.blurRadius > __FLT_EPSILON__) { // Blur on a background thread to avoid blocking interaction. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ UIImage *blurredImage = RCTBlurredImageWithRadius(image, imageProps.blurRadius); RCTExecuteOnMainQueue(^{ self->_imageView.image = blurredImage; didSetImage(); }); }); } else { self->_imageView.image = image; didSetImage(); } } - (void)didReceiveProgress:(float)progress fromObserver:(void const *)observer { if (!_eventEmitter) { return; } std::static_pointer_cast(_eventEmitter)->onProgress(progress); } - (void)didReceiveFailureFromObserver:(void const *)observer { _imageView.image = nil; if (!_eventEmitter) { return; } std::static_pointer_cast(_eventEmitter)->onError(); std::static_pointer_cast(_eventEmitter)->onLoadEnd(); } @end Class RCTImageCls(void) { return RCTImageComponentView.class; }