avatarSteven Curtis

Summary

The provided content discusses Swift's Result Builders, detailing their use with TupleViews in SwiftUI, the advantages of result builders for creating readable and intuitive code, and how to implement them for constructing HTML content in a "Swifty" way.

Abstract

The article delves into the intricacies of Swift's Result Builders, particularly in the context of SwiftUI. It explains the concept of TupleViews and how they are used to manage multiple views within SwiftUI. The author emphasizes the importance of understanding tuples, TupleViews, and the @resultBuilder attribute to fully grasp how views like VStack are implemented. The article also explores the role of ViewBuilder as a result builder that enables a declarative syntax for arranging child views. Furthermore, it illustrates how to use result builders to construct HTML content in a more natural Swift style, showcasing the benefits of domain-specific language syntax. The author provides examples and code snippets to demonstrate the creation of a custom result builder for HTML elements, highlighting the advantages of result builders in complex scenarios, such as declarative syntax, conditional and optional building, consistency with SwiftUI, encapsulation, and the ability to nest builders.

Opinions

  • The author believes that result builders are essential for creating code that is both readable and intuitive, especially when working with SwiftUI.
  • They suggest that using result builders allows for a more "Swifty" way of defining content, which is particularly advantageous when constructing HTML.
  • The author posits that result builders are not just syntactic sugar, but a powerful tool for encapsulating logic and managing complex structures in a declarative manner.
  • They imply that the use of result builders can lead to code that is more consistent with the design principles of Swift and SwiftUI.
  • The author seems to value the community's engagement and learning, inviting readers to reach out with questions and offering the opportunity to support their work through a "buy me a coffee" link.

Swift’s Result Builders

TupleViews and more!

Photo by Emily Morter on Unsplash

Difficulty: Beginner | Easy | Normal | Challenging

This article has been developed using Xcode 14.2, and Swift 5.7.2

Prerequisites:

You need to be able to code in Swift, perhaps using

Playgrounds and be able to use SwiftUI

Terminology

@resultBuilder: An attribute that transforms a sequence of statements into a single combined value

TupleView: A View created from a swift tuple of View values

Tuple: A group of zero or more values represented as a single compound value

Type: A representation of the type of data that can be processed, for example Integer or String

Variadric parameters: A parameter for a function that accepts zero or more values of the specified type

Result builders: The prerequisites

We need to understand a number of things to be able to fully understand

Tuples…from the beginning

I’ve already written about Tuples in a previous article) as they allow you to store multiple values in a single variable.

Something like var person = (“Steve”, 22) will do it in code. As you can see two elements are stored in the property called person.

TupleView (represents a view)

A TupleView is a concrete SwiftUI View type that stores multiple View.

It is used internally by SwiftUI when combining views but developers typically do not interact with this type directly.

However you *can* use a TupleView and you might create a ContentView that shows the Words “Hello” and “World” on two different lines as they are contained within a VStack.

struct ContentView: View {
    var body: some View {
        VStack {
            TupleView((Text("Hello"),Text("World")))
        }
    }
}

A look at VStack

We can use TupleView and @resultBuilder to look at VStack (and how similar views) work under the hood. This also gives a first opportunity to look at result builders.

A VStack vertically arranges child views.

ViewBuilder’s role

ViewBuilder is itself a result builder that allows the declarative syntax where views can be listed within such a view without needing separators.

TupleView

TupleView holds multiple child views in such a way that they can be stored without knowing how many views there would be in advance.

In effect SwiftUI wraps the views in a VStack internally in order manage them as a single entity. Kind of an implementation detail, but good to know.

In conclusion

When you put multiple views inside a VStack the result builder allows you to do so without separators (actually sitting behind this is ViewBuilder).

Essentially ViewBuilder transforms multiple views inside a VStack to a single view or a hierarchy of views.

A result builder is *what* now?

A result builder is a type that defines a set of static methods to construct values from a sequence of components.

The point is to make code readable and to use a declarative style.

We can say that result builders enable a domain-specific language style of coding where code blocks get transformed into rich data structures.

The HTML example

We can use result builder to construct HTML content in a “Swifty” way.

protocol HTMLElement {
    var render: String { get }
}

struct Div: HTMLElement {
    let content: String
    var render: String {
        return "<div>\(content)</div>"
    }
}

struct P: HTMLElement {
    let content: String
    var render: String {
        return "<p>\(content)</p>"
    }
}

struct A: HTMLElement {
    let href: String
    let content: String
    var render: String {
        return "<a href=\"\(href)\">\(content)</a>"
    }
}

There are a few ways you might render out the HTML. The simplest is perhaps String concatenation and might look something like:

let div = Div(content: "This is enclosed in div tags.")
let p = P(content: "This is enclosed in paragraph tags.")
let a = A(href: "https://www.example.com", content: "This is a link")

let htmlContentConcat = div.render + p.render + a.render
print(htmlContentConcat)

Which is *fine*. There is nothing *wrong* with this at all. However we can use domain-specific syntax which is offered by result builders as a way to define the content in a more Swifty way.

Using functions and static methods

For simple examples we can use functions and static methods. However notice in this example we are forced to use separators for our list of HTML statements.

struct HTMLBasicBuilder {
    static func buildBlock(_ elements: HTMLElement...) -> String {
        return elements.map { $0.render }.joined()
    }
}

let htmlBuilder = HTMLBasicBuilder.buildBlock(
        Div(content: "This is enclosed in div tags."),
        P(content: "This is enclosed in paragraph tags."),
        A(href: "https://www.example.com", content: "This is a link")
)

In this instance though you can argue that functions and static methods work fine. We will come onto further advantages or result builders further into this article.

A result builder

Using the content from above (the HTML stuff) we can construct a result builder that takes n elements and returns a String. We do not need to know how many HTML elements are used in advance because we a variadic parameter.

In order to be a result builder we must satisfy the following two requirements

- It must be annotated with the @resultBuilder attribute, which indicates that it is intended to be used as a result builder type and allows it to be used as a custom attribute

- It must supply at least one static buildBlock result-building method

This gives us the following code:

@resultBuilder
struct HTMLBuilder {
    static func buildBlock(_ elements: HTMLElement...) -> String {
        return elements.map { $0.render }.joined()
    }
}

Which is then accessed through a utility function (which dies to SOC we keep separate from the result builder itself).

func createHTML(@HTMLBuilder _ content: () -> String) -> String {
    return content()
}

We can then use this createHTML function to generate our HTML code!

let htmlContent = createHTML {
    Div(content: "This is enclosed in div tags.")
    P(content: "This is enclosed in paragraph tags.")
    A(href: "https://www.example.com", content: "This is a link")
}

Which returns the following (if printed to the console):

<div>This is enclosed in div tags.</div><p>This is enclosed in paragraph tags.</p><a href="https://www.example.com">This is a link</a>

Nice!

The advantages of result builders

Result builders really shine in more complex situations. Let’s look at more abstract reasons why you might just use result builders in your project.

Declarative syntax

I think one of the main advantages of result builders is using declarative syntax. This allows us to create readable and intuitive code.

Conditional and Optional Building

Result builders provide built-in methods to handle conditionals (buildEither(first:), buildEither(second:)) and optionals (buildOptional(:)). This makes it easier to conditionally include or exclude parts of the content based on certain conditions without breaking the flow of the DSL.

I’ll cover this in an article about ViewBuilder.

Consistency

We are able to craft code that looks great alongside SwiftUI code.

Encapsulation

With result builders, the logic for constructing the final output is encapsulated within the builder.

Nested builders

Result builders can be nested and this allows for complex structures to be build in a declarative manner.

Conclusion

Phew. That’s it for result builders for now. Helpful? I hope so!

I’d love to hear from you if you have questions for me!

You might like to help me out by buying me a coffee on https://www.buymeacoffee.com/stevenpcuri.

Swift
Swiftui
Software Development
Software Engineering
Recommended from ReadMedium