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

Design of Custom View using @ViewBuilder and Generics

Forums > SwiftUI

Hello!

I am trying to create a custom view that basically acts like a Button in SwiftUI. There are two specific initializers of Button that allow you to pass in a label and an action, as well as an action and a custom label, which can be an arbitrary view:

Initializer One:

Button("Button") {
  // action
}

Initializer Two:

Button {
  // action
} label: {
  // label (any view using @ViewBuilder)
}

I want to create a custom view that has two initializers in a similar way. I have laid out a structure as a general example of my goal:

struct GenericView<Content: View>: View {
  var label: String
  var content: Content?

  var body: some View {
    content // need help handling based on initializers
  }

  init(label: String) {
    self.label = label
    self.content = nil
  }

  init(label: String, @ViewBuilder content: () -> Content) {
    self.label = label
    self.content = content()
  }

}

Each instance will always have a label. The content property content is of type Content, which is a generic type that conforms to View. I made it optional because you mostly would not need to pass in a custom view. In that case, it would be nil. Otherwise, you can use the second initializer and pass in any view you want with @ViewBuilder.

I have a couple of questions about this.

If I were to call the second initializer:

GenericView(label: "Label") {
  // pass in custom view (any view using @ViewBuilder)
}

I don't think it would be a problem. However, it would be an issue if I were to call the first initializer:

GenericView(label: "Label") // ERROR: Generic parameter 'Content' could not be inferred (Explicitly specify the generic arguments to fix this issue)

What does this mean? I thought about moving the generic constraint to the second initializer but I need to define the content property of type Content, so that wouldn't work. I don't want to specify a generic argument like this:

GenericView<Rectangle>(label: "Label")

In addition, I wrote, "need help handling based on initializers" in a comment above. How can I go about deciding on what to show based on the initializer called? I put content in GenericView's body, but I cannot show that if you use the first initializer. It looks like nothing shows on a view if it is nil?

In general, I just need guidance on how to create a struct that acts very closely to a Button in SwiftUI, like I mentioned above. There needs to be an initialzer that will take a couple of properties, and another initializer that will take those properties in addition to an arbitrary view using @ViewBuilder.

To be clear, I am not working on making something that behaves like a button, I just want the behavior of the initializers of Button. I am working on a custom view and the above struct is just for example purposes, my implementation of my actual project is much more involved.

How can I go about this in the best way? Thanks for the help! :)

2      

Hi,

The error means that it doesn't know what the generic is suposed to be, in the case of the button the Label is allways some kind of view, a Text in the first initializer and a Closure in the second, in your first init you're not saying what the generic is going to be, so you can tell it by adding where Content == EmptyView to the init,

init(label: String) where Content == EmptyView {
    self.label = label
  }

3      

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!

Reply to this topic…

You need to create an account or log in to reply.

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.