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
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ class RCTTabViewManager(context: ReactApplicationContext) :
view.setIcons(value)
}

override fun setFocusedIcons(view: ReactBottomNavigationView?, value: ReadableArray?) {
}

override fun setLabeled(view: ReactBottomNavigationView?, value: Boolean) {
if (view != null)
view.setLabeled(value)
Expand Down
12 changes: 12 additions & 0 deletions packages/react-native-bottom-tabs/ios/RCTTabViewComponentView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
return lhs.key == rhs.key &&
lhs.title == rhs.title &&
lhs.sfSymbol == rhs.sfSymbol &&
lhs.focusedSfSymbol == rhs.focusedSfSymbol &&
lhs.badge == rhs.badge &&
lhs.activeTintColor == rhs.activeTintColor &&
lhs.iconRenderingMode == rhs.iconRenderingMode &&
Expand Down Expand Up @@ -117,6 +118,16 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
_tabViewProvider.icons = iconsArray;
}

if (oldViewProps.focusedIcons != newViewProps.focusedIcons) {
auto focusedIconsArray = [[NSMutableArray alloc] init];
for (auto &source: newViewProps.focusedIcons) {
auto imageSource = [[RCTImageSource alloc] initWithURLRequest:NSURLRequestFromImageSource(source) size:CGSizeMake(source.size.width, source.size.height) scale:source.scale];
[focusedIconsArray addObject:imageSource];
}

_tabViewProvider.focusedIcons = focusedIconsArray;
}

if (oldViewProps.sidebarAdaptable != newViewProps.sidebarAdaptable) {
_tabViewProvider.sidebarAdaptable = newViewProps.sidebarAdaptable;
}
Expand Down Expand Up @@ -197,6 +208,7 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
title:RCTNSStringFromString(item.title)
badge:RCTNSStringFromStringNilIfEmpty(item.badge)
sfSymbol:RCTNSStringFromStringNilIfEmpty(item.sfSymbol)
focusedSfSymbol:RCTNSStringFromStringNilIfEmpty(item.focusedSfSymbol)
activeTintColor:RCTUIColorFromSharedColor(item.activeTintColor)
iconRenderingMode:RCTNSStringFromStringNilIfEmpty(item.iconRenderingMode)
hidden:item.hidden
Expand Down
106 changes: 49 additions & 57 deletions packages/react-native-bottom-tabs/ios/TabViewImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ struct TabViewImpl: View {
#else
tabBar = tabController.tabBar
updateTabBarAppearance(props: props, tabBar: tabController.tabBar)
updateExperimentalBakedTintColors(props: props, tabBar: tabController.tabBar)
updateTabBarItemImages(props: props, tabBar: tabController.tabBar)
if !props.tabBarHidden {
onTabBarMeasured(
Int(tabController.tabBar.frame.size.height)
Expand Down Expand Up @@ -115,17 +115,16 @@ struct TabViewImpl: View {
}

#if !os(macOS)
private func updateExperimentalBakedTintColors(props: TabViewProps, tabBar: UITabBar?) {
guard shouldUseExperimentalBakedTintColors(props: props),
let tabBar,
private func updateTabBarItemImages(props: TabViewProps, tabBar: UITabBar?) {
guard let tabBar,
let items = tabBar.items
else { return }

configureExperimentalBakedTintColors(items: items, props: props)
configureTabBarItemImages(items: items, props: props)

DispatchQueue.main.async { [weak tabBar] in
guard let tabBar, let items = tabBar.items else { return }
configureExperimentalBakedTintColors(items: items, props: props)
configureTabBarItemImages(items: items, props: props)
}
}

Expand Down Expand Up @@ -220,7 +219,7 @@ struct TabViewImpl: View {
}
}

private func configureExperimentalBakedTintColors(items: [UITabBarItem], props: TabViewProps) {
private func configureTabBarItemImages(items: [UITabBarItem], props: TabViewProps) {
for (tabBarIndex, item) in items.enumerated() {
guard let tabData = props.filteredItems[safe: tabBarIndex],
let itemIndex = props.items.firstIndex(where: { $0.key == tabData.key })
Expand All @@ -229,13 +228,17 @@ struct TabViewImpl: View {
let tabActiveColor = tabData.activeTintColor ?? props.activeTintColor
let assetIcon = props.icons[itemIndex]
let icon = assetIcon ?? makeSFSymbolImage(named: tabData.sfSymbol)
let focusedIcon =
props.focusedIcons[itemIndex] ?? makeSFSymbolImage(named: tabData.focusedSfSymbol) ?? icon
let preservesOriginalIconColors = preservesOriginalIconColors(tabData: tabData)
let useBakedTintColors = shouldUseExperimentalBakedTintColors(props: props)
let shouldRenderLabelIntoImage =
props.hasCustomTintColors && props.labeled && tabData.role != .search && icon != nil

item.accessibilityLabel = tabData.title

if shouldRenderLabelIntoImage, let icon {
if useBakedTintColors, shouldRenderLabelIntoImage, let icon {
let selectedIcon = focusedIcon ?? icon
item.title = ""
item.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: 100)
item.image = makeTabBarItemImage(
Expand All @@ -246,7 +249,7 @@ struct TabViewImpl: View {
props: props
)
item.selectedImage = makeTabBarItemImage(
icon: icon,
icon: selectedIcon,
title: tabData.title,
color: tabActiveColor,
preservesOriginalIconColors: preservesOriginalIconColors,
Expand All @@ -259,20 +262,19 @@ struct TabViewImpl: View {
item.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: 0)

if let icon {
if preservesOriginalIconColors {
let originalIcon = icon.withRenderingMode(.alwaysOriginal)
item.image = originalIcon
item.selectedImage = originalIcon
} else {
item.image =
props.inactiveTintColor.map {
icon.withTintColor($0, renderingMode: .alwaysOriginal)
} ?? icon
item.selectedImage =
tabActiveColor.map {
icon.withTintColor($0, renderingMode: .alwaysOriginal)
} ?? icon
}
let selectedIcon = focusedIcon ?? icon
item.image = renderTabBarIcon(
icon,
color: props.inactiveTintColor,
preservesOriginalIconColors: preservesOriginalIconColors,
forceTintColor: useBakedTintColors
)
item.selectedImage = renderTabBarIcon(
selectedIcon,
color: tabActiveColor,
preservesOriginalIconColors: preservesOriginalIconColors,
forceTintColor: useBakedTintColors
)
}

item.setTitleTextAttributes(
Expand All @@ -287,31 +289,25 @@ struct TabViewImpl: View {
}
}

private func resetExperimentalBakedTintColors(props: TabViewProps, tabBar: UITabBar?) {
guard let tabBar,
let items = tabBar.items
else { return }

for (tabBarIndex, item) in items.enumerated() {
guard let tabData = props.filteredItems[safe: tabBarIndex],
let itemIndex = props.items.firstIndex(where: { $0.key == tabData.key })
else { continue }
private func preservesOriginalIconColors(tabData: TabInfo) -> Bool {
tabData.iconRenderingMode == "original"
}

let assetIcon = props.icons[itemIndex]
let icon = assetIcon ?? makeSFSymbolImage(named: tabData.sfSymbol)
let originalIcon = icon.map {
preservesOriginalIconColors(tabData: tabData) ? $0.withRenderingMode(.alwaysOriginal) : $0
}
private func renderTabBarIcon(
_ icon: UIImage,
color: UIColor?,
preservesOriginalIconColors: Bool,
forceTintColor: Bool
) -> UIImage {
if preservesOriginalIconColors {
return icon.withRenderingMode(.alwaysOriginal)
}

item.title = props.labeled ? tabData.title : nil
item.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: 0)
item.image = originalIcon
item.selectedImage = originalIcon
guard forceTintColor, let color else {
return icon
}
}

private func preservesOriginalIconColors(tabData: TabInfo) -> Bool {
tabData.iconRenderingMode == "original"
return icon.withTintColor(color, renderingMode: .alwaysOriginal)
}

private func makeSFSymbolImage(named sfSymbol: String?) -> UIImage? {
Expand Down Expand Up @@ -473,40 +469,36 @@ extension View {
}
.onChange(of: props.inactiveTintColor) { _ in
updateTabBarAppearance(props: props, tabBar: tabBar)
updateExperimentalBakedTintColors(props: props, tabBar: tabBar)
updateTabBarItemImages(props: props, tabBar: tabBar)
}
.onChange(of: props.activeTintColor) { _ in
updateTabBarAppearance(props: props, tabBar: tabBar)
updateExperimentalBakedTintColors(props: props, tabBar: tabBar)
updateTabBarItemImages(props: props, tabBar: tabBar)
}
.onChange(of: props.selectedActiveTintColor) { newValue in
tabBar?.tintColor = newValue
}
.onChange(of: props.iconsRevision) { _ in
updateExperimentalBakedTintColors(props: props, tabBar: tabBar)
updateTabBarItemImages(props: props, tabBar: tabBar)
}
.onChange(of: props.labeled) { _ in
updateExperimentalBakedTintColors(props: props, tabBar: tabBar)
updateTabBarItemImages(props: props, tabBar: tabBar)
}
.onChange(of: props.fontSize) { _ in
updateTabBarAppearance(props: props, tabBar: tabBar)
updateExperimentalBakedTintColors(props: props, tabBar: tabBar)
updateTabBarItemImages(props: props, tabBar: tabBar)
}
.onChange(of: props.fontFamily) { _ in
updateTabBarAppearance(props: props, tabBar: tabBar)
updateExperimentalBakedTintColors(props: props, tabBar: tabBar)
updateTabBarItemImages(props: props, tabBar: tabBar)
}
.onChange(of: props.fontWeight) { _ in
updateTabBarAppearance(props: props, tabBar: tabBar)
updateExperimentalBakedTintColors(props: props, tabBar: tabBar)
updateTabBarItemImages(props: props, tabBar: tabBar)
}
.onChange(of: props.experimentalBakedTintColors) { newValue in
.onChange(of: props.experimentalBakedTintColors) { _ in
updateTabBarAppearance(props: props, tabBar: tabBar)
if newValue {
updateExperimentalBakedTintColors(props: props, tabBar: tabBar)
} else {
resetExperimentalBakedTintColors(props: props, tabBar: tabBar)
}
updateTabBarItemImages(props: props, tabBar: tabBar)
}
.onChange(of: props.tabBarHidden) { newValue in
tabBar?.isHidden = newValue
Expand Down
1 change: 1 addition & 0 deletions packages/react-native-bottom-tabs/ios/TabViewProps.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class TabViewProps: ObservableObject {
@Published var items: [TabInfo] = []
@Published var selectedPage: String?
@Published var icons: [Int: PlatformImage] = [:]
@Published var focusedIcons: [Int: PlatformImage] = [:]
@Published var iconsRevision: Int = 0
@Published var sidebarAdaptable: Bool?
@Published var labeled: Bool = false
Expand Down
37 changes: 30 additions & 7 deletions packages/react-native-bottom-tabs/ios/TabViewProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
public let title: String
public let badge: String?
public let sfSymbol: String
public let focusedSfSymbol: String?
public let activeTintColor: PlatformColor?
public let iconRenderingMode: String?
public let hidden: Bool
Expand All @@ -20,6 +21,7 @@
title: String,
badge: String?,
sfSymbol: String,
focusedSfSymbol: String?,
activeTintColor: PlatformColor?,
iconRenderingMode: String?,
hidden: Bool,
Expand All @@ -31,6 +33,7 @@
self.title = title
self.badge = badge
self.sfSymbol = sfSymbol
self.focusedSfSymbol = focusedSfSymbol
self.activeTintColor = activeTintColor
self.iconRenderingMode = iconRenderingMode
self.hidden = hidden
Expand All @@ -42,10 +45,10 @@
}

@objc public protocol TabViewProviderDelegate {
func onPageSelected(key: String, reactTag: NSNumber?)

Check warning on line 48 in packages/react-native-bottom-tabs/ios/TabViewProvider.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Legacy Objective-C Reference Type Violation: Prefer Swift value types to bridged Objective-C reference types (legacy_objc_type)
func onLongPress(key: String, reactTag: NSNumber?)

Check warning on line 49 in packages/react-native-bottom-tabs/ios/TabViewProvider.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Legacy Objective-C Reference Type Violation: Prefer Swift value types to bridged Objective-C reference types (legacy_objc_type)
func onTabBarMeasured(height: Int, reactTag: NSNumber?)

Check warning on line 50 in packages/react-native-bottom-tabs/ios/TabViewProvider.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Legacy Objective-C Reference Type Violation: Prefer Swift value types to bridged Objective-C reference types (legacy_objc_type)
func onLayout(size: CGSize, reactTag: NSNumber?)

Check warning on line 51 in packages/react-native-bottom-tabs/ios/TabViewProvider.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Legacy Objective-C Reference Type Violation: Prefer Swift value types to bridged Objective-C reference types (legacy_objc_type)
}

@objc public class TabViewProvider: PlatformView {
Expand All @@ -62,9 +65,15 @@
@objc var onTabBarMeasured: RCTDirectEventBlock?
@objc var onNativeLayout: RCTDirectEventBlock?

@objc public var icons: NSArray? {

Check warning on line 68 in packages/react-native-bottom-tabs/ios/TabViewProvider.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Legacy Objective-C Reference Type Violation: Prefer Swift value types to bridged Objective-C reference types (legacy_objc_type)
didSet {
loadIcons(icons)
loadIcons(icons, focused: false)
}
}

@objc public var focusedIcons: NSArray? {

Check warning on line 74 in packages/react-native-bottom-tabs/ios/TabViewProvider.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Legacy Objective-C Reference Type Violation: Prefer Swift value types to bridged Objective-C reference types (legacy_objc_type)
didSet {
loadIcons(focusedIcons, focused: true)
}
}

Expand All @@ -86,7 +95,7 @@
}
}

@objc public var selectedPage: NSString? {

Check warning on line 98 in packages/react-native-bottom-tabs/ios/TabViewProvider.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Legacy Objective-C Reference Type Violation: Prefer Swift value types to bridged Objective-C reference types (legacy_objc_type)
didSet {
props.selectedPage = selectedPage as? String
}
Expand All @@ -98,7 +107,7 @@
}
}

@objc public var layoutDirection: NSString? {

Check warning on line 110 in packages/react-native-bottom-tabs/ios/TabViewProvider.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Legacy Objective-C Reference Type Violation: Prefer Swift value types to bridged Objective-C reference types (legacy_objc_type)
didSet {
props.layoutDirection = layoutDirection as? String
}
Expand Down Expand Up @@ -182,7 +191,8 @@

@objc public func setImageLoader(_ imageLoader: RCTImageLoader) {
self.imageLoader = imageLoader
loadIcons(icons)
loadIcons(icons, focused: false)
loadIcons(focusedIcons, focused: true)
}

override public func didUpdateReactSubviews() {
Expand Down Expand Up @@ -246,7 +256,7 @@
props.children.remove(at: index)
}

private func loadIcons(_ icons: NSArray?) {
private func loadIcons(_ icons: NSArray?, focused: Bool) {

Check warning on line 259 in packages/react-native-bottom-tabs/ios/TabViewProvider.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Cyclomatic Complexity Violation: Function should have complexity 10 or less; currently complexity is 11 (cyclomatic_complexity)
guard let imageLoader else { return }

// TODO: Diff the arrays and update only changed items.
Expand All @@ -273,13 +283,26 @@
let icon = image.resizeImageTo(size: iconSize)
#if os(iOS)
if props.experimentalBakedTintColors {
props.icons[index] = icon?.withRenderingMode(.alwaysTemplate)
props.iconsRevision += 1
if focused {
props.focusedIcons[index] = icon?.withRenderingMode(.alwaysTemplate)
} else {
props.icons[index] = icon?.withRenderingMode(.alwaysTemplate)
}
} else {
props.icons[index] = icon
if focused {
props.focusedIcons[index] = icon
} else {
props.icons[index] = icon
}
}
props.iconsRevision += 1
#else
props.icons[index] = icon
if focused {
props.focusedIcons[index] = icon
} else {
props.icons[index] = icon
}
props.iconsRevision += 1
#endif
}
})
Expand Down
Loading
Loading