3

So I have this TestView which accepts headerContent and bodyContent,

struct TestView<Content: View>: View {
  let headerContent: (() -> Content)?  = nil
  let bodyContent: () -> Content
  
  var body: some View {
    VStack {
      headerContent?()
      bodyContent()
    }

  }
}

And I use it as,

struct ContentView: View {  
  var body: some View {
    TestView {
      Text("Body Content")
    }
  }
}

Now, how do I pass headerContent? I tried doing,

struct ContentView: View {  
  var body: some View {
    TestView(headerContent: {
      Text("HeaderContent")
    }) {
      Text("BodyContent")
    }
  }
}

I get error,

Extra arguments at positions #1, #2 in call
Generic parameter 'Content' could not be inferred

4 Answers 4

4

Way1:

Here is a way, but you would get issue and error if your headerView type be deferent than the body Content! E.g. Circle and Text. I solved this issue in my second Way:

struct TestView<Content: View>: View {
    
    @ViewBuilder let headerContent: (() -> Content)?
    @ViewBuilder let bodyContent: () -> Content
    
    init(headerContent: (() -> Content)? = nil, bodyContent: @escaping () -> Content) {
        self.headerContent = headerContent
        self.bodyContent = bodyContent
    }
    
    var body: some View {
        
        return VStack {
            
            headerContent?()
            bodyContent()
        }
        
    }
    
}

Way2:

Here is the right way for you:

struct TestView<BodyContent: View, HeaderContent: View>: View {
    
    @ViewBuilder let headerContent: () -> HeaderContent
    @ViewBuilder let bodyContent: () -> BodyContent
    
    init(headerContent: @escaping () -> HeaderContent, bodyContent: @escaping () -> BodyContent) {
        self.headerContent = headerContent
        self.bodyContent = bodyContent
    }
    
    init(bodyContent: @escaping () -> BodyContent) where HeaderContent == EmptyView {
        self.headerContent = { EmptyView() }
        self.bodyContent = bodyContent
    }
    
    var body: some View {
        
      return VStack {
            
            headerContent()
            
            bodyContent()
        }
        
    }
}

Use case:

struct ContentView: View {
    
    var body: some View {
          
        TestView(headerContent: {
            Circle().fill(Color.red).frame(width: 25, height: 25)
        }, bodyContent: {
            Text("Hello, World!")
        })
            

        TestView(bodyContent: {
            Text("Hello, World!")
        })

    }
    
}
Sign up to request clarification or add additional context in comments.

1 Comment

You should prefix the headerContent and bodyContent parameters with @ViewBuilder if you want to pass content blocks with more than one view structure. E.g., init(@ViewBuilder headerContent: @escaping () ...
1

Just make it var (ie. changeable) instead of let, like

struct TestView<Content: View>: View {
  var headerContent: (() -> Content)?  = nil     // << here !!

...

1 Comment

@swiftPunk, it is out of question context.
0

Change your optional variable from let to var.

When you define a variable using let keyword then you can't change it. In your example, you have set nil as constant for a headerContent.

struct TestView<Content: View>: View {
    var headerContent: (() -> Content)?  = nil
    let bodyContent: () -> Content

You can also use let with init

struct TestView<Content: View>: View {
    let headerContent: (() -> Content)?
    let bodyContent: () -> Content
    
    init(headerContent: (() -> Content)?  = nil, bodyContent: @escaping () -> Content) {
        self.headerContent = headerContent
        self.bodyContent = bodyContent
    }

Comments

0

Making a single view an optional parameter can be done through two different approaches

Way 1 :- with this way you can easily pass view in closure.

    struct View<Content: View>: View {
      var content: Content?

      init(@ViewBuilder content: () -> Content = { EmptyView() }) {
          let view  = content()
          self.content = (view is EmptyView) ? nil : view
      }
      var body: some View {
        if let content = content {
           
        } else {
            
        }
      }
    }

Way 2 :- with this way you need to pass view with wrap in AnyView

    struct View: View {
      var content: AnyView?

      init(content: AnyView? = nil) {
          self.content = content
      }
      var body: some View {
        if let content = content {
           
        } else {
            
        }
      }
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.