Improve discoverability using Static Member Lookup in Generic Contexts

Static Member Lookup is extended to Generic Contexts since the release of SE-0299. It might seem to be a minor change at first, but it allows simplifying quite some code. Especially if you’re writing your views in SwiftUI, you’re going to have fun adjusting your code for this new addition released in Swift 5.5.

WWDC 2021 brought us many new features, including Swift 5.5, which was full of concurrency updates. It’s therefore easy to overlook smaller additions, for which I want to highlight one in this article.

A world without Static Member Lookup in Generic Contexts

To fully understand what this change means, it’s good to look at an example that changes with this new feature. Before Swift 5.5 extended Static Member Lookup to generic contexts, you would see button styles being defined as follows:

struct ContentView: View {
    var body: some View {
        Button("Open SwiftLee") {
            UIApplication.shared.open(URL(string: "https://www.avanderlee.com")!)
        }
        .buttonStyle(SwiftLeeButtonStyle()) // The button style is initialised here.
    }
}

This worked, but it would look nicer if we could define the button style as a static property and call it using the leading dot syntax as .swiftLee. Static member lookup on concrete types was already clever enough to infer the base type from context, like in this example with fonts:

Text("SwiftLee Homepage")
    .font(.body) 

As you can see, we can directly call .body instead of Font.body and the compiler knows what to do. The leading dot syntax results in cleaner code without the loss of clarity.

Up until Swift 5.5, this wasn’t possible in generic contexts. Looking at how the button style is applied, we can see that the method is defined using generics:

func buttonStyle<S>(_ style: S) -> some View where S : ButtonStyle 

As static member lookup wasn’t supported for members of protocols in generic methods, the following error would occur:

Static member lookup in generic context wasn't supported up until Swift 5.5.
Static member lookup in generic context wasn’t supported up until Swift 5.5.

Fortunately, Swift 5.5 includes support for inferring context within generic contexts, allowing us to make the above code example functional.

Using Static Member Lookup in Generic Contexts

Let’s make the above code example functional by defining a static accessor on our custom button style. We do this by creating an extension for our button style using a generic constraint:

extension ButtonStyle where Self == SwiftLeeButtonStyle {
    static var swiftLee: SwiftLeeButtonStyle { .init() }
}

It’s a small piece of code, but it allows us to access the button style from within SwiftUI View Modifiers as follows:

struct ContentView: View {
    var body: some View {
        Button("Open SwiftLee") {
            UIApplication.shared.open(URL(string: "https://www.avanderlee.com")!)
        }
        .buttonStyle(.swiftLee) // Directly accessing the static member
    }
}

By making use of the static member swiftLee using the leading dot syntax, we simplified our code while making it more readable too.

Static members have the benefit of making your code more discoverable too. As you can see in the following example, our custom SwiftLee button style is automatically suggested when using the autocompletion functionality:

Static members are shown in autocompletion.
Static members are shown in autocompletion.

Conclusion

Static Member Lookup in generic contexts is a great addition to the Swift language. It allows us to simplify our code while maintaining readability. Custom styles are more discoverable, which makes it easier to find and reuse existing code. This article covered the example of a custom button style, but there are many other cases in which you’ll likely use static members without even realizing it wasn’t possible before.

If you like to learn more tips on Swift, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.

Thanks!