diff --git a/.changeset/sour-cases-appear.md b/.changeset/sour-cases-appear.md
new file mode 100644
index 0000000..1c2b40a
--- /dev/null
+++ b/.changeset/sour-cases-appear.md
@@ -0,0 +1,5 @@
+---
+'react-native-bottom-tabs': minor
+---
+
+feat: drop SDWebImage, resolve bunch of build issues
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7ca2202..b48763f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -39,15 +39,7 @@ To run the React Native example app on Android:
yarn workspace react-native-bottom-tabs-example android
```
-To run the React Native example app on iOS:
-
-Make sure to install [`cocoapods-swift-modular-headers`](https://github.com/callstack/cocoapods-swift-modular-headers) gem, otherwise `pod install` will fail.
-
-```sh
-gem install cocoapods-swift-modular-headers
-```
-
-Next you can install cocoapods.
+To run the React Native example app on iOS, you need to install cocoapods.
```sh
cd apps/example/ios
diff --git a/apps/example/ios/Podfile b/apps/example/ios/Podfile
index 3eb951a..939a118 100644
--- a/apps/example/ios/Podfile
+++ b/apps/example/ios/Podfile
@@ -1,4 +1,4 @@
-plugin 'cocoapods-swift-modular-headers'
+ENV['RCT_NEW_ARCH_ENABLED'] = '1'
ws_dir = Pathname.new(__dir__)
ws_dir = ws_dir.parent until
@@ -8,6 +8,4 @@ require "#{ws_dir}/node_modules/react-native-test-app/test_app.rb"
workspace 'ReactNativeBottomTabsExample.xcworkspace'
-apply_modular_headers_for_swift_dependencies()
-
use_test_app! :hermes_enabled => true, :fabric_enabled => true
diff --git a/apps/example/ios/Podfile.lock b/apps/example/ios/Podfile.lock
index 6d6a375..918be67 100644
--- a/apps/example/ios/Podfile.lock
+++ b/apps/example/ios/Podfile.lock
@@ -1748,7 +1748,37 @@ PODS:
- React-RCTFBReactNativeSpec
- ReactCommon/turbomodule/core
- SocketRocket
- - react-native-bottom-tabs (0.11.1):
+ - react-native-bottom-tabs (0.11.2):
+ - boost
+ - DoubleConversion
+ - fast_float
+ - fmt
+ - glog
+ - hermes-engine
+ - RCT-Folly
+ - RCT-Folly/Fabric
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
+ - React-ImageManager
+ - React-jsi
+ - react-native-bottom-tabs/common (= 0.11.2)
+ - React-NativeModulesApple
+ - React-RCTFabric
+ - React-renderercss
+ - React-rendererdebug
+ - React-utils
+ - ReactCodegen
+ - ReactCommon/turbomodule/bridging
+ - ReactCommon/turbomodule/core
+ - SocketRocket
+ - SwiftUIIntrospect (~> 1.0)
+ - Yoga
+ - react-native-bottom-tabs/common (0.11.2):
- boost
- DoubleConversion
- fast_float
@@ -1774,8 +1804,6 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- - SDWebImage (>= 5.19.1)
- - SDWebImageSVGCoder (>= 1.7.0)
- SocketRocket
- SwiftUIIntrospect (~> 1.0)
- Yoga
@@ -2520,11 +2548,6 @@ PODS:
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- - SDWebImage (5.21.1):
- - SDWebImage/Core (= 5.21.1)
- - SDWebImage/Core (5.21.1)
- - SDWebImageSVGCoder (1.8.0):
- - SDWebImage/Core (~> 5.6)
- SocketRocket (0.7.1)
- SwiftUIIntrospect (1.3.0)
- Yoga (0.0.0)
@@ -2614,8 +2637,6 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
- - SDWebImage
- - SDWebImageSVGCoder
- SocketRocket
- SwiftUIIntrospect
@@ -2821,7 +2842,7 @@ SPEC CHECKSUMS:
React-logger: a3cb5b29c32b8e447b5a96919340e89334062b48
React-Mapbuffer: 9d2434a42701d6144ca18f0ca1c4507808ca7696
React-microtasksnativemodule: 75b6604b667d297292345302cc5bfb6b6aeccc1b
- react-native-bottom-tabs: fa973f009e321d7d11dbdb761192ce185948a05a
+ react-native-bottom-tabs: d71dd2e1b69f11d3ed2da2db23016ebdc77f4ba1
react-native-safe-area-context: c6e2edd1c1da07bdce287fa9d9e60c5f7b514616
React-NativeModulesApple: 879fbdc5dcff7136abceb7880fe8a2022a1bd7c3
React-oscompat: 93b5535ea7f7dff46aaee4f78309a70979bdde9d
@@ -2859,12 +2880,10 @@ SPEC CHECKSUMS:
RNGestureHandler: 3a73f098d74712952870e948b3d9cf7b6cae9961
RNScreens: 0bbf16c074ae6bb1058a7bf2d1ae017f4306797c
RNVectorIcons: c13cc1db346e960ecd0aafcdd5d0bb458133b9c1
- SDWebImage: f29024626962457f3470184232766516dee8dfea
- SDWebImageSVGCoder: 8e10c8f6cc879c7dfb317b284e13dd589379f01c
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
SwiftUIIntrospect: fee9aa07293ee280373a591e1824e8ddc869ba5d
Yoga: a3ed390a19db0459bd6839823a6ac6d9c6db198d
-PODFILE CHECKSUM: e4dd5fac8fa6e00534aac80dc857efbb13ef2723
+PODFILE CHECKSUM: d61a3255405492afdb755f6ddd335fd0f5a84cd3
COCOAPODS: 1.16.2
diff --git a/docs/docs/docs/getting-started/quick-start.mdx b/docs/docs/docs/getting-started/quick-start.mdx
index 71e9076..aa93773 100644
--- a/docs/docs/docs/getting-started/quick-start.mdx
+++ b/docs/docs/docs/getting-started/quick-start.mdx
@@ -19,38 +19,6 @@ If you are going to use [React Navigation / Expo Router Integration](/docs/guide
-
- If you use React Native version 0.75 or lower
-
-- For `@react-native-community/cli` users, open Podfile in ios folder and change minimum iOS version to `14.0` before `pod install`
-
-```diff
-- platform :ios, min_ios_version_supported
-+ platform :ios, '14.0'
-```
-
-- For Expo users, install `expo-build-properties`, open app.json file and update `deploymentTarget` for `ios` as below
-
-```json
-{
- "expo": {
- "plugins": [
- [
- "expo-build-properties",
- {
- "ios": {
- "deploymentTarget": "14.0"
- }
- }
- ]
- ],
- }
-}
-```
-
-
-
-
### Expo
@@ -64,53 +32,6 @@ Add the library plugin in your `app.json` config file and [create a new build](h
}
```
-Then install `expo-build-properties` to enable static linking for iOS by adding `"useFrameworks": "static"` in the plugin.
-
-```sh
-npx expo install expo-build-properties
-```
-
-```diff
-{
- "expo": {
- "plugins": [
- "react-native-bottom-tabs",
-+ [
-+ "expo-build-properties",
-+ {
-+ "ios": {
-+ "useFrameworks": "static"
-+ }
-+ }
-+ ]
-+ ]
- }
-}
-```
-
-Alternatively, you can avoid enabling static linking (which can cause problems with your existing packages) by adding the following in the `expo-build-properties` plugin.
-
-```diff
-{
- "expo": {
- "plugins": [
- "react-native-bottom-tabs",
-+ [
-+ "expo-build-properties",
-+ {
-+ "ios": {
-+ "extraPods": [
-+ { name: "SDWebImage", modular_headers: true }, // Work around for not enabling static framework, required for react-native-bottom-tabs
-+ { name: "SDWebImageSVGCoder", modular_headers: true }
-+ ]
-+ }
-+ }
-+ ]
-+ ]
- }
-}
-```
-
:::warning
This library is not supported in [Expo Go](https://expo.dev/go).
@@ -133,12 +54,6 @@ Edit `android/app/src/main/res/values/styles.xml` to inherit from provided theme
Here you can read more about [Android Native Styling](/docs/guides/android-native-styling).
-To enable static linking for iOS, Open the `./ios/Podfile` file and add the following:
-
-```ruby
-use_frameworks! :linkage => :static
-```
-
## Example usage
diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h
new file mode 100644
index 0000000..3bd5d35
--- /dev/null
+++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h
@@ -0,0 +1,33 @@
+#ifdef __cplusplus
+
+#pragma once
+
+#include
+#include
+#include
+
+namespace facebook::react {
+
+class RNCTabViewComponentDescriptor final
+ : public ConcreteComponentDescriptor {
+ public:
+ using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
+
+ void adopt(ShadowNode& shadowNode) const override {
+ ConcreteComponentDescriptor::adopt(shadowNode);
+
+#if !defined(ANDROID)
+ auto &tabViewShadowNode =
+ static_cast(shadowNode);
+
+ std::weak_ptr imageLoader =
+ contextContainer_->at>("RCTImageLoader");
+ tabViewShadowNode.setImageLoader(imageLoader);
+#endif
+ }
+};
+
+
+} // namespace facebook::react
+
+#endif
diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp
new file mode 100644
index 0000000..dcdcaf2
--- /dev/null
+++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp
@@ -0,0 +1,19 @@
+#include
+
+namespace facebook::react {
+
+extern const char RNCTabViewComponentName[] = "RNCTabView";
+
+void RNCTabViewShadowNode::setImageLoader(
+ std::weak_ptr imageLoader) {
+ getStateDataMutable().setImageLoader(imageLoader);
+}
+
+RNCTabViewShadowNode::StateData &
+RNCTabViewShadowNode::getStateDataMutable() {
+ ensureUnsealed();
+ return const_cast(getStateData());
+}
+
+
+} // namespace facebook::react
diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h
new file mode 100644
index 0000000..43e37fa
--- /dev/null
+++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h
@@ -0,0 +1,35 @@
+#ifdef __cplusplus
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+namespace facebook::react {
+
+JSI_EXPORT extern const char RNCTabViewComponentName[];
+
+/*
+ * `ShadowNode` for component.
+ */
+class JSI_EXPORT RNCTabViewShadowNode final: public ConcreteViewShadowNode<
+RNCTabViewComponentName,
+RNCTabViewProps,
+RNCTabViewEventEmitter,
+RNCTabViewState> {
+
+public:
+ using ConcreteViewShadowNode::ConcreteViewShadowNode;
+ using StateData = ConcreteViewShadowNode::ConcreteStateData;
+
+ void setImageLoader(std::weak_ptr imageLoader);
+
+ StateData &getStateDataMutable();
+};
+
+}
+
+#endif
diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.cpp b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.cpp
new file mode 100644
index 0000000..e4a536d
--- /dev/null
+++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.cpp
@@ -0,0 +1,15 @@
+#include
+
+namespace facebook::react {
+
+void RNCTabViewState::setImageLoader(
+ std::weak_ptr imageLoader) {
+ imageLoader_ = imageLoader;
+}
+
+std::weak_ptr RNCTabViewState::getImageLoader()
+ const noexcept {
+ return imageLoader_;
+}
+
+} // namespace facebook::react
diff --git a/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.h b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.h
new file mode 100644
index 0000000..cb4e2d0
--- /dev/null
+++ b/packages/react-native-bottom-tabs/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.h
@@ -0,0 +1,25 @@
+#ifdef __cplusplus
+
+#pragma once
+
+#include
+#ifdef RN_SERIALIZABLE_STATE
+#include
+#endif
+
+namespace facebook::react {
+
+class RNCTabViewState final {
+ public:
+ RNCTabViewState() = default;
+
+ void setImageLoader(std::weak_ptr imageLoader);
+ std::weak_ptr getImageLoader() const noexcept;
+
+ private:
+ std::weak_ptr imageLoader_;
+};
+
+} // namespace facebook::react
+
+#endif
diff --git a/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm b/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm
index 9b5a2b3..c41f931 100644
--- a/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm
+++ b/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm
@@ -1,7 +1,7 @@
#ifdef RCT_NEW_ARCH_ENABLED
#import "RCTTabViewComponentView.h"
-#import
+#import
#import
#import
#import
@@ -19,6 +19,7 @@
#import
#import "RCTImagePrimitivesConversions.h"
#import "RCTConversions.h"
+#import
#if TARGET_OS_OSX
typedef NSView PlatformView;
@@ -200,6 +201,15 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
return result;
}
+- (void)updateState:(const facebook::react::State::Shared &)state oldState:(const facebook::react::State::Shared &)oldState
+{
+ auto _state = std::static_pointer_cast(state);
+ auto data = _state->getData();
+ if (auto imgLoaderPtr = _state.get()->getData().getImageLoader().lock()) {
+ [_tabViewProvider setImageLoader:unwrapManagedObject(imgLoaderPtr)];
+ }
+}
+
// MARK: TabViewProviderDelegate
- (void)onPageSelectedWithKey:(NSString *)key reactTag:(NSNumber *)reactTag {
diff --git a/packages/react-native-bottom-tabs/ios/RCTTabViewViewManager.mm b/packages/react-native-bottom-tabs/ios/RCTTabViewViewManager.mm
index 491bdc6..3a761d1 100644
--- a/packages/react-native-bottom-tabs/ios/RCTTabViewViewManager.mm
+++ b/packages/react-native-bottom-tabs/ios/RCTTabViewViewManager.mm
@@ -73,7 +73,10 @@ - (NSView *)view
- (UIView *) view
#endif
{
- return [[TabViewProvider alloc] initWithDelegate:self];
+ TabViewProvider *tabview = [[TabViewProvider alloc] initWithDelegate:self];
+ RCTImageLoader *imageLoader = [self.bridge moduleForClass:[RCTImageLoader class]];
+ [tabview setImageLoader:imageLoader];
+ return tabview;
}
@end
diff --git a/packages/react-native-bottom-tabs/ios/SVG/CoreSVG.h b/packages/react-native-bottom-tabs/ios/SVG/CoreSVG.h
new file mode 100644
index 0000000..3b5ac54
--- /dev/null
+++ b/packages/react-native-bottom-tabs/ios/SVG/CoreSVG.h
@@ -0,0 +1,19 @@
+#if TARGET_OS_OSX
+#import
+typedef NSImage PlatformImage;
+#else
+#import
+typedef UIImage PlatformImage;
+#endif
+
+
+@interface CoreSVGWrapper : NSObject
+
++ (instancetype)shared;
+
+- (PlatformImage *)imageFromSVGData:(NSData *)data;
+
++ (BOOL)isSVGData:(NSData *)data;
++ (BOOL)supportsVectorSVGImage;
+
+@end
diff --git a/packages/react-native-bottom-tabs/ios/SVG/CoreSVG.mm b/packages/react-native-bottom-tabs/ios/SVG/CoreSVG.mm
new file mode 100644
index 0000000..44a554d
--- /dev/null
+++ b/packages/react-native-bottom-tabs/ios/SVG/CoreSVG.mm
@@ -0,0 +1,177 @@
+#import "CoreSVG.h"
+#import
+#import
+
+#define kSVGTagEnd @""
+
+typedef struct CF_BRIDGED_TYPE(id) CGSVGDocument *CGSVGDocumentRef;
+
+static CGSVGDocumentRef (*CoreSVGDocumentRetain)(CGSVGDocumentRef);
+static void (*CoreSVGDocumentRelease)(CGSVGDocumentRef);
+static CGSVGDocumentRef (*CoreSVGDocumentCreateFromData)(CFDataRef data, CFDictionaryRef options);
+static void (*CoreSVGContextDrawSVGDocument)(CGContextRef context, CGSVGDocumentRef document);
+static CGSize (*CoreSVGDocumentGetCanvasSize)(CGSVGDocumentRef document);
+
+#if !TARGET_OS_OSX
+static SEL CoreSVGImageWithDocumentSEL = NULL;
+static SEL CoreSVGDocumentSEL = NULL;
+#endif
+#if TARGET_OS_OSX
+static Class CoreSVGImageRepClass = NULL;
+static Ivar CoreSVGImageRepDocumentIvar = NULL;
+#endif
+
+static inline NSString *Base64DecodedString(NSString *base64String) {
+ NSData *data = [[NSData alloc] initWithBase64EncodedString:base64String options:NSDataBase64DecodingIgnoreUnknownCharacters];
+ if (!data) {
+ return nil;
+ }
+ return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+}
+
+@implementation CoreSVGWrapper
+
++ (instancetype)shared {
+ static dispatch_once_t onceToken;
+ static CoreSVGWrapper *wrapper;
+ dispatch_once(&onceToken, ^{
+ wrapper = [[CoreSVGWrapper alloc] init];
+ });
+ return wrapper;
+}
+
++ (void)initialize {
+ CoreSVGDocumentRetain = (CGSVGDocumentRef (*)(CGSVGDocumentRef))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dTVkdEb2N1bWVudFJldGFpbg==").UTF8String);
+ CoreSVGDocumentRelease = (void (*)(CGSVGDocumentRef))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dTVkdEb2N1bWVudFJlbGVhc2U=").UTF8String);
+ CoreSVGDocumentCreateFromData = (CGSVGDocumentRef (*)(CFDataRef data, CFDictionaryRef options))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dTVkdEb2N1bWVudENyZWF0ZUZyb21EYXRh").UTF8String);
+ CoreSVGContextDrawSVGDocument = (void (*)(CGContextRef context, CGSVGDocumentRef document))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dDb250ZXh0RHJhd1NWR0RvY3VtZW50").UTF8String);
+ CoreSVGDocumentGetCanvasSize = (CGSize (*)(CGSVGDocumentRef document))dlsym(RTLD_DEFAULT, Base64DecodedString(@"Q0dTVkdEb2N1bWVudEdldENhbnZhc1NpemU=").UTF8String);
+
+#if !TARGET_OS_OSX
+ CoreSVGImageWithDocumentSEL = NSSelectorFromString(Base64DecodedString(@"X2ltYWdlV2l0aENHU1ZHRG9jdW1lbnQ6"));
+ CoreSVGDocumentSEL = NSSelectorFromString(Base64DecodedString(@"X0NHU1ZHRG9jdW1lbnQ="));
+#endif
+#if TARGET_OS_OSX
+ CoreSVGImageRepClass = NSClassFromString(Base64DecodedString(@"X05TU1ZHSW1hZ2VSZXA="));
+ if (CoreSVGImageRepClass) {
+ CoreSVGImageRepDocumentIvar = class_getInstanceVariable(CoreSVGImageRepClass, Base64DecodedString(@"X2RvY3VtZW50").UTF8String);
+ }
+#endif
+}
+
+- (PlatformImage *)imageFromSVGData:(NSData *)data {
+ if (!data) {
+ return nil;
+ }
+
+ if (![self.class supportsVectorSVGImage]) {
+ return nil;
+ }
+
+ return [self createVectorSVGWithData:data];
+}
+
+- (PlatformImage *)createVectorSVGWithData:(NSData *)data {
+ if (!data) return nil;
+
+ PlatformImage *image;
+
+#if TARGET_OS_OSX
+ if (!CoreSVGImageRepClass) {
+ return nil;
+ }
+ Class imageRepClass = CoreSVGImageRepClass;
+ NSImageRep *imageRep = [[imageRepClass alloc] initWithData:data];
+ if (!imageRep) {
+ return nil;
+ }
+ image = [[NSImage alloc] initWithSize:imageRep.size];
+ [image addRepresentation:imageRep];
+#else
+ if (!CoreSVGDocumentCreateFromData || !CoreSVGDocumentRelease) {
+ return nil;
+ }
+ CGSVGDocumentRef document = CoreSVGDocumentCreateFromData((__bridge CFDataRef)data, NULL);
+
+ if (!document) {
+ return nil;
+ }
+
+ image = ((UIImage *(*)(id,SEL,CGSVGDocumentRef))[UIImage.class methodForSelector:CoreSVGImageWithDocumentSEL])(UIImage.class, CoreSVGImageWithDocumentSEL, document);
+ CoreSVGDocumentRelease(document);
+#endif
+
+#if TARGET_OS_OSX
+ // Test render to catch potential CoreSVG crashes on macOS
+ NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:NULL
+ pixelsWide:1
+ pixelsHigh:1
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSCalibratedRGBColorSpace
+ bytesPerRow:0
+ bitsPerPixel:0];
+
+ NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:bitmap];
+ [NSGraphicsContext saveGraphicsState];
+ [NSGraphicsContext setCurrentContext:context];
+
+ @try {
+ [image drawInRect:NSMakeRect(0, 0, 1, 1)];
+ } @catch (...) {
+ [NSGraphicsContext restoreGraphicsState];
+ return nil;
+ }
+
+ [NSGraphicsContext restoreGraphicsState];
+#else
+ // Test render to catch potential CoreSVG crashes
+ UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 1.0);
+ @try {
+ [image drawInRect:CGRectMake(0, 0, 1, 1)];
+ } @catch (...) {
+ UIGraphicsEndImageContext();
+ return nil;
+ }
+ UIGraphicsEndImageContext();
+#endif
+
+
+ return image;
+}
+
++ (BOOL)isSVGData:(NSData *)data {
+ if (!data) {
+ return NO;
+ }
+ // Check end with SVG tag
+ return [data rangeOfData:[kSVGTagEnd dataUsingEncoding:NSUTF8StringEncoding] options:NSDataSearchBackwards range: NSMakeRange(data.length - MIN(100, data.length), MIN(100, data.length))].location != NSNotFound;
+}
+
++ (BOOL)supportsVectorSVGImage {
+ static dispatch_once_t onceToken;
+ static BOOL supports;
+ dispatch_once(&onceToken, ^{
+#if TARGET_OS_OSX
+ // macOS 10.15+ supports SVG built-in rendering, use selector to check is more accurate
+ if (CoreSVGImageRepClass) {
+ supports = YES;
+ } else {
+ supports = NO;
+ }
+#else
+ // iOS 13+ supports SVG built-in rendering, use selector to check is more accurate
+ if ([UIImage respondsToSelector:CoreSVGImageWithDocumentSEL]) {
+ supports = YES;
+ } else {
+ supports = NO;
+ }
+#endif
+ });
+ return supports;
+}
+
+@end
diff --git a/packages/react-native-bottom-tabs/ios/SVG/SvgDecoder.h b/packages/react-native-bottom-tabs/ios/SVG/SvgDecoder.h
new file mode 100644
index 0000000..5e7fde1
--- /dev/null
+++ b/packages/react-native-bottom-tabs/ios/SVG/SvgDecoder.h
@@ -0,0 +1,10 @@
+#ifdef __cplusplus
+
+#import
+#import
+
+@interface SvgDecoder : NSObject
+
+@end
+
+#endif
diff --git a/packages/react-native-bottom-tabs/ios/SVG/SvgDecoder.mm b/packages/react-native-bottom-tabs/ios/SVG/SvgDecoder.mm
new file mode 100644
index 0000000..1bbddd0
--- /dev/null
+++ b/packages/react-native-bottom-tabs/ios/SVG/SvgDecoder.mm
@@ -0,0 +1,38 @@
+#import "SvgDecoder.h"
+#import "CoreSVG.h"
+
+@implementation SvgDecoder
+
+RCT_EXPORT_MODULE()
+
+- (BOOL)canDecodeImageData:(NSData *)imageData
+{
+ return [CoreSVGWrapper isSVGData:imageData];
+}
+
+- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
+ size:(CGSize)size
+ scale:(CGFloat)scale
+ resizeMode:(RCTResizeMode)resizeMode
+ completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
+{
+ UIImage *image = [CoreSVGWrapper.shared imageFromSVGData:imageData];
+
+ if (image) {
+ completionHandler(nil, image);
+ } else {
+ NSError *error = [NSError errorWithDomain:@"SVGDecoderErrorDomain"
+ code:2
+ userInfo:@{NSLocalizedDescriptionKey: @"Failed to render SVG to image"}];
+ completionHandler(error, nil);
+ }
+ return ^{};
+}
+
+- (std::shared_ptr)getTurboModule:
+(const facebook::react::ObjCTurboModule::InitParams &)params
+{
+ return std::make_shared(params);
+}
+
+@end
diff --git a/packages/react-native-bottom-tabs/ios/TabViewProvider.swift b/packages/react-native-bottom-tabs/ios/TabViewProvider.swift
index 6a3e4ea..e207a16 100644
--- a/packages/react-native-bottom-tabs/ios/TabViewProvider.swift
+++ b/packages/react-native-bottom-tabs/ios/TabViewProvider.swift
@@ -1,7 +1,5 @@
import Foundation
import React
-import SDWebImage
-import SDWebImageSVGCoder
import SwiftUI
@objcMembers
@@ -48,6 +46,7 @@ public final class TabInfo: NSObject {
}
@objc public class TabViewProvider: PlatformView {
+ private var imageLoader: RCTImageLoaderProtocol?
private weak var delegate: TabViewProviderDelegate?
private var props = TabViewProps()
private var hostingController: PlatformHostingController?
@@ -173,7 +172,11 @@ public final class TabInfo: NSObject {
@objc public convenience init(delegate: TabViewProviderDelegate) {
self.init()
self.delegate = delegate
- SDImageCodersManager.shared.addCoder(SDImageSVGCoder.shared)
+ }
+
+ @objc public func setImageLoader(_ imageLoader: RCTImageLoader) {
+ self.imageLoader = imageLoader
+ loadIcons(icons)
}
override public func didUpdateReactSubviews() {
@@ -238,40 +241,27 @@ public final class TabInfo: NSObject {
private func loadIcons(_ icons: NSArray?) {
// TODO: Diff the arrays and update only changed items.
// Now if the user passes `unfocusedIcon` we update every item.
- guard let imageSources = icons as? [RCTImageSource?] else { return }
-
- for (index, imageSource) in imageSources.enumerated() {
- guard let imageSource,
- let url = imageSource.request.url else { continue }
-
- let isSVG = url.pathExtension.lowercased() == "svg"
-
- var options: SDWebImageOptions = [.continueInBackground,
- .scaleDownLargeImages,
- .avoidDecodeImage,
- .highPriority]
-
- if isSVG {
- options.insert(.decodeFirstFrameOnly)
- }
-
- let context: [SDWebImageContextOption: Any]? = isSVG ? [
- .imageThumbnailPixelSize: iconSize,
- .imagePreserveAspectRatio: true
- ] : nil
-
- SDWebImageManager.shared.loadImage(
- with: url,
- options: options,
- context: context,
- progress: nil
- ) { [weak self] image, _, _, _, _, _ in
- guard let self else { return }
- DispatchQueue.main.async {
- if let image {
- self.props.icons[index] = image.resizeImageTo(size: self.iconSize)
- }
- }
+ if let imageSources = icons as? [RCTImageSource?] {
+ for (index, imageSource) in imageSources.enumerated() {
+ guard let imageSource, let imageLoader else { continue }
+ imageLoader.loadImage(
+ with: imageSource.request,
+ size: imageSource.size,
+ scale: imageSource.scale,
+ clipped: true,
+ resizeMode: RCTResizeMode.contain,
+ progressBlock: { _,_ in },
+ partialLoad: { _ in },
+ completionBlock: { error, image in
+ if error != nil {
+ print("[TabView] Error loading image: \(error!.localizedDescription)")
+ return
+ }
+ guard let image else { return }
+ DispatchQueue.main.async {
+ self.props.icons[index] = image.resizeImageTo(size: self.iconSize)
+ }
+ })
}
}
}
diff --git a/packages/react-native-bottom-tabs/package.json b/packages/react-native-bottom-tabs/package.json
index f4390bb..cba0caa 100644
--- a/packages/react-native-bottom-tabs/package.json
+++ b/packages/react-native-bottom-tabs/package.json
@@ -118,7 +118,7 @@
},
"codegenConfig": {
"name": "RNCTabView",
- "type": "components",
+ "type": "all",
"jsSrcsDir": "./src",
"android": {
"javaPackageName": "com.rcttabview"
@@ -126,6 +126,11 @@
"ios": {
"componentProvider": {
"RNCTabView": "RCTTabViewComponentView"
+ },
+ "modulesConformingToProtocol": {
+ "RCTImageDataDecoder": [
+ "SvgDecoder"
+ ]
}
}
}
diff --git a/packages/react-native-bottom-tabs/react-native-bottom-tabs.podspec b/packages/react-native-bottom-tabs/react-native-bottom-tabs.podspec
index d4b9289..befef4b 100644
--- a/packages/react-native-bottom-tabs/react-native-bottom-tabs.podspec
+++ b/packages/react-native-bottom-tabs/react-native-bottom-tabs.podspec
@@ -1,6 +1,7 @@
require "json"
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
+new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
Pod::Spec.new do |s|
s.name = "react-native-bottom-tabs"
@@ -20,9 +21,14 @@ Pod::Spec.new do |s|
s.source_files = "ios/**/*.{h,m,mm,cpp,swift}"
s.static_framework = true
+ if new_arch_enabled
+ s.subspec "common" do |ss|
+ ss.source_files = "common/cpp/**/*.{cpp,h}"
+ ss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/common/cpp\"" }
+ end
+ end
+
s.dependency "SwiftUIIntrospect", '~> 1.0'
- s.dependency 'SDWebImage', '>= 5.19.1'
- s.dependency 'SDWebImageSVGCoder', '>= 1.7.0'
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES'
diff --git a/packages/react-native-bottom-tabs/src/NativeSVGDecoder.ts b/packages/react-native-bottom-tabs/src/NativeSVGDecoder.ts
new file mode 100644
index 0000000..dad9f8f
--- /dev/null
+++ b/packages/react-native-bottom-tabs/src/NativeSVGDecoder.ts
@@ -0,0 +1,5 @@
+import { TurboModuleRegistry, type TurboModule } from 'react-native';
+
+export interface Spec extends TurboModule {}
+
+export default TurboModuleRegistry.getEnforcing('SvgDecoder');
diff --git a/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts b/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts
index f374688..ddc8bee 100644
--- a/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts
+++ b/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts
@@ -60,4 +60,6 @@ export interface TabViewProps extends ViewProps {
fontSize?: Int32;
}
-export default codegenNativeComponent('RNCTabView');
+export default codegenNativeComponent('RNCTabView', {
+ interfaceOnly: true,
+});