Home > Mobile >  SwiftUI extension generic where clause not matching
SwiftUI extension generic where clause not matching

Time:04-02

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 not Label because I need to use the SwiftUI Label 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'

enter image description here

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() }

  • Related