1

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.