diff --git a/apps/Example.tsx b/apps/Example.tsx index 306e1799c2..d2dbca98fe 100644 --- a/apps/Example.tsx +++ b/apps/Example.tsx @@ -30,7 +30,7 @@ import Orientation from './src/screens/Orientation'; import SearchBar from './src/screens/SearchBar'; import Events from './src/screens/Events'; import Gestures from './src/screens/Gestures'; -// import BarButtonItems from './src/screens/BarButtonItems'; +import BarButtonItems from './src/screens/BarButtonItems'; import { GestureDetectorProvider } from 'react-native-screens/gesture-handler'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; @@ -128,11 +128,11 @@ const SCREENS: Record< component: Gestures, type: 'playground', }, - // BarButtonItems: { - // title: 'Bar Button Items', - // component: BarButtonItems, - // type: 'playground', - // }, + BarButtonItems: { + title: 'Bar Button Items', + component: BarButtonItems, + type: 'playground', + }, }; if (isTestSectionEnabled()) { diff --git a/ios/RNSBarButtonItem.mm b/ios/RNSBarButtonItem.mm index a35d72871f..8a7f4bac64 100644 --- a/ios/RNSBarButtonItem.mm +++ b/ios/RNSBarButtonItem.mm @@ -2,7 +2,7 @@ #import #import #import -#import +#import "RCTImageSource+AccessHiddenMembers.h" #import "RNSDefines.h" @implementation RNSBarButtonItem { @@ -20,48 +20,36 @@ - (instancetype)initWithConfig:(NSDictionary *)dict return self; } - self.title = dict[@"title"]; - + NSString *title = dict[@"title"]; NSDictionary *imageSourceObj = dict[@"imageSource"]; NSDictionary *templateSourceObj = dict[@"templateSource"]; - - RCTImageSource *imageSource = nil; - if (imageSourceObj) { - imageSource = [RCTConvert RCTImageSource:imageSourceObj]; - } else if (templateSourceObj) { - imageSource = [RCTConvert RCTImageSource:templateSourceObj]; - } - - if (imageSource) { - [imageLoader loadImageWithURLRequest:imageSource.request - size:imageSource.size - scale:imageSource.scale - clipped:true - resizeMode:RCTResizeModeContain - progressBlock:^(int64_t progress, int64_t total) { - } - partialLoadBlock:^(UIImage *_Nonnull image) { - } - completionBlock:^(NSError *_Nullable error, UIImage *_Nullable image) { - dispatch_async(dispatch_get_main_queue(), ^{ - UIImage *imageWithRenderingMode = nil; - if (imageSourceObj) { - imageWithRenderingMode = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; - } else if (templateSourceObj) { - imageWithRenderingMode = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - } - self.image = imageWithRenderingMode; - }); - }]; - } NSString *sfSymbolName = dict[@"sfSymbolName"]; - if (sfSymbolName) { + + if (imageSourceObj != nil) { + void (^completionAction)(NSError *_Nullable, UIImage *_Nullable) = + ^(NSError *_Nullable error, UIImage *_Nullable image) { + self.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; + }; + [self loadImageSyncFromImageSourceJson:imageSourceObj withImageLoader:imageLoader completionBlock:completionAction]; + } else if (templateSourceObj != nil) { + void (^completionAction)(NSError *_Nullable, UIImage *_Nullable) = + ^(NSError *_Nullable error, UIImage *_Nullable image) { + self.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + }; + [self loadImageSyncFromImageSourceJson:templateSourceObj + withImageLoader:imageLoader + completionBlock:completionAction]; + } else if (sfSymbolName != nil) { self.image = [UIImage systemImageNamed:sfSymbolName]; } - NSDictionary *titleStyle = dict[@"titleStyle"]; - if (titleStyle) { - [self setTitleStyleFromConfig:titleStyle]; + if (title != nil) { + self.title = title; + + NSDictionary *titleStyle = dict[@"titleStyle"]; + if (titleStyle != nil) { + [self setTitleStyleFromConfig:titleStyle]; + } } id tintColorObj = dict[@"tintColor"]; @@ -339,4 +327,55 @@ - (void)setBadgeFromConfig:(NSDictionary *)badgeObj } #endif +/** + * Should be called from UI thread only. If done so, the method **tries** to load the image synchronously. + * There is no guarantee, because in release mode we rely on `RCTImageLoader` implementation details. + * No matter how the image is loaded, `completionBlock` is executed on main queue. + */ +- (void)loadImageSyncFromImageSourceJson:(nonnull NSDictionary *)imageSourceJson + withImageLoader:(nullable RCTImageLoader *)imageLoader + completionBlock: + (void (^_Nonnull)(NSError *_Nullable error, UIImage *_Nullable image))completion +{ + RCTAssert(RCTIsMainQueue(), @"[RNScreens] Expected to run on main queue"); + + RCTImageSource *imageSource = [RCTConvert RCTImageSource:imageSourceJson]; + RCTAssert(imageSource != nil, @"[RNScreens] Expected nonnill image source"); + + // We use `+ [RCTConvert UIImage:]` only in debug mode, because it is deprecated, however + // I haven't found different way to load image synchronously in debug other than + // writing the code manually. + +#if !defined(NDEBUG) // We're in debug mode here + if (!imageSource.packagerAsset) { + // This is rather unexpected. In debug mode local asset should be sourced from packager. + RCTLogWarn(@"[RNScreens] Unexpected case during image load: loading not a packager asset"); + } + + // Try to load anyway. + UIImage *loadedImage = [RCTConvert UIImage:imageSourceJson]; + completion(nil, loadedImage); + return; +#else // We're in release mode here + [imageLoader loadImageWithURLRequest:imageSource.request + size:imageSource.size + scale:imageSource.scale + clipped:true + resizeMode:RCTResizeModeContain + progressBlock:^(int64_t progress, int64_t total) { + } + partialLoadBlock:^(UIImage *_Nonnull image) { + } + completionBlock:^(NSError *_Nullable error, UIImage *_Nullable image) { + if (RCTIsMainQueue()) { + completion(error, image); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(error, image); + }); + } + }]; +#endif +} + @end diff --git a/ios/utils/extensions/RCTImageSource+AccessHiddenMembers.h b/ios/utils/extensions/RCTImageSource+AccessHiddenMembers.h new file mode 100644 index 0000000000..6d4ad928d3 --- /dev/null +++ b/ios/utils/extensions/RCTImageSource+AccessHiddenMembers.h @@ -0,0 +1,9 @@ +#pragma once + +// This field should exist in extension in `RCTImageSource.m` + +@interface RCTImageSource (AccessHiddenMembers) + +@property (nonatomic, assign) BOOL packagerAsset; + +@end diff --git a/react-navigation b/react-navigation index 3d1e4effe8..782b6d4bb4 160000 --- a/react-navigation +++ b/react-navigation @@ -1 +1 @@ -Subproject commit 3d1e4effe82daba682df308556eaabb73052a45c +Subproject commit 782b6d4bb43ab69c1d67447e9aa0bcfe46780056 diff --git a/src/gesture-handler/fabricUtils.ts b/src/gesture-handler/fabricUtils.ts index 0a6f36a039..4f882b3ded 100644 --- a/src/gesture-handler/fabricUtils.ts +++ b/src/gesture-handler/fabricUtils.ts @@ -1,6 +1,6 @@ 'use strict'; -import { View } from "react-native"; +import { View } from 'react-native'; /* eslint-disable */ @@ -24,11 +24,11 @@ export function getShadowNodeWrapperAndTagFromRef(ref: View | null): { return { shadowNodeWrapper: null, tag: -1, - } + }; } const internalRef = ref as unknown as HostInstance; return { shadowNodeWrapper: internalRef.__internalInstanceHandle.stateNode.node, tag: internalRef.__nativeTag, - } + }; }