-
Notifications
You must be signed in to change notification settings - Fork 92
Expand file tree
/
Copy pathNewTabView.swift
More file actions
136 lines (119 loc) · 3.95 KB
/
NewTabView.swift
File metadata and controls
136 lines (119 loc) · 3.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import React
import SwiftUI
@available(iOS 18, macOS 15, visionOS 2, tvOS 18, *)
struct NewTabView: AnyTabView {
@ObservedObject var props: TabViewProps
var onLayout: (CGSize) -> Void
var onSelect: (String) -> Void
var updateTabBarAppearance: () -> Void
private var effectiveLayoutDirection: LayoutDirection {
let dir = props.layoutDirection ?? "locale"
if let mapped = ["rtl": LayoutDirection.rightToLeft,
"ltr": LayoutDirection.leftToRight][dir] {
return mapped
}
let system = UIView.userInterfaceLayoutDirection(for: .unspecified)
return system == .rightToLeft ? .rightToLeft : .leftToRight
}
@ViewBuilder
var body: some View {
TabView(selection: $props.selectedPage) {
ForEach(props.children) { child in
if let index = props.children.firstIndex(of: child),
let tabData = props.items[safe: index] {
let isFocused = props.selectedPage == tabData.key
if !tabData.hidden || isFocused {
let icon = props.icons[index]
let context = TabAppearContext(
index: index,
tabData: tabData,
props: props,
updateTabBarAppearance: updateTabBarAppearance,
onSelect: onSelect
)
Tab(value: tabData.key, role: tabData.role?.convert()) {
RepresentableView(view: child.view)
.ignoresSafeArea(.container, edges: .all)
.tabAppear(using: context)
.hideTabBar(props.tabBarHidden)
} label: {
TabItem(
title: tabData.title,
icon: icon,
sfSymbol: tabData.sfSymbol,
labeled: props.labeled
)
}
#if !os(tvOS)
.badge(tabData.badge.flatMap { !$0.isEmpty ? Text($0) : nil })
#endif
.accessibilityIdentifier(tabData.testID ?? "")
}
}
}
}
.environment(\.layoutDirection, effectiveLayoutDirection)
.measureView { size in
onLayout(size)
}
.modifier(ConditionalBottomAccessoryModifier(props: props))
}
}
struct ConditionalBottomAccessoryModifier: ViewModifier {
@ObservedObject var props: TabViewProps
private var bottomAccessoryView: PlatformView? {
props.children.first { child in
let className = String(describing: type(of: child.view))
return className == "RCTBottomAccessoryComponentView"
}?.view
}
func body(content: Content) -> some View {
#if compiler(>=6.1) && !os(macOS)
if #available(iOS 26.0, tvOS 26.0, visionOS 3.0, *), bottomAccessoryView != nil {
content
.tabViewBottomAccessory {
renderBottomAccessoryView()
}
} else {
content
}
#else
content
#endif
}
@ViewBuilder
private func renderBottomAccessoryView() -> some View {
#if compiler(>=6.1) && !os(macOS)
if let bottomAccessoryView {
if #available(iOS 26.0, *) {
BottomAccessoryRepresentableView(view: bottomAccessoryView)
}
}
#endif
}
}
#if compiler(>=6.1) && !os(macOS)
@available(iOS 26.0, *)
struct BottomAccessoryRepresentableView: PlatformViewRepresentable {
@Environment(\.tabViewBottomAccessoryPlacement) var tabViewBottomAccessoryPlacement
var view: PlatformView
func makeUIView(context: Context) -> PlatformView {
let wrapper = UIView()
wrapper.addSubview(view)
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
emitPlacementChanged(for: view)
return wrapper
}
func updateUIView(_ uiView: PlatformView, context: Context) {
if let subview = uiView.subviews.first {
subview.frame = uiView.bounds
}
emitPlacementChanged(for: view)
}
private func emitPlacementChanged(for uiView: PlatformView) {
if let contentView = uiView.value(forKey: "bottomAccessoryProvider") as? BottomAccessoryProvider {
contentView.emitPlacementChanged(tabViewBottomAccessoryPlacement)
}
}
}
#endif