Protocol Concrete Properties

Protocols are a great tool to define some type of configuration because it gives flexibility. SwiftUI, for example, uses to define styles for interface elements.

protocol PrimitiveButtonStyle { ... }

protocol PickerStyle { ... }

protocol ListStyle { ... }

The use of protocol here, instead of enumerations, allows the definition of platform-specific styles.

@available(watchOS 6.0, *)
@available(iOS, unavailable)
@available(OSX, unavailable)
@available(tvOS, unavailable)
public struct CarouselListStyle : SwiftUI.ListStyle { ... }

It also allows the implementation of styles in addition to the predefined ones.

struct CustomButtonStyle: PrimitiveButtonStyle {

    func makeBody(configuration: PrimitiveButtonStyle.Configuration) -> some View {
        ...
    }

}

A downside of this approach is that we lose the use of dot syntax to access the existing styles that the use of enumeration would allow. This is particularly problematic in frameworks because it leaves the user completely clueless. This type of styling ends up having a poor usability dependent on documentation, not always as good as it could be, or tips from a text editor, who does not always suggest the right options.

Picker(selection: $selectedOption, label: Text("Option")) {
    ...
}
.pickerStyle(???)

One may try declare a static property in the protocol only to realize that this is not currently possible.

extension PickerStyle {
    static var wheels = WheelPickerStyle() // Swift Compiler Error: Static stored properties not supported in protocol extensions
}

I propose to introduce a concrete keyword that would allow the declaration of a static like property in a protocol extension but constrained to a concrete implementation of it, hence a new keyword instead of use static.

extension PickerStyle {
    concrete var wheels = WheelPickerStyle()
}

This would give us the flexibility of protocols and the usability of dot syntax.

Picker(selection: $selectedOption, label: Text("Option")) {
    ...
}
.pickerStyle(.wheels)
1 Like

Why is this not possible with an enum, and what would it take to make it possible?

Sorry, I actually made a wrong statement. I could swear it was not possible to annotate enumerations cases with @available but it is possible.

I'd still say that enum is quite lacking in providing this much configurability. It needs to have a .custom case with a protocol payload. At which point we might as well just use said protocol.

3 Likes

This has already been discussed in detail in the protocol metatype extension thread

1 Like

I agree about the problem statement, this is a significant hurdle to SwiftUI usability. But I wonder if the solution isn't to be found in XCode instead? It should be able to know which concrete types are allowed at a particular function argument and could suggest them. The only challenge is really what trigger the user should have to supply, since there is no leading dot. Perhaps simply waiting a few seconds? Or pressing space?

You can press the Esc key to trigger completion suggestions if I recall correctly.

However, I don't think that's the way to go. Even the SwiftUI team wanted to provide dot notation for View styles originally, as explained in the thread @Palle linked. To achieve that feature they provided a workaround in SwiftUI, using StaticMember (wayback machine link, since it's been removed after a few betas), a concrete wrapper for enabling implicit member expressions and thought to be used in the following way:

protocol ColorStyle {
    typealias Member = StaticMember<Self>
}

extension StaticMember where Base: ColorStyle {
    static var red: RedStyle.Member { .init(.init()) }
    static var blue: BlueStyle.Member { .init(.init()) }
}

extension View {
    func colorStyle<S: ColorStyle>(_ style: S.Member) -> some View {
        ...
    }
}

MyView().colorStyle(.red)
MyView().colorStyle(.blue)

To be honest I never quite understood what StaticMember was about, and it's still slightly mysterious...

But I'd say the concrete type problem is a general one, there should be a Find -> Implementers popup menu for protocols, and/or an option under Assistant, along with subclasses, superclasses etc.

1 Like

StaticMember was a workaround to solve this usability problem but in fact it was kind of confusing. I miss it though.