Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI, setting title to child views of TabView inside of NavigationView does not work

Tags:

swiftui

Why I am putting TabView into a NavigationView is because I need to hide the bottom tab bar when user goes into 2nd level 'detail' views which have their own bottom action bar.

But doing this leads to another issue: all the 1st level 'list' views hosted by TabView no longer display their titles. Below is a sample code:

import SwiftUI

enum Gender: String {
    case female, male
}

let members: [Gender: [String]] = [
    Gender.female: ["Emma", "Olivia", "Ava"], Gender.male: ["Liam", "Noah", "William"]
]

struct TabItem: View {
    let image: String
    let label: String
    var body: some View {
        VStack {
            Image(systemName: image).imageScale(.large)
            Text(label)
        }
    }
}

struct ContentView: View {
    var body: some View {
        NavigationView {
            TabView {
                ListView(gender: .female).tag(0).tabItem {
                    TabItem(image: "person.crop.circle", label: Gender.female.rawValue)
                }
                ListView(gender: .male).tag(1).tabItem {
                    TabItem(image: "person.crop.circle.fill", label: Gender.male.rawValue)
                }
            }
        }
    }
}

struct ListView: View {
    let gender: Gender
    var body: some View {
        let names = members[gender]!
        return List {
            ForEach(0..<names.count, id: \.self) { index in
                NavigationLink(destination: DetailView(name: names[index])) {
                    Text(names[index])
                }
            }
        }.navigationBarTitle(Text(gender.rawValue), displayMode: .inline)
    }
}

struct DetailView: View {
    let name: String
    var body: some View {
        ZStack {
            VStack {
                Text("profile views")
            }
            VStack {
                Spacer()
                HStack {
                    Spacer()
                    TabItem(image: "pencil.circle", label: "Edit")
                    Spacer()
                    TabItem(image: "minus.circle", label: "Delete")
                    Spacer()
                }
            }
        }
        .navigationBarTitle(Text(name), displayMode: .inline)
    }
}

What I could do is to have a @State var title in the root view and pass the binding to all the list views, then have those list views to set their title back to root view on appear. But I just don't feel so right about it, is there any better way of doing this? Thanks for any help.

like image 489
ZhouX Avatar asked Oct 15 '22 07:10

ZhouX


1 Answers

The idea is to join TabView selection with NavigationView content dynamically.

Demo:

TabView with NavigationView

Here is simplified code depicting approach (with using your views). The NavigationView and TabView just position independently in ZStack, but content of NavigationView depends on the selection of TabView (which content is just stub), thus they don't bother each other. Also in such case it becomes possible to hide/unhide TabView depending on some condition - in this case, for simplicity, presence of root list view.

struct TestTabsOverNavigation: View {

    @State private var tabVisible = true
    @State private var selectedTab: Int = 0
    var body: some View {
        ZStack(alignment: .bottom) {
            contentView
            tabBar
        }
    }

    var contentView: some View {
        NavigationView {
            ListView(gender: selectedTab == 0 ? .female : .male)
            .onAppear {
                withAnimation {
                    self.tabVisible = true
                }
            }
            .onDisappear {
                withAnimation {
                    self.tabVisible = false
                }
            }
        }
    }

    var tabBar: some View {
        TabView(selection: $selectedTab) {
            Rectangle().fill(Color.clear).tag(0).tabItem {
                TabItem(image: "person.crop.circle", label: Gender.female.rawValue)
            }
            Rectangle().fill(Color.clear).tag(1).tabItem {
                TabItem(image: "person.crop.circle.fill", label: Gender.male.rawValue)
            }
        }
        .frame(height: 50) // << !! might be platform dependent
        .opacity(tabVisible ? 1.0 : 0.0)
    }
}
like image 184
Asperi Avatar answered Oct 21 '22 07:10

Asperi