I have this simple ThemedNavigationButton
view that handles some stuff whilst creating a NavigationLink
(The inner workings aren't important):
struct ThemedNavigationButton<Destination, L>: View where Destination: View, L: View {
var destination: () -> Destination
var label: () -> L
var body: some View {
...
}
}
I use
L
here and notLabel
because I need to use the SwiftUILabel
next
which I use like this:
ThemedNavigationButton {
NextView()
} label: {
Label {
Text("Some text")
} icon: {
Image(systemName: "check")
.foregroundColor(theme.tint)
}
}
I want to create a simpler initialiser when it is used in this manner, so I came up with this:
extension ThemedNavigationButton where L == Label<Text, Image> {
init(text: String, systemImage: String, destination: @escaping () -> Destination) {
self.destination = destination
self.label = {
Label {
Text(text text)
} icon: {
Image(systemName: systemImage)
}
}
}
}
which works great like this:
ThemedNavigationButton(text: "Some text", systemImage: "check") { NextView() }
The problem I have, is as soon as I add the image tint colour to the new initialiser I get the error:
Cannot convert value of type 'some View' to closure result type 'Image'
I'm guessing because my Image
is no longer an Image
. But what is it and how do I declare it. I can't use some View
which is what the compiler is telling me it is.
CodePudding user response:
Generics specialisation requires concrete types, so here is a possible approach to resolve this situation - introduce custom wrapper/proxy type and use it in extension.
Tested with Xcode 13.2
struct MyLabel: View { // new wrapper type
let text: String
let systemImage: String
var tintColor = Color.green
var body: some View {
Label {
Text(text text)
} icon: {
Image(systemName: systemImage)
.foregroundColor(tintColor)
}
}
}
extension ThemedNavigationButton where L == MyLabel { // << here !!
init(text: String, systemImage: String, destination: @escaping () -> Destination) {
self.destination = destination
self.label = {
MyLabel(text: text, systemImage: systemImage)
}
}
}
CodePudding user response:
To use the following notation:
ThemedNavigationButton(text: "Some text", systemImage: "check") { NextView() }
you can create a View
with just one generic type for Destination
, because the Label
will receive basic String
types.
You can set ThemedNavigationButton
as follows:
// Only one generic type needed
struct ThemedNavigationButton<Destination: View>: View {
// Constants for the label (make them appear before "destination")
let text: String
let systemImage: String
// Destination view
var destination: () -> Destination
var body: some View {
// Show the views the way you want
VStack {
destination()
// Use the label this way
Label {
Text(text)
} icon: {
Image(systemName: systemImage)
}
}
}
}
Customise the body
the way you need.
You can use it calling:
ThemedNavigationButton(text: "Some text", systemImage: "check") { NextView() }