UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

NavigationView with TabView on chatting app

Forums > SwiftUI

As Paul Hudson noted in one of his articles: "It’s common to want to use NavigationView and TabView at the same time, but you should be careful: TabView should be the parent view, with the tabs inside it having a NavigationView as necessary, rather than the other way around."

Nevertheless, I need the tabbar to disappear once a chat is open. So I currently have a NavigationView as the parent of TabView, hidden on all tabs but the "Chats" one. This NavigationView holds the NavigationLinks within a List that displays a chat view. It works althogh there are some bugs and concernings (e.g. adding an EditButton to List, make it show but doesn't work). Hence I would like to know if someone could figure out another way of making my UI with better code.

In case I didn't express myself clearly, I want to achieve a design similar to Whatsapp or Telegram.

My code nowadays is similar to this:

  • Main code (key stuff)

    enum Tab {
      case chats, otherTab
    }
    struct ContentView: View {
      @State private var currentTab: Tab 
    
      var body: some View {
        NavigationView{
          TabView(selection: $currentTab){
            ChatsView()
              .tabItem{
                ...
              }
              .tag(Tab.chats)
              .navigationBarHidden(false)
            OtherTab()
              .tabItem{
                ...
              }
              .tag(Tab.otherTab)
              .navigationBarHidden(true)
          }
          .navigationBarTitle("Messages")
        }
      }
    }
  • Auxiliar and model code (may not be relevant)
    struct Chat{
      let contact, contactImage: String
    }
    class ChatsList: ObservableObject {
      @Published var array = [Chat]()
      static var sampleData = ChatList(array: (1...20).map({
        Chat(contact: "contact\($0)", contactImage: String($0))
      }))
    }
    struct ChatsView: View {
      @State private var chatList = ChatsList.sampleData
      var body: some View {
        List{
          if condition {
            Color.red
          }
          ForEach(chatsList.array){ chat in
            NavigationLink(destination: ChatView(contact: chat.contact)){
              HStack{
                Image(chat.contactImage)
                Text(chat.contact)
                Spacer()
              }
            }
          }
        }
      }
    }
    struct ChatView: View{
      let contact: String
      var body: some View {
        Text("¡Ayuda, por favor!")
        .navigationBarTitle(Text(contact), displayMode: .inline)
      }
      init(_ contact: String){
        self.contact = contact
      }
    }

Thank you in advance.

2      

Hi I been thinking about this and refering back to Paul statement about TabView and NavigationView.

I thought this might be one solution.

Create the enum

enum Tab {
    case home, chat
}

Then create a TabView

struct TabView: View {
    @Binding var tabIndex: Tab

    var body: some View {
        HStack {
            Group {
                Spacer()

                Button (action: {
                    self.tabIndex = .home
                }) {
                    Image(systemName: "house.fill")
                }
                .foregroundColor(self.tabIndex == .home ? .blue : .secondary)

                Spacer(minLength: 0)

                Button (action: {
                    self.tabIndex = .chat
                }) {
                    Image(systemName: "text.bubble.fill")
                }
                .foregroundColor(self.tabIndex == .chat ? .blue : .secondary)

                Spacer()
            }
            .padding(.bottom, 15)
        }
        .font(.system(size: 20))
        .frame(height: 70)
        .background(Color.black.opacity(0.1))
    }
}

Then create ChatView

struct ChatView: View {
    var body: some View {
        List(1..<10, id: \.self) { num in
            NavigationLink(destination: Text("Chat \(num)"))  {
                Text("Chat \(num)")
            }

        }
    }
}

Then add in ContentView this code

struct ContentView: View {
    @State var tabIndex: Tab = .home

    var body: some View {
        NavigationView {
            VStack(spacing: 0) {
                ZStack {
                    if tabIndex == .home {
                        Color.red
// Un-Comment if you want Navigation Bar on Chat screen and remove out one below
//                        .navigationBarHidden(true)
                    } else if tabIndex == .chat {
                        ChatView()
                    }
                }
                Spacer(minLength: 0)
                TabView(tabIndex: self.$tabIndex)

            }
            .edgesIgnoringSafeArea(.bottom)
            .navigationBarTitle("Chat", displayMode: .inline)
// Remove if you want Navigation Bar on Chat screen and un-comment out one above
            .navigationBarHidden(true)
        }
    }
}

When run it hids the Nav Bar until you open a chat and then the Tab bar disappear and Nav Bar appears

Hope that some ideas

3      

I appreciate the help, but the suggested implementation by @NigelGee has several problems:

  • About the custom TabView:

    • It doesn't own (is not the parent) of tabbed views.
    • It allows to have only 2 tabs (it's not flexible).
    • It gives no option to switch tab items (it's not easily reusable).
    • The overall behaviour and appearance is not very system-like.
  • About ContentView:

    • It's aware of TabView way of function. In fact, most functionality is declared inside ContentView, bad idea.
    • There is an Spacer() defining the alignment of the tabbed view to the top (it's not configurable).

All the highlighted issues are mainly resolved by using native views. Otherwise either the development time is generally increased or delivered products have lower quality. So I am asking for an answer that relies only or mainly on native elements. Whenever possible the concerns should not be considerable if having to create custom views.

2      

I am sorry that @RubeDEV do not like the solution. But I will address the comments

  • The Custom TabView

    • It a View to act like a TabBar (to hold buttons) and navigate between other views. The NavigationView is the Parent which sould resolve problems you have later with EditButton().
    • In your example you gave two tabs that why I only did two. You can have as many "tabs" as your need by adding more Button to TabView() (very flexible). Also why add else if to ContentView and not else.
    • Do you mean "no option to switch tab items", the images, you can change these if you want text just put in a VStack with the text(very reusable)
    • The behaviour is like the system one tap on a button and you get a view. The appearance is a matter of color and texture that down to you changing the color etc or custom it to look completely different if you want. I was not going to spend alot of time on that as example code.
  • The Content View

    • Holds the TabView and which view is selected, which is the only functionalty it does as the same as when using system TabView. All other functionalty is done in the selected views. Only used Color.red two represent other view.
    • The Spacer(minLength, 0) is there to push the TabView to the bottom.

I will also put what Paul said "TabView should be the parent view, with the tabs inside it having a NavigationView as necessary, rather than the other way around." Good Luck with finding a native views.

2      

I mean you must actually change the code to adap it to your needs. That's not good practice, instead you should deliver code that can be adaptable when using it. E.g: You can use

// The native one
TabView(...){
  FirstTabbedView()
  ...
  NthTabbedView()
}

whatever n you want (up to 10 due to ViewBuilder restrictions).

That's not possible with the code you provided. Instead you should rewrite TabView implementation if you wanted two tabs with different number of elements. Again, changing the items shown on the bar require you to change TabView code, whereas native TabView lets you set it dynamically as you instantiate.

About ContentView: it is responsible of showing the view corresponding to the tapped item, which is different of how native TabView works; it only exposes the selected tab in order to let you check in case you would like to perform actions when changed. Also, the best option is to use switch instead of if else pattern. (In fact you don't need to wrap the views inside a ZStack. Use Group or AnyView if compiler complains about returning different view types, but it should not as they are in the VStack ViewBuilder body). The Spacer is inside a VStack, so it pushes TabView to bottom and the other view upwards.

I know it kind of work, but consider how many changes you should make given that you wanted to add or remove tabs. These are not good coding practices.

PS: I just want to point out all of these objections I made arise from the purpose of spreading good code practices which lead to easier and lighter work in the long-term.

2      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.