Result Builderは、元となる子要素を組み合わせ、何か一つの親要素を作り上げるのに非常に便利な文法です。
この方法を用いれば、SwiftUIのような書き方で様々な子要素の組み合わせコンポーネントを作成できるようになります。ここではそのメリット、具体的な使い方について説明します。
Result Builderのメリット
例えば、このような画面を表示したいとします。
今はSwiftUIで便利に実装できる部分もありますが、これをNSAttributeString
で丁寧に実装しようとすると、以下のようなコードを書く必要があるかと思われます。
private var richText2: NSAttributedString {
let formerAttributeString = NSMutableAttributedString(string: "We like ")
let latterAttributedString = NSMutableAttributedString(string: "swift")
latterAttributedString.addAttribute(NSAttributedString.Key.underlineStyle,
value: 1,
range: .init(location: 0,
length: latterAttributedString.length))
formerAttributeString.append(latterAttributedString)
return formerAttributeString
}
これがもし、resultBuilderを使った場合、このように書くことができます。
@RichTextBuilder
private var richText: NSAttributedString {
RichText(string: "We like ")
RichText(string: "swift")
.lined()
}
このように可読性が非常に高いカスタムコードが書けることこそ、ResultBuilderの最大のメリットです。
Result Builderを使ってみる
Result Builderは以下のように、enum
で@resultBuilder
修飾子をつけることで使用することが出来るようになります
@resultBuilder
enum RichTextBuilder {
static func buildBlock(_ components: NSAttributedString...) -> NSAttributedString {
let attributedString = NSMutableAttributedString()
for component in components {
attributedString.append(component)
}
return attributedString
}
}
こちらを見ていただくと、個数制限のないcomponentを引数に取り、最終的に一つのアウトプットを出すようなコードになっていることがわかると思います。
さらにここから可読性を上げていくため、typealias
やextension
メソッドを駆使して、さらにSwiftUIのようなシンプルで可読性の高いコードに仕上げていきます。
typealias RichText = NSMutableAttributedString
private extension NSMutableAttributedString {
func lined() -> NSMutableAttributedString {
self.addAttribute(NSAttributedString.Key.underlineStyle,
value: 1,
range: .init(location: 0,
length: self.length))
return self
}
}
以上の二つのコードを追加することで、上記に例示したテキストを、以下のようなコードで表現できるようになります。
@RichTextBuilder
private var richText: NSAttributedString {
RichText(string: "We like ")
RichText(string: "swift")
.lined()
}
Result Builderの応用編
例えば、配列をループさせて値を代入し、特定の場合のみ下線を引く、のような処理を実現したい場合を考えてみます。
完成予想図

まず、配列をループさせるために、result builderに新たな関数を追加します。
private let langs = ["Ruby", "PHP", "Golang", "Swift"]
@RichTextBuilder
private var richText: NSAttributedString {
for lang in langs {
RichText(string: "We like ")
RichText(string: lang)
.lined()
}
}
@resultBuilder
enum RichTextBuilder {
...
// 追加
static func buildArray(_ components: [NSAttributedString]) -> NSAttributedString {
let attributedString = NSMutableAttributedString()
for component in components {
attributedString.append(component)
}
return attributedString
}
}
result builder中にarrayが入った場合の処理をここでは記述しています。
さらに、swiftの場合でのみ下線を引く、のような条件分岐をする際には、以下のようなコードを記述します。
private let langs = ["Ruby", "PHP", "Golang", "Swift"]
@RichTextBuilder
private var richText: NSAttributedString {
for lang in langs {
RichText(string: "We like ")
if lang == "Swift" {
RichText(string: lang)
.lined()
} else {
RichText(string: lang)
}
}
}
@resultBuilder
enum RichTextBuilder {
...
// 二つの関数を追加
static func buildEither(first component: NSAttributedString) -> NSAttributedString {
return component
}
static func buildEither(second component: NSAttributedString) -> NSAttributedString {
return component
}
}
ここで関数を二つ追加しているのは、firstがif-elseのifがtrueの場合、secondがif-elseのifがfalseの場合(つまり、elseに行く場合)の処理を示しており、その両方に対して対処できるようにするためです。
これでほぼ完成ですが、このままだと全ての文章が繋がって見えてしまうため、適切に改行やカンマ、ピリオドを打っていきたいと思います。
句読点を意味する、Punctuations
を以下のように定義します。
enum Punctuations {
case period
case comma
}
そして、こちらをNSAttributeStringへ変換できるようにするため、最終的に以下のようなコードを追加します。
private let langs = ["Ruby", "PHP", "Golang", "Swift"]
@RichTextBuilder
private var richText: NSAttributedString {
for lang in langs {
RichText(string: "We like ")
if lang == "Swift" {
RichText(string: lang)
.lined()
Punctuations.period
} else {
RichText(string: lang)
Punctuations.comma
}
}
}
@resultBuilder
enum RichTextBuilder {
...
// 追加
static func buildExpression(_ expression: Punctuations) -> NSAttributedString {
switch expression {
case .period:
return RichText(".\n")
case .comma:
return RichText(",\n")
}
}
// 追加(上記を実装すると、builder内の全てのコンポーネントがPunctuationsとして扱われてしまうため、
// ここでNSMutableAttributeStringも処理対象であることを明示する。)
static func buildExpression(_ expression: NSMutableAttributedString) -> NSAttributedString {
return expression
}
}
これで、上記の例に示したようなレイアウトを、SwiftUIのような書き方で書けるようになりました。
まとめ
- Result Builderによってカスタムの言語仕様を作ることが出来るようになる。
- 完成したResult Builderはcomputed property、関数、クロージャに使うことができる。
-
buildBlock()
を定義することで、子要素を組み合わせたコンポーネントを可読性高く作成できる。 -
typealias
やextension
を作成することでSwiftUIのような書き方が可能になり、さらに可読性が上げられる。 -
buildEither(first:)
/buildEither(second:)
,buildArray()
,buildExpression()
を用いてループや条件分岐、型に対応することが出来るようになる。
最後に
こちらは私が書籍で学んだ学習内容をアウトプットしたものです。
わかりにくい点、間違っている点等ございましたら是非ご指摘お願いいたします。