0

I'm having a bit of a challenge figuring this one out. In the below code I create a TabView and have the ability to switch which tab is active using the .tag(), but I have a horizontal ScrollView of Tab items. Each tab screen displays similar information so I am using a single view to create it and then a ForEach loop to go through the items. But it breaks the .tag() function and I can't seem to figure out how to get it to work using the ForEach loop. Any ideas on how to approach this or what I am missing?

import SwiftUI
\\ Working code with normal Swipable tabs using .tag()
struct ContentView: View {
    var body: some View {
        
        CustomTabView()
        
    }
}

struct CustomTabView: View {
    
    @State var selectedTab = "house"
    
    var body: some View {
        
        ZStack(alignment: Alignment(horizontal: .center, vertical: .bottom)) {
            
            TabView(selection: $selectedTab) {
                
                HouseView()
                    .tag("house")
                
                EnvelopeView()
                    .tag("envelope")
                
                FolderView()
                    .tag("folder")
                
                SettingsView()
                    .tag("gear")
                
                SliderView()
                    .tag("slider.horizontal.3")

                VideoView()
                    .tag("video")

                UpView()
                    .tag("arrow.up.doc")

            }
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
            .ignoresSafeArea(.all, edges: .bottom)
            
            ScrollView(.horizontal, showsIndicators: false, content: {
                
                HStack {
                    
                    ForEach(tabs,id: \.self){image in
                        
                        TabButton(image: image, selectedTab: $selectedTab)

                    }
                }

            })
            .padding(.horizontal, 25)
            .padding(.vertical, 5)
            .background(Color.white)
            .clipShape(Capsule())
            .shadow(color: Color.black.opacity(0.15), radius: 5, x: 5, y: 5)
            .shadow(color: Color.black.opacity(0.15), radius: 5, x: -5, y: -5)
            .padding(.horizontal)
        }
        .ignoresSafeArea(.keyboard, edges: .bottom)
        .background(Color.black.opacity(0.05).ignoresSafeArea(.all, edges: .all))
    }
}

// tab...

var tabs = ["house", "envelope", "folder", "gear", "slider.horizontal.3", "video", "arrow.up.doc"]

struct TabButton: View {
    
    var image : String
    @Binding var selectedTab : String
    
    var body: some View {
        
        Button(action: {selectedTab =  image}) {
            
            Image(systemName: image)
                .renderingMode(.template)
                .opacity(self.selectedTab == image ? (1) : (0.5))
                .padding()
        }
    }
}

struct HouseView: View {

    var body: some View {
  
         VStack {
            Text("House View")

      }
   }
}

struct EnvelopeView: View {

    var body: some View {
  
         VStack {
            Text("Envelope View")

      }
   }
}

struct FolderView: View {

    var body: some View {
  
         VStack {
            Text("Folder View")

      }
   }
}

struct SettingsView: View {

    var body: some View {
  
         VStack {
            Text("Settings View")

      }
   }
}

struct SliderView: View {

    var body: some View {
  
         VStack {
            Text("Slider View")

      }
   }
}

struct VideoView: View {

    var body: some View {
  
         VStack {
            Text("Video View")

      }
   }
}

struct UpView: View {

    var body: some View {
  
         VStack {
            Text("Up View")

      }
   }
}

Working example of TabBar highlighting on swipe or selection

Problematic Code after the ForEach loop on the views in the TabView. The views populate but it breaks the .tag() functionality and I'm lost as to how to make it work again.

import SwiftUI

struct ContentView: View {
    var body: some View {
        
        CustomTabView()
        
    }
}

struct CustomTabView: View {

    @State var selectedTab = "house"
    @State var viewData : AllViewData!

    var body: some View {

        ZStack(alignment: Alignment(horizontal: .center, vertical: .bottom)) {

            TabView(selection: $selectedTab) {

                ForEach(allViews){views in

                    ViewsCardView(viewData: views)
                        .tag("//not sure if this is the right place for the .tag()")
                }

            }
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
            .ignoresSafeArea(.all, edges: .bottom)

            ScrollView(.horizontal, showsIndicators: false, content: {

                HStack {

                    ForEach(tabs,id: \.self){image in

                        TabButton(image: image, selectedTab: $selectedTab)

                    }
                }

            })
            .padding(.horizontal, 25)
            .padding(.vertical, 5)
            .background(Color.white)
            .clipShape(Capsule())
            .shadow(color: Color.black.opacity(0.15), radius: 5, x: 5, y: 5)
            .shadow(color: Color.black.opacity(0.15), radius: 5, x: -5, y: -5)
            .padding(.horizontal)
        }
        .background(Color.black.opacity(0.05).ignoresSafeArea(.all, edges: .all))
    }
}

// tab...

var tabs = ["house", "envelope", "folder", "gear", "slider.horizontal.3", "video", "arrow.up.doc"]

struct TabButton: View {

    var image : String
    @Binding var selectedTab : String

    var body: some View {

        Button(action: {selectedTab =  image}) {

            Image(systemName: image)
                .renderingMode(.template)
                .opacity(self.selectedTab == image ? (1) : (0.5))
                .padding()
        }
    }
}

struct ViewsCardView: View {

     @State var viewData : AllViewData!

     var body: some View {

         VStack {

           Text(viewData.name)

       }
    }
}

struct AllViewData : Identifiable {

    var id = UUID().uuidString
    var name : String

}

var allViews = [

    AllViewData(name: "House"),
    AllViewData(name: "Envelope"),
    AllViewData(name: "Folder"),
    AllViewData(name: "Settings"),
    AllViewData(name: "Slider"),
    AllViewData(name: "Video"),
    AllViewData(name: "Up"),

]


TabBar doesn't function like the first example

Laren
  • 37
  • 1
  • 8
  • Please make a [Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example). You have left too many things out of your code for this to run properly, so it is difficult to see what is going on. Also, please define the images with stock SF Symbols so we can see the tabs. – Yrb Aug 03 '21 at 18:42
  • Updated the code so it can be reproduced. Thank you for pointing this out. I hope I will be able to find a solution to this. – Laren Aug 04 '21 at 01:54
  • The problematic code won't compile. You should strip out anything that is already in the first section, and just put in the struct that needs to be swapped. – Yrb Aug 04 '21 at 19:05
  • Strange because it compiles for me. The problem is that the tabs are no longer highlighting when swiping to the next view like the working code example. My trouble is trying to figure out the ForEach for the TabViews while also using .tag(). I put some gif examples showing the behavior. – Laren Aug 05 '21 at 09:08

1 Answers1

0

The issue you are having is that you have stringly typed everything. So, you simply a lack of having access to the image that is associated with each view in your tab bar. Your ForEach iterates through your allViews which only has the name of the view, not the image. So, when you need to set the tab, you do not have the image name available. I changed your strings to an enum which gives you a limited set of choices and more control over what you are trying to accomplish. A great deal of tutorials use strongly typing when they are trying to demonstrate something, but it is a bad habit that can create difficult to find bugs.

struct ContentView: View {
    var body: some View {
        
        CustomTabView()
        
    }
}

struct CustomTabView: View {
    
    @State var selectedTab = ViewSelect.House // This is an enum defined below.
    @State var viewData : AllViewData!
    
    var body: some View {
        
        ZStack(alignment: Alignment(horizontal: .center, vertical: .bottom)) {
            
            TabView(selection: $selectedTab) {
                ForEach(ViewSelect.allCases, id: \.self) { view in // This iterates through all of the enum cases.
                    ViewsCardView(viewData: view.allViewData)
                        .tag(view.rawValue) // by having the tag be the enum's raw value,
                                            // you can always compare enum to enum.
                }
            }
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
            .ignoresSafeArea(.all, edges: .bottom)
            
            ScrollView(.horizontal, showsIndicators: false, content: {
                
                HStack {
                    
                    ForEach(ViewSelect.allCases ,id: \.self){viewSelect in
                        
                        TabButton(viewSelect: viewSelect, selectedTab: $selectedTab)
                        
                    }
                }
                
            })
            .padding(.horizontal, 25)
            .padding(.vertical, 5)
            .background(Color.white)
            .clipShape(Capsule())
            .shadow(color: Color.black.opacity(0.15), radius: 5, x: 5, y: 5)
            .shadow(color: Color.black.opacity(0.15), radius: 5, x: -5, y: -5)
            .padding(.horizontal)
        }
        .background(Color.black.opacity(0.05).ignoresSafeArea(.all, edges: .all))
    }
}

struct TabButton: View {
    let viewSelect: ViewSelect
    @Binding var selectedTab: ViewSelect
    
    var body: some View {
        
        Button(action: {selectedTab = viewSelect}) {
            
            Image(systemName: viewSelect.tabIcon)
                .renderingMode(.template)
                // this compares the selection to the button's associated enum.
                // The enum provides the image name to the button,
                // but we are always dealing with a case of the enum.
                .opacity(selectedTab == viewSelect ? (1) : (0.5))
                .padding()
        }
    }
}

struct ViewsCardView: View {
    
    @State var viewData: AllViewData
    
    var body: some View {
        
        VStack {
            
            Text(viewData.name)
            
        }
    }
}

struct AllViewData : Identifiable, Hashable {
    
    var id = UUID().uuidString
    var name : String
    
}

public enum ViewSelect: String, CaseIterable {
    case House, Envelope, Folder, Settings, Slider, Video, Up
    
    var tabIcon: String {
        switch self {
        case .House:
            return "house"
        case .Envelope:
            return "envelope"
        case .Folder:
            return "folder"
        case .Settings:
            return "gear"
        case .Slider:
            return "slider.horizontal.3"
        case .Video:
            return "video"
        case .Up:
            return "arrow.up.doc"
        }
    }

    var allViewData: AllViewData {
        return AllViewData(name: self.rawValue)
    }
}
Yrb
  • 8,103
  • 2
  • 14
  • 44
  • Awesome! And thank you so much for clarifying the issue I was having and giving a bit of guidance as well. – Laren Aug 06 '21 at 04:02
  • Follow-up question. Your solution works fantastic by the way, but once I start to add additional variables in the AllViewData struct, I run into an issue with the allViewData variable inside the enum. What’s the best way to approach many variables inside of AllViewData struct? The ViewsCardView will contain more information than just the name, but using one view because the layout will stay the same, but the data will change. @Yrb – Laren Aug 20 '21 at 09:51
  • I was looking back, but I am not sure what `allViewData` variable is inside the enum. However, that question is outside of the scope of this question. The reason Stack Overflow is useful to everyone is that we ask discreet questions and get answers to that question. When something like this pops up, you need to ask a new question, and post a [Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example). That was you get this question answered, and the next person will find that question and answer. – Yrb Aug 20 '21 at 14:50