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
5 changes: 5 additions & 0 deletions .changeset/tidy-tools-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-native-bottom-tabs": major
---

Disable Apple-platform SVG tab icon support by default and add the `$RNBottomTabsEnableSVG` Podfile opt-in and the `RN_BOTTOM_TABS_ENABLE_SVG=1` Swift Package Manager opt-in. The optional CoreSVG decoder is now scoped to bottom-tab icons instead of being registered app-wide with React Native's image loader.
1 change: 1 addition & 0 deletions apps/example/ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
ENV['RCT_NEW_ARCH_ENABLED'] = '1'
$RNBottomTabsEnableSVG = ENV['RN_BOTTOM_TABS_ENABLE_SVG'] == '1'

ws_dir = Pathname.new(__dir__)
ws_dir = ws_dir.parent until
Expand Down
42 changes: 40 additions & 2 deletions docs/docs/docs/guides/usage-with-react-navigation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,6 @@ Function that given `{ focused: boolean }` returns `ImageSource` or `AppleIcon`
component={Albums}
options={{
tabBarIcon: () => require('person.png'),
// SVG is also supported
tabBarIcon: () => require('person.svg'),
// or
tabBarIcon: () => ({ sfSymbol: 'person' }),
// You can also pass a URL
Expand All @@ -286,6 +284,46 @@ Function that given `{ focused: boolean }` returns `ImageSource` or `AppleIcon`
SF Symbols are only supported on Apple platforms.
:::

#### SVG icons on Apple platforms

SVG tab icons remain available by default on Android. On Apple platforms, SVG support is disabled by default because its native decoder uses private CoreSVG APIs. PNG, JPEG, and SF Symbol icons do not need this decoder.

To opt in, set the flag before React Native configures pods in your `Podfile`, then run `pod install` and rebuild the native app:

```ruby
$RNBottomTabsEnableSVG = true

# require React Native's pod helpers and call use_react_native! below
```

With the decoder enabled, local and remote SVG sources can be used like other image sources:

```tsx
tabBarIcon: () => require('person.svg')
```

If your app uses Swift Package Manager instead of CocoaPods, set the `RN_BOTTOM_TABS_ENABLE_SVG` environment variable to `1` when SwiftPM resolves packages. For example, run the sync and the build from the same shell:

```sh
export RN_BOTTOM_TABS_ENABLE_SVG=1
npx react-native spm
npx react-native run-ios
```

Xcode reads this variable only when it is set in Xcode's own environment. If you open the project from the Dock or Finder, the variable from your shell does not reach it.

Enabling this flag compiles the optional CoreSVG implementation into the Apple-platform library. The decoder is scoped to bottom-tab icons and is not registered with React Native's app-wide image loader.

If you are upgrading from a version where Apple-platform SVG support was enabled automatically, add this flag before upgrading to preserve your existing SVG tab icons. Expo prebuild projects can use the package's `enableSVG` plugin option instead:

```json
{
"expo": {
"plugins": [["react-native-bottom-tabs", { "enableSVG": true }]]
}
}
```

#### `tabBarIconRenderingMode`

Rendering mode for image icons.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,7 @@ function NativeBottomTabsEmbeddedStacks() {
name="Chat"
component={ChatStackScreen}
options={{
tabBarIcon: () =>
require('../../assets/icons/message-circle-code.svg'),
tabBarIcon: () => require('../../assets/icons/chat_dark.png'),
}}
/>
</Tab.Navigator>
Expand Down
4 changes: 1 addition & 3 deletions packages/example-shared/src/Examples/TintColors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ export default function TintColorsExample() {
},
{
key: 'chat',
focusedIcon: {
uri: 'https://upload.wikimedia.org/wikipedia/commons/f/fa/Apple_logo_black.svg',
},
focusedIcon: require('../../assets/icons/chat_dark.png'),
title: 'Chat',
},
]);
Expand Down
20 changes: 16 additions & 4 deletions packages/react-native-bottom-tabs/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,24 @@

import PackageDescription

// Apple-platform SVG tab icons use private CoreSVG APIs, so they are opt-in.
// Set RN_BOTTOM_TABS_ENABLE_SVG=1 when SwiftPM resolves the package to enable them.
let enableSVG = Context.environment["RN_BOTTOM_TABS_ENABLE_SVG"] == "1"

let reactHeaders: [Target.Dependency] = [
.product(name: "ReactHeaders", package: "ReactNative"),
.product(name: "ReactNativeHeaders", package: "ReactNative"),
.product(name: "ReactNativeDependenciesHeaders", package: "ReactNative"),

Check warning on line 12 in packages/react-native-bottom-tabs/Package.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Trailing Comma Violation: Collection literals should not have trailing commas (trailing_comma)
]

let svgTargets: [Target] = enableSVG ? [
.target(
name: "BottomTabsSVG",
path: "ios/SVG",
publicHeadersPath: "."
),

Check warning on line 20 in packages/react-native-bottom-tabs/Package.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Trailing Comma Violation: Collection literals should not have trailing commas (trailing_comma)
] : []

let package = Package(
name: "react_native_bottom_tabs",
platforms: [.iOS(.v15)],
Expand All @@ -15,7 +27,7 @@
.library(
name: "react_native_bottom_tabs",
targets: ["react_native_bottom_tabs"]
),

Check warning on line 30 in packages/react-native-bottom-tabs/Package.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Trailing Comma Violation: Collection literals should not have trailing commas (trailing_comma)
],
dependencies: [
// React Native references this package through
Expand All @@ -23,14 +35,14 @@
// SwiftPM resolves these paths relative to that alias, including in monorepos.
.package(name: "ReactNative", path: "../../../../xcframeworks"),
.package(name: "React-GeneratedCode", path: "../../../ios"),
.package(url: "https://github.com/siteline/swiftui-introspect.git", "1.0.0"..<"2.0.0"),

Check warning on line 38 in packages/react-native-bottom-tabs/Package.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Trailing Comma Violation: Collection literals should not have trailing commas (trailing_comma)
],
targets: [
.target(
name: "BottomTabsSwift",
dependencies: reactHeaders + [
.product(name: "SwiftUIIntrospect", package: "swiftui-introspect"),

Check warning on line 44 in packages/react-native-bottom-tabs/Package.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Trailing Comma Violation: Collection literals should not have trailing commas (trailing_comma)
],
] + (enableSVG ? ["BottomTabsSVG"] : []),
path: "ios",
exclude: [
"RCTTabViewComponentView.h",
Expand All @@ -40,8 +52,9 @@
"react-native-bottom-tabs-Bridging-Header.h",
"SVG",
"include",
"Bridge",

Check warning on line 55 in packages/react-native-bottom-tabs/Package.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Trailing Comma Violation: Collection literals should not have trailing commas (trailing_comma)
]
],
swiftSettings: enableSVG ? [.define("RN_BOTTOM_TABS_ENABLE_SVG")] : []
),
.target(
name: "BottomTabsBridge",
Expand All @@ -60,7 +73,6 @@
sources: [
"ios/RCTTabViewComponentView.mm",
"ios/RCTBottomAccessoryComponentView.mm",
"ios/SVG",
"common/cpp",
],
publicHeadersPath: "ios/include",
Expand All @@ -72,7 +84,7 @@
.define("NDEBUG", .when(configuration: .release)),
]
),
],
] + svgTargets,
swiftLanguageModes: [.v5],
cxxLanguageStandard: .cxx20
)
2 changes: 2 additions & 0 deletions packages/react-native-bottom-tabs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

The full documentation can be found on our [website](https://oss.callstack.com/react-native-bottom-tabs/).

SVG tab icons on Apple platforms are disabled by default. See the [icon configuration guide](https://oss.callstack.com/react-native-bottom-tabs/docs/guides/usage-with-react-navigation#svg-icons-on-apple-platforms) for the Podfile and Expo opt-ins.

## Contributing

See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
Expand Down
26 changes: 21 additions & 5 deletions packages/react-native-bottom-tabs/ios/SVG/SvgDecoder.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
#ifdef __cplusplus
#import <Foundation/Foundation.h>
#import <TargetConditionals.h>

#if TARGET_OS_OSX
#import <AppKit/AppKit.h>
#else
#import <UIKit/UIKit.h>
#endif

#import <RNCTabView/RNCTabView.h>
#import <React/RCTImageDataDecoder.h>
NS_ASSUME_NONNULL_BEGIN

@interface SvgDecoder : NSObject <RCTImageDataDecoder, NativeSVGDecoderSpec>
#ifdef __cplusplus
extern "C" {
#endif

@end
#if TARGET_OS_OSX
NSImage *_Nullable RNBottomTabsDecodeSVGData(NSData *data);
#else
UIImage *_Nullable RNBottomTabsDecodeSVGData(NSData *data);
#endif

#ifdef __cplusplus
}
#endif

NS_ASSUME_NONNULL_END
35 changes: 4 additions & 31 deletions packages/react-native-bottom-tabs/ios/SVG/SvgDecoder.mm
Original file line number Diff line number Diff line change
@@ -1,38 +1,11 @@
#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
PlatformImage *RNBottomTabsDecodeSVGData(NSData *data)
{
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);
if (![CoreSVGWrapper isSVGData:data]) {
return nil;
}
return ^{};
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeSVGDecoderSpecJSI>(params);
return [CoreSVGWrapper.shared imageFromSVGData:data];
}

@end
44 changes: 44 additions & 0 deletions packages/react-native-bottom-tabs/ios/SVGIconLoader.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#if RN_BOTTOM_TABS_ENABLE_SVG
import Foundation
#if SWIFT_PACKAGE
import BottomTabsSVG
#endif

/// Loads SVG tab icons with the opt-in CoreSVG decoder.
enum SVGIconLoader {
static func isSVGRequest(_ request: URLRequest) -> Bool {
guard let url = request.url else { return false }
return url.pathExtension.lowercased() == "svg"
|| url.absoluteString.lowercased().hasPrefix("data:image/svg+xml")
}

static func decode(
with request: URLRequest,
completion: @escaping (PlatformImage?) -> Void
) {
guard let url = request.url else {
completion(nil)
return
}

let decode: (Data?) -> Void = { data in
guard let data else {
completion(nil)
return
}
completion(RNBottomTabsDecodeSVGData(data))
}

if url.scheme == "http" || url.scheme == "https" {
let task = URLSession.shared.dataTask(with: request) { data, _, _ in
decode(data)
}
task.resume()
} else {
DispatchQueue.global(qos: .userInitiated).async {
decode(try? Data(contentsOf: url))
}
}
}
}
#endif
Loading
Loading