Skip to content
Draft
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
2 changes: 1 addition & 1 deletion RNScreens.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Pod::Spec.new do |s|
s.platforms = { :ios => min_supported_ios_version, :tvos => min_supported_tvos_version, :visionos => min_supported_visionos_version }
s.source = { :git => "https://github.com/software-mansion/react-native-screens.git", :tag => "#{s.version}" }
s.source_files = source_files
s.project_header_files = "ios/bridging/Swift-Bridging.h"
s.project_header_files = "ios/bridging/Swift-Bridging.h", "ios/**/*Internal.h"
s.requires_arc = true

if !gamma_project_enabled
Expand Down
58 changes: 58 additions & 0 deletions ios/helpers/image/RNSImageLoadingHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,66 @@
#import <React/RCTImageLoader.h>
#import <React/RCTImageSource.h>

typedef NS_ENUM(NSInteger, RNSImageType) {
RNSImageTypeImage,
RNSImageTypeTemplate,
RNSImageTypeSfSymbol,
RNSImageTypeXcasset,
};

typedef NS_ENUM(NSInteger, RNSImageSourceType) {
RNSImageSourceTypeResourceName,
RNSImageSourceTypeReactImageSource,
};

@interface RNSImageSource : NSObject

@property (nonatomic, readonly) RNSImageSourceType imageSourceType;

+ (nullable RNSImageSource *)imageSourceDescriptorWithResourceName:(nullable NSString *)resourceName;

+ (nullable RNSImageSource *)xcassetSourceDescriptorWithResourceName:(nullable NSString *)resourceName;

+ (nullable RNSImageSource *)sfSymbolSourceDescriptorWithResourceName:(nullable NSString *)resourceName;

+ (nullable RNSImageSource *)reactImageSourceDescriptorWithImageSource:(nullable RCTImageSource *)imageSource;

@end

@interface RNSResourceNameSourceDescriptor : RNSImageSource

- (nullable instancetype)initWithResourceName:(nullable NSString *)resourceName;

@property (nonatomic, readonly, nullable) NSString *resourceName;

@end

@interface RNSReactImageSourceSourceDescriptor : RNSImageSource

- (nullable instancetype)initWithReactImageSource:(nullable RCTImageSource *)imageSource;

@property (nonatomic, readonly, nullable) RCTImageSource *imageSource;

@end

/**
* Information necessary to load an image with image loading helper.
*/
@interface RNSImageDescriptor : NSObject

@property (nonatomic, readonly) RNSImageType imageType;

@property (nonatomic, readonly, nonnull) RNSImageSource *imageSource;

@end

@interface RNSImageLoadingHelper : NSObject

+ (void)loadImageFromDescriptor:(nonnull RNSImageDescriptor *)imageDescriptor
withImageLoader:(nonnull RCTImageLoader *)reactImageLoader
asTemplate:(BOOL)isTemplate
completionBlock:(void (^_Nonnull)(UIImage *_Nullable image))imageLoadingCompletionBlock;

/**
* Should be called from UI thread only.
* If done so, the method **tries** to load the image synchronously from image source represented in JSON via
Expand Down
149 changes: 149 additions & 0 deletions ios/helpers/image/RNSImageLoadingHelper.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,36 @@

@implementation RNSImageLoadingHelper

+ (void)loadImageFromDescriptor:(nonnull RNSImageDescriptor *)imageDescriptor
withImageLoader:(nonnull RCTImageLoader *)reactImageLoader
asTemplate:(BOOL)isTemplate
completionBlock:(void (^_Nonnull)(UIImage *_Nullable image))imageLoadingCompletionBlock
{
RCTAssert(RCTIsMainQueue(), @"[RNScreens] Expected to run on the main queue");
RCTAssert(imageDescriptor != nil, @"[RNScreens] Expected non-null image descriptor");
RCTAssert(reactImageLoader != nil, @"[RNScreens] Expected non-null image loader");

auto completionBlock = ^(UIImage *image) {
imageLoadingCompletionBlock([self handleRenderingModeForImage:image isTemplate:isTemplate]);
};

if (imageDescriptor == nil || reactImageLoader == nil) {
return;
}

switch (imageDescriptor.imageType) {
case RNSImageTypeImage:
break;
case RNSImageTypeTemplate:
break;
case RNSImageTypeSfSymbol:
[self loadSfSymbolFromSource:imageDescriptor.imageSource completionBlock:completionBlock];
break;
case RNSImageTypeXcasset:
break;
}
}

+ (void)loadImageSyncIfPossibleFromJsonSource:(nonnull NSDictionary *)jsonImageSource
withImageLoader:(nonnull RCTImageLoader *)imageLoader
asTemplate:(BOOL)isTemplate
Expand Down Expand Up @@ -68,4 +98,123 @@ + (nullable UIImage *)handleRenderingModeForImage:(nullable UIImage *)image isTe
}
}

+ (void)loadSfSymbolFromSource:(nonnull RNSImageSource *)sourceDescriptor
completionBlock:(void (^_Nonnull)(UIImage *_Nullable image))imageLoadingCompletionBlock
{
if (sourceDescriptor.imageSourceType != RNSImageSourceTypeResourceName) {
imageLoadingCompletionBlock(nil);
return;
}

const auto *resourceNameSource = static_cast<RNSResourceNameSourceDescriptor *>(sourceDescriptor);
UIImage *_Nullable loadedImage = [UIImage systemImageNamed:resourceNameSource.resourceName];
imageLoadingCompletionBlock(loadedImage);
}

+ (void)loadTemplateFromSource:(nonnull RNSImageSource *)sourceDescriptor
withImageLoader:(nonnull RCTImageLoader *)imageLoader
completionBlock:(void (^_Nonnull)(UIImage *_Nullable image))imageLoadingCompletionBlock
{
if (sourceDescriptor.imageSourceType != RNSImageSourceTypeReactImageSource) {
imageLoadingCompletionBlock(nil);
return;
}

const auto *reactImageSource = static_cast<RNSReactImageSourceSourceDescriptor *>(sourceDescriptor);
[self loadImageFromSource:reactImageSource.imageSource
withImageLoader:imageLoader
asTemplate:YES
completionBlock:imageLoadingCompletionBlock];
}

+ (void)loadXcassetFromSource:(nonnull RNSImageSource *)imageSource
completionBlock:(void (^_Nonnull)(UIImage *_Nullable image))imageLoadingCompletionBlock
{
if (imageSource.imageSourceType != RNSImageSourceTypeResourceName) {
imageLoadingCompletionBlock(nil);
return;
}
const auto *resourceNameSource = static_cast<RNSResourceNameSourceDescriptor *>(imageSource);
UIImage *_Nullable loadedImage = [UIImage imageNamed:resourceNameSource.resourceName];
imageLoadingCompletionBlock(loadedImage);
}

@end

@interface RNSImageSource ()

@property (nonatomic) RNSImageSourceType imageSourceType;

@end

@implementation RNSImageSource

+ (nullable RNSImageSource *)imageSourceDescriptorWithResourceName:(nullable NSString *)resourceName
{
if (resourceName == nil) {
return nil;
}

return [[RNSResourceNameSourceDescriptor alloc] initWithResourceName:resourceName];
}

+ (nullable RNSImageSource *)xcassetSourceDescriptorWithResourceName:(nullable NSString *)resourceName
{
if (resourceName == nil) {
return nil;
}

return [[RNSResourceNameSourceDescriptor alloc] initWithResourceName:resourceName];
}

+ (nullable RNSImageSource *)sfSymbolSourceDescriptorWithResourceName:(nullable NSString *)resourceName
{
if (resourceName == nil) {
return nil;
}

return [[RNSResourceNameSourceDescriptor alloc] initWithResourceName:resourceName];
}

+ (nullable RNSImageSource *)reactImageSourceDescriptorWithImageSource:(nullable RCTImageSource *)imageSource
{
if (imageSource == nil) {
return nil;
}
return [[RNSReactImageSourceSourceDescriptor alloc] initWithReactImageSource:imageSource];
}

@end

@implementation RNSResourceNameSourceDescriptor

- (nullable instancetype)initWithResourceName:(nullable NSString *)resourceName
{
if (self = [super init]) {
self.imageSourceType = RNSImageSourceTypeResourceName;
_resourceName = resourceName;
}
return self;
}

@end

@implementation RNSReactImageSourceSourceDescriptor

- (nullable instancetype)initWithReactImageSource:(nullable RCTImageSource *)imageSource
{
if (self = [super init]) {
self.imageSourceType = RNSImageSourceTypeReactImageSource;
_imageSource = imageSource;
}
return self;
}

@end

/**
* Information necessary to load an image with image loading helper.
*/
@implementation RNSImageDescriptor

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

#import "RNSExternalImageRepository.h"

typedef void (^ImageCallback)(NSString *, UIImage *);

@interface RNSExternalImageRepository ()

- (nullable UIImage *)imageForKey:(nonnull NSString *)key withInsertionCallback:(ImageCallback)callback;

@end
35 changes: 35 additions & 0 deletions ios/integrations/image-repository/RNSExternalImageRepository.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#pragma once

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

/**
* External image repository.
*
*/
@interface RNSExternalImageRepository : NSObject

+ (instancetype)sharedInstance;

/**
* This method will trigger an assertion error in debug mode if the key is nullish.
* In release it'll return `NO`.
*/
- (BOOL)insertImage:(nullable UIImage *)image forKey:(nonnull NSString *)key;

/**
* This method will trigger an assertion error in debug mode if the key is nullish.
* In release it'll return `nil`.
*/
- (nullable UIImage *)imageForKey:(nonnull NSString *)key;

/**
* This method will trigger an assertion error in debug mode if the key is nullish.
* In release it'll return `NO`.
*/
- (BOOL)removeImageForKey:(nonnull NSString *)key;

@end

NS_ASSUME_NONNULL_END
88 changes: 88 additions & 0 deletions ios/integrations/image-repository/RNSExternalImageRepository.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#import "RNSExternalImageRepository.h"
#import <React/RCTAssert.h>
#import "RNSExternalImageRepository+Internal.h"

@implementation RNSExternalImageRepository {
NSMutableDictionary<NSString *, UIImage *> *_imageRegistry;
NSMutableDictionary<NSString *, ImageCallback> *_callbackRegistry;
}

+ (instancetype)sharedInstance
{
static RNSExternalImageRepository *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [RNSExternalImageRepository new];
});

return instance;
}

- (BOOL)insertImage:(nullable UIImage *)image forKey:(nonnull NSString *)key
{
RCTAssert(key != nil, @"[RNScreens] Nullish key on attempt to insert image: %@ to the registry", image);

if ([[self requireStorage] objectForKey:key]) {
return NO;
}

[[self requireStorage] setObject:image forKey:key];
_Nullable ImageCallback imageCallback = [[self requireCallbackRegistry] valueForKey:key];
if (imageCallback != nil) {
imageCallback(key, image);
}
return YES;
}

- (nullable UIImage *)imageForKey:(nonnull NSString *)key
{
return [[self requireStorage] objectForKey:key];
}

- (BOOL)removeImageForKey:(nonnull NSString *)key
{
if ([[self requireStorage] objectForKey:key]) {
[[self requireStorage] removeObjectForKey:key];
return YES;
}
return NO;
}

- (nullable UIImage *)imageForKey:(nonnull NSString *)key withInsertionCallback:(ImageCallback)callback
{
UIImage *image = [self imageForKey:key];

if (image != nil) {
return image;
}

if (callback == nil) {
return nil;
}

if ([[self requireCallbackRegistry] objectForKey:key]) {
RCTAssert(NO, @"[RNScreens] A callback is already registered for image with key: %@", key);
return nil;
}

[[self requireCallbackRegistry] setValue:callback forKey:key];
return nil;
}

- (NSMutableDictionary *)requireStorage
{
if (_imageRegistry == nil) {
_imageRegistry = [NSMutableDictionary new];
}
return _imageRegistry;
}

- (NSMutableDictionary *)requireCallbackRegistry
{
if (_callbackRegistry == nil) {
_callbackRegistry = [NSMutableDictionary new];
}
return _callbackRegistry;
}

@end
11 changes: 11 additions & 0 deletions ios/tabs/screen/RNSTabsScreenComponentView+Internal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

#import "RNSTabsScreenComponentView.h"

@class RNSImageSource;

@interface RNSTabsScreenComponentView ()

- (nullable RNSImageSource *)createIconImageSource;

@end
Loading
Loading