0

I'm trying to build a reusable navigation tab in SwiftUI and I'm facing some challenges. I come from ReactJS and wanted to create a component that I can pass the tab images and the different views.

This is the code I have so far which works pretty well:

struct Navigation<Content: View>: View  {
    @State var selectedIndex = 0
    var tabBarImageNames: [String]
    let content: [Content]
    
    init(tabBarImageNames: [String], _ content: Content...) {
        self.content = content
        self.tabBarImageNames = tabBarImageNames
    }
    
    var body: some View {
        VStack{
            ZStack {
                content[selectedIndex]
            }
            
            Spacer()
            
            HStack {
                ForEach(0 ..< tabBarImageNames.count) { tabNum in
                  [ ... ]
                }
            }
        }
    }
}

I can call Navigation with these arguments an everything works as expected:

Navigation(
    tabBarImageNames: ["house.fill", "chart.bar.xaxis", "target", "bubble.left"],
    VStack {
        Text("First view")
            .navigationTitle("First Tab")
    },
    VStack {
        Text("Second view")
            .navigationTitle("Second Tab")
    },
    VStack {
        Text("Third view")
            .navigationTitle("Third Tab")
    },
    VStack {
        Text("Fourth view")
            .navigationTitle("Fourth Tab")
    }
)

The real problem

The issue with the above code is that if the Views I'm passing as arguments are not exactly the same (when I say exactly I literally mean exactly the same type and number of components), Xcode complains with the following error:

Error in Xcode

I'm pretty sure it's something not very difficult to solve, but given my short experience with this language I'm struggling to find the right answer. I tried to change the init content protocol to AnyView instead of Content among other things, but no luck so far.

Looking for help

Anyone with SwiftUI experience that can help me out and explain me what I'm missing? Also open to feedback on improvements on the code.

Can't use any type of library or external package btw, everything has to be built manually.

Thanks Stack overflow!

Jose A. Ayllón
  • 866
  • 6
  • 19
  • I have a Swift package [ViewExtractor](https://github.com/GeorgeElsham/ViewExtractor) which looks like exactly what you need. – George Feb 16 '22 at 11:25
  • @Andrew You can pass multiple views, 1 is usually the whole screen, and 2 means sidebar + content – George Feb 16 '22 at 11:26
  • @George can't use any external libraries, but thanks for the tip - I have updated to post to reflect that too – Jose A. Ayllón Feb 16 '22 at 11:31
  • @JoseA.Ayllón It's only 1 small file if you see the source. Just use that. If you do, I recommend leaving the GitHub link though in the source code for future reference if the code changes. Obviously Swift Packages would be better though if possible, because if any issues are fixed then you would get the update. – George Feb 16 '22 at 11:32
  • @Andrew I did yeah, but the problem is not what I'm passing, the problem is that if the views I'm passing are not the same views, my code breaks – Jose A. Ayllón Feb 16 '22 at 11:34
  • 1
    I know. The problem is you have one generic `Content` so all the views must have the same type. If you had a fixed number of things to insert as content you could create a generic for each. The `ViewExtractor` package will work with varying view types, as it just reads the `@ViewBuilder`'s `TupleView` directly. – George Feb 16 '22 at 11:36
  • I don't think this is a great pattern anyways. You should only really have one `NavigationView` at the root. – George Feb 16 '22 at 11:38
  • I'll take a look to your ViewExtractor and see if fits - yeah, forget about the multiple Navigation views, they are just an example. I can use the same with `VStack` and have the same problem – Jose A. Ayllón Feb 16 '22 at 11:41
  • you should try to pass an Array of @ViewBuilder functions into your struct – ChrisR Feb 16 '22 at 12:03
  • @ChrisR They would each need their own `View` generic so it would have to be a fixed number. You couldn't make it an array because then the elements would have the same concrete type, therefore it doesn't solve the problem – George Feb 16 '22 at 12:07
  • 1
    @George your stuff is really useful and exactly what I needed, thanks so much. I'll make sure to leave a link to your repo for future references – Jose A. Ayllón Feb 16 '22 at 12:08

0 Answers0