-
Notifications
You must be signed in to change notification settings - Fork 75
Open
Description
Problem Description
When I switch tabs in TabView, the navigation options from the previous session persist.
I do not need to set independent navigation states across different tabs, as I require the navigation to be in a fresh state when switching tabs.
I don't need to wrap my TabView inside a FlowStack, because if I do, I won't be able to hide the Tab when navigating via push.
Expected Effect
When using FlowStacks in conjunction, only one navigation instance is required. When switching TabView options, only the content of the current view is displayed (clearing the residual state from the previous navigation), and the TabView is hidden when performing a navigation jump.
Screenshots attached
Related code
import FlowStacks
import SwiftUI
// Define route enum
enum Route: Hashable {
case home
case fruits
case profile
case settings
case purchase(amount: Double)
case fruitList
case notifications
case fruitDetails(id: Int)
}
// Main tab enum
enum TabItem: Int, CaseIterable {
case home = 0
case fruits = 1
case profile = 2
case settings = 3
var title: String {
switch self {
case .home: return "Home"
case .fruits: return "Fruits"
case .profile: return "Profile"
case .settings: return "Settings"
}
}
var icon: String {
switch self {
case .home: return "house"
case .fruits: return "apple.logo"
case .profile: return "person"
case .settings: return "gear"
}
}
}
// Main application view
struct MainTabView: View {
@State private var selectedTab: TabItem = .home
// Create independent navigation path for each tab
@State private var flowPath = FlowPath()
var body: some View {
FlowStack($flowPath, withNavigation: true) {
TabView(selection: $selectedTab) {
// Home tab
HomeView()
.tabItem {
Label(TabItem.home.title, systemImage: TabItem.home.icon)
}
.tag(TabItem.home)
// Fruits tab
FruitsTabView()
.tabItem {
Label(TabItem.fruits.title, systemImage: TabItem.fruits.icon)
}
.tag(TabItem.fruits)
// Profile tab
ProfileView()
.tabItem {
Label(TabItem.profile.title, systemImage: TabItem.profile.icon)
}
.tag(TabItem.profile)
// Settings tab
SettingsView()
.tabItem {
Label(TabItem.settings.title, systemImage: TabItem.settings.icon)
}
.tag(TabItem.settings)
}
.flowDestination(for: Route.self) { route in
switch route {
case .purchase(let amount):
PurchaseView(amount: amount)
case .settings:
SettingsView()
case .fruitList:
FruitListView()
case .fruitDetails(let id):
FruitDetailView(id: id)
case .notifications:
NotificationsView()
default:
EmptyView()
}
}
.onChange(of: flowPath) { oldValue, newValue in
print(oldValue, newValue)
}
}
}
}
// Home view
struct HomeView: View {
@EnvironmentObject var navigator: FlowPathNavigator
var body: some View {
VStack(spacing: 20) {
Text("Fruit World")
.font(.largeTitle)
Button("Browse Fruits") {
navigator.push(Route.fruits)
}
.buttonStyle(.borderedProminent)
Button("Fruit Catalog") {
navigator.push(Route.fruitList)
}
.buttonStyle(.borderedProminent)
Button("Featured Fruit") {
navigator.push(Route.fruitDetails(id: Int.random(in: 1...100)))
}
.buttonStyle(.borderedProminent)
}
.padding()
.navigationTitle("Fruit World")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {}) {
Image(systemName: "cart")
}
}
}
}
}
// Fruits tab main view
struct FruitsTabView: View {
@EnvironmentObject var navigator: FlowPathNavigator
var body: some View {
VStack(spacing: 20) {
Text("Fruit Selection")
.font(.largeTitle)
List {
let range = 1...10
ForEach(range, id: \.self) { index in
Button(action: {
let price = Double(index) * 2.99
navigator.push(Route.purchase(amount: price))
}) {
HStack {
Image(systemName: getFruitIcon(index))
.font(.title2)
.foregroundColor(getFruitColor(index))
VStack(alignment: .leading) {
Text(getFruitName(index))
.font(.headline)
Text("Fresh and organic")
.font(.subheadline)
.foregroundColor(.gray)
}
Spacer()
let price = Double(index) * 2.99
let formattedPrice = String(format: "%.2f", price)
Text("$\(formattedPrice)")
.font(.caption)
.foregroundColor(.gray)
}
}
}
}
}
.navigationTitle("Fruits")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {}) {
Image(systemName: "slider.horizontal.3")
}
}
}
}
// Helper functions to generate fruit data
// 水果数据集合
private let fruits = ["Apple", "Banana", "Orange", "Strawberry", "Pineapple", "Mango", "Grape", "Watermelon", "Kiwi", "Peach"]
private let icons = ["apple", "leaf", "flame.fill", "heart.fill", "star.fill", "sun.max.fill", "moon.fill", "cloud.fill", "bolt.fill", "drop.fill"]
private let colors: [Color] = [.red, .green, .orange, .pink, .yellow, .purple, .blue, .mint, .brown, .indigo]
func getFruitName(_ index: Int) -> String {
let position = index % fruits.count
return fruits[position]
}
func getFruitIcon(_ index: Int) -> String {
let position = index % icons.count
return icons[position]
}
func getFruitColor(_ index: Int) -> Color {
let position = index % colors.count
return colors[position]
}
}
// Fruit detail view
struct FruitView: View {
@EnvironmentObject var navigator: FlowPathNavigator
var fruitName: String
var body: some View {
VStack(spacing: 20) {
Text(fruitName)
.font(.largeTitle)
Button("Purchase") {
navigator.push(Route.purchase(amount: 9.99))
}
.buttonStyle(.borderedProminent)
Button("Return to Home") {
navigator.goBackToRoot()
}
.buttonStyle(.bordered)
}
.padding()
.navigationTitle(fruitName)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {}) {
Image(systemName: "heart")
}
}
}
}
}
// Purchase view
struct PurchaseView: View {
@EnvironmentObject var navigator: FlowPathNavigator
let amount: Double
var body: some View {
VStack(spacing: 20) {
Text("Checkout")
.font(.largeTitle)
Text("Total: $\(amount, specifier: "%.2f")")
.font(.title2)
Button("Complete Purchase") {
navigator.goBackToRoot()
}
.buttonStyle(.borderedProminent)
Button("Go Back") {
navigator.goBack()
}
.buttonStyle(.bordered)
}
.padding()
.navigationTitle("Checkout")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {}) {
Image(systemName: "creditcard")
}
}
}
}
}
// Fruit list view
struct FruitListView: View {
@EnvironmentObject var navigator: FlowPathNavigator
let fruits = ["Apple", "Banana", "Orange", "Pear", "Strawberry"]
let prices = [1.99, 0.99, 2.49, 1.79, 3.99]
var body: some View {
VStack {
List(0..<5) { item in
Button("\(fruits[item]) - $\(String(format: "%.2f", prices[item]))") {
navigator.push(Route.purchase(amount: prices[item]))
}
}
}
.navigationTitle("Fruit Catalog")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {}) {
Image(systemName: "arrow.up.arrow.down")
}
}
}
}
}
// Fruit detail view
struct FruitDetailView: View {
@EnvironmentObject var navigator: FlowPathNavigator
let id: Int
var fruitName: String {
let fruits = ["Apple", "Banana", "Orange", "Pear", "Strawberry", "Blueberry", "Pineapple", "Mango", "Kiwi", "Peach"]
return fruits[id % fruits.count]
}
var body: some View {
VStack(spacing: 20) {
Image(systemName: "leaf")
.font(.system(size: 80))
.foregroundColor(.green)
Text(fruitName)
.font(.largeTitle)
Text("This \(fruitName) is fresh and organic. Rich in vitamins and minerals.")
.font(.body)
.multilineTextAlignment(.center)
.padding()
Button("Back") {
navigator.goBack()
}
.buttonStyle(.borderedProminent)
}
.padding()
.navigationTitle(fruitName)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {}) {
Image(systemName: "cart.badge.plus")
}
}
}
}
}
// Profile view
struct ProfileView: View {
@EnvironmentObject var navigator: FlowPathNavigator
var body: some View {
VStack(spacing: 20) {
Image(systemName: "person.circle.fill")
.font(.system(size: 100))
.foregroundColor(.blue)
Text("Fruit Lover")
.font(.title)
Text("fruit.lover@example.com")
.foregroundColor(.gray)
Divider()
Button("Account Settings") {
navigator.push(Route.settings)
}
.buttonStyle(.borderedProminent)
Button("My Orders") {
navigator.push(Route.purchase(amount: 199.99))
}
.buttonStyle(.bordered)
}
.padding()
.navigationTitle("Profile")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {}) {
Image(systemName: "pencil")
}
}
}
}
}
// Settings view
struct SettingsView: View {
@EnvironmentObject var navigator: FlowPathNavigator
var body: some View {
List {
Section(header: Text("Account")) {
NavigationLink("Personal Information", destination: Text("Personal Information Settings"))
NavigationLink("Privacy Settings", destination: Text("Privacy Settings Page"))
}
Section(header: Text("Notifications")) {
Button("Notification Settings") {
navigator.push(Route.notifications)
}
}
Section(header: Text("About")) {
NavigationLink("App Version", destination: Text("Version 1.0.0"))
NavigationLink("Terms of Use", destination: Text("Terms of Use Content"))
NavigationLink("Privacy Policy", destination: Text("Privacy Policy Content"))
}
}
.navigationTitle("Settings")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {}) {
Image(systemName: "info.circle")
}
}
}
}
}
// Notifications settings view
struct NotificationsView: View {
@State private var pushEnabled = true
@State private var emailEnabled = false
@State private var smsEnabled = true
var body: some View {
Form {
Section(header: Text("Notification Methods")) {
Toggle("Push Notifications", isOn: $pushEnabled)
Toggle("Email Notifications", isOn: $emailEnabled)
Toggle("SMS Notifications", isOn: $smsEnabled)
}
Section(header: Text("Notification Types")) {
Toggle("New Fruit Arrivals", isOn: .constant(true))
Toggle("Price Drops", isOn: .constant(true))
Toggle("Seasonal Specials", isOn: .constant(false))
}
}
.navigationTitle("Notification Settings")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {}) {
Image(systemName: "bell")
}
}
}
}
}
// Main application entry
struct AppView: View {
var body: some View {
MainTabView()
}
}Metadata
Metadata
Assignees
Labels
No labels