swiftuiswiftui-tabviewviewbuilder

How to pass in content for TabView through a function


I am trying to build a reusable onboarding pager overlay. So I want to pass in different "slides" into the pager TabView based on a variable IntroType. But since TabView takes content plainly without any container, what would be the return type for that?

This is how I wish it worked:

struct IntroViewTabPageTest: View {
    // MARK: Variables
    @Binding var isPresented: Bool
    @State var activeSlide: Int = 0
    var introType: IntroType
    
    // MARK: UI
    var body: some View {
        ZStack(alignment: .bottom) {
            Color.darkGalaxy
            
            TabView(selection: $activeSlide) {
                getContentBasedOnType() // <---- Content here
            }
            .tabViewStyle(.page)
            .indexViewStyle(.page(backgroundDisplayMode: .always))
        }
    }
    
    // MARK: Functions
    private func getContentBasedOnType() -> some View {
        switch (introType) {
        case .Main:
            return introContentMain
        case .SA:
            return introContentSA
        case .Journey:
            return introContentJourney
        }
    }
}

// Static content blocks for each type
extension IntroViewTabPageTest {

    // ----> Of course everywhere errors here, "some View" is not the right return type;
    // But what is it?

    private var introContentMain: some View {
        MainIntroSlide1()
        TextSlide(headline: "Headline", text: "Text")
    }
    
    private var introContentSA: some View {
        TextSlide(headline: "Headline", text: "Text")
        TextSlide(headline: "Headline2", text: "Text2")
        TextSlide(headline: "Headline3", text: "Text3")
    }
    
    private var introContentJourney: some View {
        TextSlide(headline: "Headline", text: "Text")
    }
}

enum IntroType: String {
    case Main, SA, Journey
}

struct IntroViewTabPageTest_Previews: PreviewProvider {
    static var previews: some View {
        IntroViewTabPageTest(isPresented: .constant(true), introType: .Main)
    }
}


An alternative would be to have each variable in the extension return a whole TabView, but that's very ugly and I haven't figured it out fully either, still getting some other error with that route.

I assume that there must be some way to do this and I just don't know the available tools of SwiftUI well enough yet. I can imagine a function with a @ViewBuilder wrapper could be of use here, but I don't fully understand the logic behind it so far.

The closest I've come was using an array of AnyView() downcasts of the slides, but that made using a ForEach hard and erased the types.


Solution

  • You could pass the content in like this:

        import SwiftUI
    
        struct IntroViewTabPageTest<Content: View>: View {
      
          let content: Content
      
          init(@ViewBuilder content: () -> Content) {
            self.content = content()
          }
      
          var body: some View {
            ZStack(alignment: .bottom) {
              Color.gray
          
              TabView() {
                self.content
              }
              .tabViewStyle(.page)
              .indexViewStyle(.page(backgroundDisplayMode: .always))
            }
          }
      
        }
    
        struct IntroViewTabPageTest_Previews: PreviewProvider {
          static var previews: some View {
            Group {
          
              IntroViewTabPageTest {
                Text("hello 1")
                Text("hello 2")
              }
          
              IntroViewTabPageTest {
                Rectangle()
                  .fill(Color.green)
                  .frame(width: 100, height: 100)
              }
          
            }
          }
        }