0

I'm trying to show a fullScreenCover when the user taps the third tab instead of actually switching to that tab. The issue is that resetting selectedTab = oldValue in onChange breaks the NavigationStack push/pop animations in the previous tab.

The Problem

When I reset the tab selection synchronously, the NavigationStack in the previous tab loses its animations - no push/pop transitions work anymore until I switch tabs away and back.

Broken code:

struct ContentView: View {
  @State private var selectedTab: Int = 0
  @State private var showSheet: Bool = false

  var body: some View {
    TabView(selection: $selectedTab) {
      Tab("First", systemImage: "1.circle.fill", value: 0) {
        FirstTabView()
      }
      Tab("Second", systemImage: "2.circle.fill", value: 1) {
        SecondTabView()
      }
      Tab("Sheet", systemImage: "ellipsis", value: 2, role:.search) {
        EmptyView()
      }
    }
    .onChange(of: selectedTab) { oldValue, newValue in
      if newValue == 2 {
        showSheet = true
        selectedTab = oldValue  // This breaks NavigationStack animations!
      }
    }
    .fullScreenCover(isPresented: $showSheet) {
      SheetView()
    }
  }
}

Broken navigation animation here: https://youtube.com/shorts/SeBlTQxbV68

The Workaround

Adding a small delay before resetting the tab selection seems to fix it:

.onChange(of: selectedTab) { oldValue, newValue in
  if newValue == 2 {
    Task { @MainActor in
      showSheet = true
      try? await Task.sleep(for: .seconds(0.25))
      selectedTab = oldValue
    }
  }
}

Working with delay: https://youtube.com/shorts/B4AbX72vc3g

Full Reproducible Code

import SwiftUI

struct FirstTabView: View {
  var body: some View {
    NavigationStack {
      VStack {
        Text("Basic View")
      }
    }
  }
}

struct SecondTabView: View {
  @State private var items: [String] = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"]

  var body: some View {
    NavigationStack {
      List(items, id: \.self) { item in
        NavigationLink(value: item) {
          Text(item)
        }
      }
      .navigationTitle("Second Tab")
      .navigationBarTitleDisplayMode(.inline)
      .navigationDestination(for: String.self) { item in
        Text(item)
      }
      .toolbar {
        ToolbarItem(placement: .navigationBarTrailing) {
          Button(action: {
            items.append("Item \(items.count + 1)")
          }) {
            Image(systemName: "plus")
          }
        }
      }
    }
  }
}

struct SheetView: View {
  @Environment(\.dismiss) private var dismiss

  var body: some View {
    NavigationStack {
      VStack {
        Text("Hello World")
      }
      .navigationTitle("Sheet View")
      .navigationBarTitleDisplayMode(.inline)
      .toolbar {
        ToolbarItem(placement: .navigationBarTrailing) {
          Button(action: {
            dismiss()
          }) {
            Image(systemName: "xmark")
          }
        }
      }
    }
  }
}

struct ContentView: View {
  @State private var selectedTab: Int = 0
  @State private var showSheet: Bool = false

  var body: some View {
    TabView(selection: $selectedTab) {
      Tab("First", systemImage: "1.circle.fill", value: 0) {
        FirstTabView()
      }
      Tab("Second", systemImage: "2.circle.fill", value: 1) {
        SecondTabView()
      }
      Tab("Sheet", systemImage: "ellipsis", value: 2, role:.search) {
        EmptyView()
      }
    }
    .onChange(of: selectedTab) { oldValue, newValue in
      if newValue == 2 {
        Task { @MainActor in
          showSheet = true
          try? await Task.sleep(for: .seconds(0.25))
          selectedTab = oldValue
        }
      }
    }
    .fullScreenCover(isPresented: $showSheet) {
      SheetView()
    }
  }
}

#Preview {
  ContentView()
}

Questions

  1. Why does the synchronous reset break NavigationStack animations?
  2. Is there a cleaner solution that doesn't require a hardcoded delay?
  3. Is this a known iOS 26 bug with TabView and NavigationStack?

Environment: iOS 26.1, Xcode 26.1

1

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.