Skip to content

Commit c1a0dd7

Browse files
authored
feat: add Tab api support on newer OS (#364)
* feat: create LegacyTabView implementation * feat: create NewTabView implementation * refactor: use different TabView depending on os version * feat: Create AnyTabView protocol * feat: Implement AnyTabView protocol in TabViews * refactor: Use AnyTabView as tabContent * feat: Create TabAppearModifier.swift * refactor: Use tabAppear modifier * fix: move LegacyTabView measureView * chore: remove tabViewCustomization * fix: remove props from AnyTabView as AnyTabView is internal * fix: revert tabContent to some View * fix: revert move LegacyTabView measureView * feat: create LegacyTabView implementation * feat: create NewTabView implementation * refactor: use different TabView depending on os version * feat: Create AnyTabView protocol * feat: Implement AnyTabView protocol in TabViews * refactor: Use AnyTabView as tabContent * feat: Create TabAppearModifier.swift * refactor: Use tabAppear modifier * fix: move LegacyTabView measureView * chore: remove tabViewCustomization * fix: remove props from AnyTabView as AnyTabView is internal * fix: revert tabContent to some View * fix: revert move LegacyTabView measureView * fix: working badges on new tabview * chore: run swiftlint * chore: remove nil tab role * fix: move .tag outside tabItem * Merge branch 'main' of https://github.com/padosoft/react-native-bottom-tabs *  fix: move .tab modifiers in LegacyTabView * refactor: update NewTabView .badge
1 parent 9e1c9c6 commit c1a0dd7

File tree

5 files changed

+363
-240
lines changed

5 files changed

+363
-240
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import SwiftUI
2+
3+
struct TabAppearContext {
4+
let index: Int
5+
let tabData: TabInfo
6+
let props: TabViewProps
7+
let updateTabBarAppearance: () -> Void
8+
let onSelect: (_ key: String) -> Void
9+
}
10+
11+
struct TabAppearModifier: ViewModifier {
12+
let context: TabAppearContext
13+
14+
func body(content: Content) -> some View {
15+
content.onAppear {
16+
#if !os(macOS)
17+
context.updateTabBarAppearance()
18+
#endif
19+
20+
#if os(iOS)
21+
if context.index >= 4, context.props.selectedPage != context.tabData.key {
22+
context.onSelect(context.tabData.key)
23+
}
24+
#endif
25+
}
26+
}
27+
}
28+
29+
extension View {
30+
func tabAppear(using context: TabAppearContext) -> some View {
31+
self.modifier(TabAppearModifier(context: context))
32+
}
33+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import SwiftUI
2+
3+
public protocol AnyTabView: View {
4+
var onLayout: (_ size: CGSize) -> Void { get }
5+
var onSelect: (_ key: String) -> Void { get }
6+
var updateTabBarAppearance: () -> Void { get }
7+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import SwiftUI
2+
3+
struct LegacyTabView: AnyTabView {
4+
@ObservedObject var props: TabViewProps
5+
6+
var onLayout: (CGSize) -> Void
7+
var onSelect: (String) -> Void
8+
var updateTabBarAppearance: () -> Void
9+
10+
@ViewBuilder
11+
var body: some View {
12+
TabView(selection: $props.selectedPage) {
13+
ForEach(props.children.indices, id: \.self) { index in
14+
renderTabItem(at: index)
15+
}
16+
.measureView { size in
17+
onLayout(size)
18+
}
19+
}
20+
.hideTabBar(props.tabBarHidden)
21+
}
22+
23+
@ViewBuilder
24+
private func renderTabItem(at index: Int) -> some View {
25+
if let tabData = props.items[safe: index] {
26+
let isFocused = props.selectedPage == tabData.key
27+
28+
if !tabData.hidden || isFocused {
29+
let icon = props.icons[index]
30+
let child = props.children[safe: index] ?? PlatformView()
31+
let context = TabAppearContext(
32+
index: index,
33+
tabData: tabData,
34+
props: props,
35+
updateTabBarAppearance: updateTabBarAppearance,
36+
onSelect: onSelect
37+
)
38+
39+
RepresentableView(view: child)
40+
.ignoresSafeArea(.container, edges: .all)
41+
.tabItem {
42+
TabItem(
43+
title: tabData.title,
44+
icon: icon,
45+
sfSymbol: tabData.sfSymbol,
46+
labeled: props.labeled
47+
)
48+
.accessibilityIdentifier(tabData.testID ?? "")
49+
}
50+
.tag(tabData.key)
51+
.tabBadge(tabData.badge)
52+
.tabAppear(using: context)
53+
}
54+
}
55+
}
56+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import SwiftUI
2+
3+
@available(iOS 18, macOS 15, visionOS 2, tvOS 18, *)
4+
struct NewTabView: AnyTabView {
5+
@ObservedObject var props: TabViewProps
6+
7+
var onLayout: (CGSize) -> Void
8+
var onSelect: (String) -> Void
9+
var updateTabBarAppearance: () -> Void
10+
11+
@ViewBuilder
12+
var body: some View {
13+
TabView(selection: $props.selectedPage) {
14+
ForEach(props.children.indices, id: \.self) { index in
15+
if let tabData = props.items[safe: index] {
16+
let isFocused = props.selectedPage == tabData.key
17+
18+
if !tabData.hidden || isFocused {
19+
let icon = props.icons[index]
20+
21+
let platformChild = props.children[safe: index] ?? PlatformView()
22+
let child = RepresentableView(view: platformChild)
23+
let context = TabAppearContext(
24+
index: index,
25+
tabData: tabData,
26+
props: props,
27+
updateTabBarAppearance: updateTabBarAppearance,
28+
onSelect: onSelect
29+
)
30+
31+
Tab(value: tabData.key) {
32+
child
33+
.ignoresSafeArea(.container, edges: .all)
34+
.tabAppear(using: context)
35+
} label: {
36+
TabItem(
37+
title: tabData.title,
38+
icon: icon,
39+
sfSymbol: tabData.sfSymbol,
40+
labeled: props.labeled
41+
)
42+
}
43+
.badge(tabData.badge.flatMap { !$0.isEmpty ? Text($0) : nil })
44+
.accessibilityIdentifier(tabData.testID ?? "")
45+
}
46+
}
47+
}
48+
}
49+
.measureView { size in
50+
onLayout(size)
51+
}
52+
.hideTabBar(props.tabBarHidden)
53+
}
54+
}

0 commit comments

Comments
 (0)