SwiftUI tab view display sheet

4.7k views Asked by At

I'm new to SwiftUI and I tried to build a tab bar that contained a tab that will return a modal(sheet) but not view. After I tried I found sometimes it will work but sometime are not. I want to make the previous tabbed item as the selected tab after the user dismissed the modal. But I can't find what the error. Anyone explains to me what the problem of my code?

import SwiftUI
struct ContentView: View {
    @State var isPresenting = false
    @State private var selectedItem = 1
    @State private var oldSelectedItem = 1


    var body: some View {
        TabView(selection: $selectedItem){
            Text("1")
                .tabItem {
                        Image(systemName: "house")
                }.tag(1)
            .onAppear {
                self.oldSelectedItem = self.selectedItem
            }

            Text("")    // I want this to display the sheet.
                .tabItem { Image(systemName: "plus.circle") }
                .tag(2)
            .onAppear {
                self.isPresenting = true
                self.selectedItem = self.oldSelectedItem

            }

            Text("3")
                .tabItem {
                    Image(systemName: "calendar")
                }.tag(3)
            .onAppear {
                self.oldSelectedItem = self.selectedItem
            }
        }
        .sheet(isPresented: $isPresenting) {
            testSheet
        }
        .accentColor(Color.orange)

    
    }
    var testSheet : some View {
        VStack{
            Text("testing")
        }
    }
}
3

There are 3 answers

2
Asperi On BEST ANSWER

A possible solution is to use TabView selection to activate sheet programmatically, but do not actually allow this selection to be changed (tested with Xcode 12 / iOS 14).

Update: retested with Xcode 13.4 / iOS 15.5

demo

struct ContentView: View {
    @State var isPresenting = false
    @State private var selectedItem = 1
    @State private var oldSelectedItem = 1


    var body: some View {
        TabView(selection: $selectedItem){
            Text("1")
                .tabItem {
                        Image(systemName: "house")
                }.tag(1)

            Text("")    // I want this to display the sheet.
                .tabItem { Image(systemName: "plus.circle") }
                .tag(2)

            Text("3")
                .tabItem {
                    Image(systemName: "calendar")
                }.tag(3)
        }
     //   .onReceive(Just(selectedItem))  // SwiftUI 1.0 - import Combine for this
        .onChange(of: selectedItem) {    // SwiftUI 2.0 track changes
                if 2 == selectedItem {
                self.isPresenting = true
                } else {
                    self.oldSelectedItem = $0
                }
            }
        .sheet(isPresented: $isPresenting, onDismiss: {
                self.selectedItem = self.oldSelectedItem
            }) {
            testSheet
        }
        .accentColor(Color.orange)


    }
    var testSheet : some View {
        VStack{
            Text("testing")
        }
    }
}
0
Omar Ibrahim On

A small change to Martijn Pieters's answer:-

The original code changes the current tab to a blank tab behind the sheet. This update addresses this issue by keeping the last selected tab alive.

struct ContentView: View {
    @State var isPresenting = false
    @State private var selectedItem = 1
    @State private var oldSelectedItem = 1


    var body: some View {
        TabView(selection: $selectedItem){
            Text("1")
                .tabItem {
                        Image(systemName: "house")
                }.tag(1)

            Text("")    // I want this to display the sheet.
                .tabItem { Image(systemName: "plus.circle") }
                .tag(2)

            Text("3")
                .tabItem {
                    Image(systemName: "calendar")
                }.tag(3)
        }
     //   .onReceive(Just(selectedItem))  // SwiftUI 1.0 - import Combine for this
        .onChange(of: selectedItem) {    // SwiftUI 2.0 track changes
                if 2 == selectedItem {
                self.isPresenting = true
                self.selectedItem = self.oldSelectedItem
                } else if (isPresented == false) {
                    self.oldSelectedItem = $0
                }
            }
        .sheet(isPresented: $isPresenting) {
            testSheet
        }
        .accentColor(Color.orange)


    }
    var testSheet : some View {
        VStack{
            Text("testing")
        }
    }
}

After user clicks on the sheet option, the onChange listener restores self.oldselecteditem as the active tab. The dismiss event listener on the sheet has been removed since the last active tab is already active and the listener would serve no purpose.

0
Khim Bahadur Gurung On

Add the code below to your TabView

.onChange(of: selectedItem) { newValue in
    if newValue == 2 {
        isPresenting = true
        selectedItem = oldSelectedItem
    } else {
        oldSelectedItem = newValue
    }
}
.sheet(isPresented: $isPresenting) {
    // Sheet view
}