Reputation: 2856
I would like to create reusable components in my app.
I have searched for similar problem. But I have only found much more complex examples.
Let's try this simple example - a button that could open different Views based on passed parameter. I have 2 views that I will open as a sheet:
FirstView.swift
import SwiftUI
struct FirstView: View {
var body: some View {
Text("First view")
}
}
SecondView.swift
struct SecondView: View {
var body: some View {
Text("Second view")
}
}
ButtonView.swift This is a view I would like to use as a reusable component in my design system.
import SwiftUI
struct ButtonView: View {
@State private var showModal: Bool = false
// This works
var text: String
// Here I am getting an error:
// Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements
var link: View
var body: some View {
VStack {
Spacer()
Button(action: {
self.showModal = true
}) {
Text(text)
.padding(20)
.foregroundColor(Color.white)
}.sheet(isPresented: self.$showModal) {
link
}
.background(Color.blue)
}
}
}
struct ButtonView_Previews: PreviewProvider {
static var previews: some View {
ButtonView(text: "TEST", link: FirstView())
}
}
ContentView.swift Here I am trying to use the same button component, but with different labels and links.
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
ButtonView(text: "first", link: FirstView())
.padding()
ButtonView(text: "second", link: SecondView())
.padding()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Passing String parameters works. Labels are different. But I cannot make it work with links to different Views. I am getting an error:
Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements
Upvotes: 2
Views: 2734
Reputation: 1046
Keeping First view and Second View as the same, use the following for the ButtonView:
struct ButtonView<Content : View>: View {
@State private var showModal: Bool = false
var text: String
// This is the generic content parameter
let content: Content
init(text: String, @ViewBuilder contentBuilder: () -> Content){
self.text = text
self.content = contentBuilder()
}
var body: some View {
VStack {
Spacer()
Button(action: {
self.showModal = true
}) {
Text(text)
.padding(20)
.foregroundColor(Color.white)
}.sheet(isPresented: self.$showModal) {
content
}
.background(Color.blue)
}
}
}
Here the generic parameter named content is used to receive any view and the initializer is used with the @ViewBuilder property wrapper to build the view.Now use it in the following way in ContentView struct:
struct ContentView: View {
var body: some View {
HStack {
ButtonView(text: "First") {
FirstView()
}
ButtonView(text: "Second") {
SecondView()
}
}
}
}
It will work like a charm :)
Also if you want to keep preview for ButtonView and don't want it to crash then add the preview as:
struct ButtonView_Previews: PreviewProvider {
static var previews: some View {
ButtonView(text: "First") {
FirstView()
}
}
}
Upvotes: 2