Search code examples
swiftprotocolsassociated-types

How do i work in Swift 5 with protocol function parameters that use protocols with associated types (i.e. .pickerStyle())


I am using SwiftUI and would like to set the pickerStyle of a View depending on the number of items in the Picker. With a few items, SegmentedPickerStyle() is ideal, with more WheelPickerStyle() is better.

}.pickerStyle(productsObserver.product.productFamilies?.count ?? 0 < 5 ? SegmentedPickerStyle() : WheelPickerStyle())

The function signature reads: func pickerStyle<S>(_ style: S) -> some View where S : PickerStyle which i have learned uses a generic in the functiona signature because PickerStyle uses an associated type.

It shouldn't be so difficult a problem and probably isn't - Protocols should work like this easy = , but i can't see it. Any help is highly appreciated!


Solution

  • pickerStyle is a generic method that accepts a concrete type (at compile-time) that conforms PickerStyle. So, it cannot be either SegmentedPickerStyle or WheelPickerStyle (determined at run-time) - it has to be one or the other.

    So, one suggestion would be to create a view modifier and apply the picker style conditionally. The critical difference here is that it returns a conditional view of type _ConditionalContent<TrueContent, FalseContent>.

    struct PickerStyleOption<P1: PickerStyle, P2: PickerStyle>: ViewModifier {
        let predicate: () -> Bool
        let style1: P1
        let style2: P2
        
        @ViewBuilder
        func body(content: Content) -> some View {
            if predicate() {
                content
                    .pickerStyle(style1)
            } else {
                content
                    .pickerStyle(style2)
            }
        }
    }
    

    For convenience, you could create an extension:

    extension View {
        func pickerStyleOption<P1: PickerStyle, P2: PickerStyle>(
                _ condition: @autoclosure @escaping () -> Bool,
                then style1: P1, 
                else style2: P2) -> some View {
    
            self.modifier(
                PickerStyleOption(predicate: condition, style1: style1, style2: style2)
            )
        }
    }
    

    And use it like so:

    Picker(...) {
       ...
    }
    .pickerStyleOption((productsObserver.product.productFamilies?.count ?? 0) < 5, 
       then: SegmentedPickerStyle(), else: WheelPickerStyle())