2

I have the following View extension that allows the use of a .toolbar if it is available, but allows the code to compile if it is not:

@available(iOS 14, *)
struct ToolbarAvailable14ViewModifier<V>: ViewModifier where V: View {
    var placement: ToolbarItemPlacement
    var item: (V) -> V

    func body(content: Content) -> some View {
        content
            .toolbar {
                ToolbarItemGroup(placement: placement) { //Type '(V) -> V' cannot conform to 'View'
                    item
                }
            }
    }
}

@available(iOS 15, *)
struct ToolbarAvailable15ViewModifier<V>: ViewModifier where V: View {
    var placement: ToolbarItemPlacement
    var item: (V) -> V
    
    func body(content: Content) -> some View {
        content
            .toolbar {
                ToolbarItemGroup(placement: placement) { //Type '(V) -> V' cannot conform to 'View'
                    item
                }
            }
    }
}

extension View {
    @ViewBuilder
    func toolbarIfAvailable<V>(placement: ToolbarItemPlacement, _ item: @escaping (V) -> V) -> some View where V: View {
        if #available(iOS 14, *) {
            self
                .modifier(ToolbarAvailable14ViewModifier(placement: placement, item: item))
        }
        if #available(iOS 15, *) {
            self
                .modifier(ToolbarAvailable15ViewModifier(placement: placement, item: item))
        }
        else {
            self
        }
    }
}

The above gets me to here:

        .toolbarIfAvailable(placement: .keyboard) { _ in
            Button("Click") {
                print("Click Pressed")
            }
        }

My code is giving me the following error on each of the ViewModifiers:

"Type '(V) -> V' cannot conform to 'View'" V is a generic that is defined as V: View. Two related questions: 1. How do I get rid of the errors marked in the code, and 2. how do I get rid of the need for the _ in in the call?

I have reviewed these questions: Add view as a parameter to a custom ViewModifier, How to use view modifiers only available for iOS 14+ with deployment target of iOS 13 & Modify view from within a view modifier, but they don't quite get me there.

Yrb
  • 8,103
  • 2
  • 14
  • 44

1 Answers1

1

Each of your item parameters is defined as (V) -> V -- this means a closure that takes V as a parameter and returns V. In fact, all you want is () -> V (a closure that takes no parameters and returns V). That gets you the trailing closure you want -- then, you can pass just the V to the view modifiers.

@available(iOS 14, *)
struct ToolbarAvailable14ViewModifier<V>: ViewModifier where V: View {
    var placement: ToolbarItemPlacement
    var item: V

    func body(content: Content) -> some View {
        content
            .toolbar {
                ToolbarItemGroup(placement: placement) {
                    item
                }
            }
    }
}

@available(iOS 15, *)
struct ToolbarAvailable15ViewModifier<V>: ViewModifier where V: View {
    var placement: ToolbarItemPlacement
    var item: V
    
    func body(content: Content) -> some View {
        content
            .toolbar {
                ToolbarItemGroup(placement: placement) {
                    item
                }
            }
    }
}

extension View {
    @ViewBuilder
    func toolbarIfAvailable<V>(placement: ToolbarItemPlacement, _ item: @escaping () -> V) -> some View where V: View {
        if #available(iOS 14, *) {
            self
                .modifier(ToolbarAvailable14ViewModifier(placement: placement, item: item()))
        }
        if #available(iOS 15, *) {
            self
                .modifier(ToolbarAvailable15ViewModifier(placement: placement, item: item()))
        }
        else {
            self
        }
    }
}

This also takes care of your _ in issue since the closure no longer takes an argument/parameter.

jnpdx
  • 45,847
  • 6
  • 64
  • 94
  • 1
    Thank you! I never thought of passing `item()` instead of `item`, and it makes sense that it fixes the second issue as well. – Yrb Jan 14 '22 at 12:27