diff --git a/README.md b/README.md
index 10a0255..1c8c182 100644
--- a/README.md
+++ b/README.md
@@ -191,11 +191,51 @@ Background color color of the control. (iOS 13+ only)
### `values`
-The labels for the control's segment buttons, in order.
+The labels for the control's segment buttons, in order. Supports strings, image sources (`require()`), and SF Symbol objects (iOS 13+ only).
-| Type | Required |
-| ------- | -------- |
-| (string | number | Image)[] | No |
+| Type | Required |
+| --------------------------------- | -------- |
+| `(string \| number \| SFSymbol)[]` | No |
+
+#### Using SF Symbols (iOS 13+ only)
+
+You can use [SF Symbols](https://developer.apple.com/sf-symbols/) as segment values by passing an object with a `systemImage` key:
+
+```javascript
+
+```
+
+SF Symbol objects support the following properties:
+
+| Property | Type | Default | Description |
+| ------------- | -------- | ----------- | ---------------------------------------------------- |
+| `systemImage` | string | *required* | The SF Symbol name (e.g., `'star.fill'`, `'heart'`) |
+| `fontSize` | number | `19` | The point size of the symbol |
+| `weight` | string | `'regular'` | Symbol weight: `'ultraLight'`, `'thin'`, `'light'`, `'regular'`, `'medium'`, `'semibold'`, `'bold'`, `'heavy'`, `'black'` |
+
+You can also mix text and SF Symbols:
+
+```javascript
+
+```
+
+> **Note:** SF Symbols are only supported on iOS. On Android and Web, segments with SF Symbol values will render as empty.
+
+---
### `appearance`
diff --git a/example/App.js b/example/App.js
index d1ac48b..5319443 100644
--- a/example/App.js
+++ b/example/App.js
@@ -7,7 +7,7 @@
import SegmentedControl from '..';
import React, {useEffect, useState} from 'react';
-import {ScrollView, StyleSheet, Text, View, useColorScheme} from 'react-native';
+import {ScrollView, StyleSheet, Text, View, Platform, useColorScheme} from 'react-native';
const App = () => {
const colorScheme = useColorScheme();
@@ -53,6 +53,36 @@ const App = () => {
]}
/>
+ {Platform.OS === 'ios' && (
+
+
+ Segmented controls can have SF Symbols (iOS only)
+
+
+
+ )}
+ {Platform.OS === 'ios' && (
+
+
+ SF Symbols can be mixed with text
+
+
+
+ )}
Segmented controls can have pre-selected values
diff --git a/index.d.ts b/index.d.ts
index aa8d5f5..8d81819 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -12,6 +12,41 @@ import {
type Constructor = new (...args: any[]) => T;
+export type SFSymbolWeight =
+ | 'ultraLight'
+ | 'thin'
+ | 'light'
+ | 'regular'
+ | 'medium'
+ | 'semibold'
+ | 'bold'
+ | 'heavy'
+ | 'black';
+
+/**
+ * Represents an SF Symbol configuration (iOS 13+ only).
+ * SF Symbols are Apple's built-in icon system providing thousands of
+ * configurable symbols that automatically align with text and adapt to
+ * the system appearance.
+ */
+export type SFSymbol = {
+ /**
+ * The name of the SF Symbol (e.g., 'star.fill', 'heart', 'gear').
+ * See https://developer.apple.com/sf-symbols/ for the full list.
+ */
+ systemImage: string;
+ /**
+ * The point size of the symbol. Default is 19.
+ */
+ fontSize?: number;
+ /**
+ * The weight of the symbol. Default is 'regular'.
+ */
+ weight?: SFSymbolWeight;
+};
+
+export type SegmentValue = string | number | SFSymbol;
+
export interface NativeSegmentedControlIOSChangeEvent extends TargetedEvent {
value: string;
selectedSegmentIndex: number;
@@ -70,7 +105,7 @@ export interface SegmentedControlProps extends ViewProps {
/**
* Callback that is called when the user taps a segment; passes the segment's value as an argument
*/
- onValueChange?: (value: string) => void;
+ onValueChange?: (value: SegmentValue) => void;
/**
* The index in props.values of the segment to be (pre)selected.
@@ -90,8 +125,9 @@ export interface SegmentedControlProps extends ViewProps {
/**
* The labels for the control's segment buttons, in order.
+ * Supports strings, image sources (require()), and SF Symbol objects (iOS 13+ only).
*/
- values?: string[];
+ values?: SegmentValue[];
/**
* (iOS 13+ only)
diff --git a/ios/RNCSegmentedControl.m b/ios/RNCSegmentedControl.m
index 4a1bfea..c348099 100644
--- a/ios/RNCSegmentedControl.m
+++ b/ios/RNCSegmentedControl.m
@@ -24,20 +24,80 @@ - (instancetype)initWithFrame:(CGRect)frame {
}
- (void)setValues:(NSArray *)values {
- [self removeAllSegments];
- for (id segment in values) {
- if ([segment isKindOfClass:[NSMutableDictionary class]]){
- UIImage *image = [[RCTConvert UIImage:segment] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
- [self insertSegmentWithImage:image
- atIndex:self.numberOfSegments
- animated:NO];
- } else {
- [self insertSegmentWithTitle:(NSString *)segment
- atIndex:self.numberOfSegments
- animated:NO];
- }
- }
- super.selectedSegmentIndex = _selectedIndex;
+ [self removeAllSegments];
+
+ for (id segment in values) {
+ if ([segment isKindOfClass:[NSDictionary class]]) {
+
+ NSDictionary *dict = (NSDictionary *)segment;
+ UIImage *image = nil;
+
+ // 🔹 CASE 1: SF Symbol
+ if (dict[@"systemImage"]) {
+
+#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_13_0) && \
+ __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
+ if (@available(iOS 13.0, *)) {
+
+ NSString *systemName = dict[@"systemImage"];
+
+ CGFloat fontSize = dict[@"fontSize"] ? [dict[@"fontSize"] floatValue] : 19.0;
+
+ UIImageSymbolWeight weight = UIImageSymbolWeightRegular;
+
+ if (dict[@"weight"]) {
+ NSString *weightString = dict[@"weight"];
+
+ if ([weightString isEqualToString:@"ultraLight"])
+ weight = UIImageSymbolWeightUltraLight;
+ else if ([weightString isEqualToString:@"thin"])
+ weight = UIImageSymbolWeightThin;
+ else if ([weightString isEqualToString:@"light"])
+ weight = UIImageSymbolWeightLight;
+ else if ([weightString isEqualToString:@"medium"])
+ weight = UIImageSymbolWeightMedium;
+ else if ([weightString isEqualToString:@"semibold"])
+ weight = UIImageSymbolWeightSemibold;
+ else if ([weightString isEqualToString:@"bold"])
+ weight = UIImageSymbolWeightBold;
+ else if ([weightString isEqualToString:@"heavy"])
+ weight = UIImageSymbolWeightHeavy;
+ else if ([weightString isEqualToString:@"black"])
+ weight = UIImageSymbolWeightBlack;
+ }
+
+ UIImageSymbolConfiguration *config =
+ [UIImageSymbolConfiguration configurationWithPointSize:fontSize
+ weight:weight];
+
+ image = [[UIImage systemImageNamed:systemName]
+ imageByApplyingSymbolConfiguration:config];
+
+ image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
+ }
+#endif
+ }
+ else {
+ image = [[RCTConvert UIImage:segment]
+ imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
+ }
+
+ if (image) {
+ [self insertSegmentWithImage:image
+ atIndex:self.numberOfSegments
+ animated:NO];
+ }
+ }
+
+ else if ([segment isKindOfClass:[NSString class]]) {
+
+ [self insertSegmentWithTitle:(NSString *)segment
+ atIndex:self.numberOfSegments
+ animated:NO];
+ }
+ }
+
+ super.selectedSegmentIndex = _selectedIndex;
}
- (void)setSelectedIndex:(NSInteger)selectedIndex {
diff --git a/js/SegmentedControl.ios.js b/js/SegmentedControl.ios.js
index 282fb2f..6b78a3e 100644
--- a/js/SegmentedControl.ios.js
+++ b/js/SegmentedControl.ios.js
@@ -84,9 +84,17 @@ class SegmentedControlIOS extends React.Component {
}
: undefined
}
- values={values.map((val) =>
- typeof val === 'string' ? val : Image.resolveAssetSource(val),
- )}
+ values={values.map((val) => {
+ if (typeof val === 'string') {
+ return val;
+ }
+ // SF Symbol objects have a systemImage key — pass through as-is
+ if (typeof val === 'object' && val !== null && val.systemImage) {
+ return val;
+ }
+ // Image sources (require() numbers or objects) need resolution
+ return Image.resolveAssetSource(val);
+ })}
{...props}
ref={forwardedRef}
style={[styles.segmentedControl, this.props.style]}
diff --git a/js/SegmentedControlTab.js b/js/SegmentedControlTab.js
index 31f18fa..fffc966 100644
--- a/js/SegmentedControlTab.js
+++ b/js/SegmentedControlTab.js
@@ -16,10 +16,10 @@ import {
Platform,
} from 'react-native';
-import type {FontStyle, ViewStyle} from './types';
+import type {FontStyle, ViewStyle, SFSymbol} from './types';
type Props = $ReadOnly<{|
- value: string | number | Object,
+ value: string | number | SFSymbol,
tintColor?: ?string,
onSelect: () => void,
selected: boolean,
@@ -103,7 +103,10 @@ export const SegmentedControlTab = ({
ios: typeof value === 'string' ? value : testID,
})}>
- {typeof value === 'number' || typeof value === 'object' ? (
+ {typeof value === 'object' && value !== null && value.systemImage ? (
+ // SF Symbols are iOS-only; on Android/Web render nothing
+ null
+ ) : typeof value === 'number' || typeof value === 'object' ? (
) : isBase64(value) ? (
diff --git a/js/types.js b/js/types.js
index 9342605..fd2f36d 100644
--- a/js/types.js
+++ b/js/types.js
@@ -16,6 +16,37 @@ export type Event = SyntheticEvent<
export type ViewStyle = ViewStyleProp;
+export type SFSymbolWeight =
+ | 'ultraLight'
+ | 'thin'
+ | 'light'
+ | 'regular'
+ | 'medium'
+ | 'semibold'
+ | 'bold'
+ | 'heavy'
+ | 'black';
+
+/**
+ * Represents an SF Symbol configuration (iOS 13+ only).
+ */
+export type SFSymbol = $ReadOnly<{|
+ /**
+ * The name of the SF Symbol (e.g., 'star.fill', 'heart', 'gear').
+ */
+ systemImage: string,
+ /**
+ * The point size of the symbol. Default is 19.
+ */
+ fontSize?: number,
+ /**
+ * The weight of the symbol. Default is 'regular'.
+ */
+ weight?: SFSymbolWeight,
+|}>;
+
+export type SegmentValue = string | number | SFSymbol;
+
export type FontStyle = $ReadOnly<{|
/**
* Font Color of Segmented Control
@@ -51,7 +82,7 @@ export type SegmentedControlProps = $ReadOnly<{|
/**
* The labels for the control's segment buttons, in order.
*/
- values: $ReadOnlyArray,
+ values: $ReadOnlyArray,
/**
* The index in `props.values` of the segment to be (pre)selected.
*/
@@ -60,7 +91,7 @@ export type SegmentedControlProps = $ReadOnly<{|
* Callback that is called when the user taps a segment;
* passes the segment's value as an argument
*/
- onValueChange?: ?(value: string | number | Object) => mixed,
+ onValueChange?: ?(value: SegmentValue) => mixed,
/**
* Callback that is called when the user taps a segment;
* passes the event as an argument