Mac Catalyst 18 TabView selection binding broken

I think I've found a bug with SwiftUI TabView selection binding on Mac Catalyst 18.

This code prints to the console when the SceneStorage variable for the TabView selection changes.

If you run the code below on iOS and change the tab, the selection prints to the console indicating the SceneStorage variable has changed.

If you run the code below on Mac Catalyst 18 in Sequoia and change the tab, the selection does not print to the console indicating the SceneStorage variable has not changed.

Looking for input please before I turn this over to Apple code-level support.

//
//  ContentView.swift
//  tabview-catalyst-issue
//

import SwiftUI
import os.log

enum TabDisplay: String, CaseIterable, Identifiable {
    case list
    case grid

    var id: String { self.rawValue }
}

struct ContentView: View {
    @SceneStorage("selectedTabView") var selectedTabView = TabDisplay.list
    
    var body: some View {
        VStack {
            TabView(selection: $selectedTabView) {
                Text("LIST")
                    .tabItem {
                        Image(systemName: "list.bullet")
                        Text("List")
                    }
                    .tag(TabDisplay.list)
                Text("GRID")
                    .tabItem {
                        Image(systemName: "rectangle.split.3x3")
                        Text("Grid")
                    }
                    .tag(TabDisplay.grid)
            }
        }
        .padding()
        .onChange(of: self.selectedTabView, perform: { value in
            // Added to show that the TabView binding doesn't work in Mac Catalyst 18
            print(value)
        })
    }
}

#Preview {
    ContentView()
}
Answered by Vision Pro Engineer in 807822022

Hi @SRiggs ,

This is a known issue, can you please test with Xcode 16.1 beta 3 if you have not already? https://developer.apple.com/download/applications/

Also, which macOS are you on? the latest is macOS 15.1 beta 6.

Thanks!

Accepted Answer

Hi @SRiggs ,

This is a known issue, can you please test with Xcode 16.1 beta 3 if you have not already? https://developer.apple.com/download/applications/

Also, which macOS are you on? the latest is macOS 15.1 beta 6.

Thanks!

I'm having a similar issue. My TabView used to work on all platforms and it still does on iOS and iPadOS.

However on macCatalyst clicking any tab will revert to the first tab on the TabView after a short while. The selection value will not be updated when clicking on a new tab. Setting the value programmatically works fine.

The issue still persists when building with Xcode 16.1 beta 3.

//storing selected tab in view struct
enum UITab {
    case clip
    case charts
}

@State private var selectedUITab: UITab = .clip

...

//the actual TabView in the view's body
TabView(selection: self.$selectedUITab) {
    Tab("First Tab", image: "tab1", value: .clip) {
        FirstView()
    }
                    
    Tab("Second Tab", image: "tab2", value: .charts) {
        SecondView()
    }
                    
    //...etc
}

@olvrptz ,

Can you please tell me what macOS version you're running and submit a bug report at https://feedbackassistant.apple.com with that info and a sample project and post the FB number here.

Thank you!!

I have run into the same issue. If any subview of the TabView is invalidated due to an ObservableObject being updated, on MacCatalyst or Mac designed for iPad builds, the TabView resets to the default tab. No issue on iOS or iPadOS.

import SwiftUI

@main
struct TestTabViewApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
class AppState: ObservableObject {}

struct TestViewButton: View {
    var color: Color
    @ObservedObject var appState: AppState = AppState()

    var body: some View {
        ZStack {
            color
            Button("\(color.description)") {
                appState.objectWillChange.send()
            }
            .padding()
            .background(.white)
        }
    }
}

struct ContentView: View {

    var body: some View {
        TabView {
            Button("First tab") {}
                .tabItem {Text("First tab")}
            TestViewButton(color: .blue)
                .tabItem {Text("Blue")}
            TestViewButton(color: .green)
                .tabItem {Text("Green")}
            TestViewButton(color: .red)
                .tabItem {Text("Red")}
            Button("No change") {}
                .tabItem {Text("No change")}
        }
    }
}

In the above basic application, each tab has a button.

When run on My Mac (Mac Catalyst) or My Mac (made for iPad), pressing the button under the Red, Green or Blue tabs causes the tab view to reset. Pressing the button on the "No change" tab, does not reset the TabView, as the subview state is not invalidated.

When run on an iPad 18.0.1 or My Mac (macOS), the TabView behaves correctly, without any reset.

Feedback submitted (FB15436253)

OK I am happy to report I have found a workaround which is benign on other platforms, until Apple fixes TabView on Catalyst. Adding an onAppear() to each tab subview, and manually setting the selectedView stops Catalyst from resetting the subview, and doesn't appear to have any issue on iOS/iPadOS.

struct ContentViewFixed: View {
    @State private var selectedTab: Int = 1
    var body: some View {
        TabView(selection: $selectedTab) {
            Button("First tab") {}
                .tabItem {Text("First tab")}
                .tag(1).onAppear {selectedTab = 1}
            TestViewButton(color: .blue)
                .tabItem {Text("Blue")}
                .tag(2).onAppear {selectedTab = 2}
            TestViewButton(color: .green)
                .tabItem {Text("Green")}
                .tag(3).onAppear {selectedTab = 3}
            TestViewButton(color: .red)
                .tabItem {Text("Red")}
                .tag(4).onAppear {selectedTab = 4}
            Button("No change") {}
                .tabItem {Text("No change")}
                .tag(5).onAppear {selectedTab = 5}
        }
    }
}
Mac Catalyst 18 TabView selection binding broken
 
 
Q