🎨

[Swift] AttributedStringで該当する部分全てを修飾する

2021/09/11に公開

iOS15から使えるAttributedStringサンプルコードにこういうのがあります。

var attributedString = AttributedString("This is a basic string that contains a link.")
let range = attributedString.range(of: "link")!
attributedString[range].link = URL(string: "https://www.example.com")

ところが、linkという文字列が複数ある場合はこの方法では動きません。rangeは連続するインデックスしか扱えないので、複数の部分を一度に扱うことはできないからです。

解決方法

以下の通りでいけます。

// attributedStringをsubstringに変換する
var substring = attributedString[attributedString.startIndex ..< attributedString.endIndex]
while let range = substring.range(of: "key") {
    // ここでattributedString[range]の処理を行う
    // 処理した部分の後の部分をsubstringに代入する
    substring = attributedString[range.upperBound...]
}

extensionにしておきましょう。

extension AttributedString {
    func ranges<T: StringProtocol>(of stringToFind: T) -> [Range<AttributedString.Index>] {
        var ranges: [Range<AttributedString.Index>] = []
        var substring = self[self.startIndex ..< self.endIndex]
        while let range = substring.range(of: stringToFind) {
	    ranges.append(range)
            substring = self[range.upperBound...]
	}
        return ranges
    }
}

これでいつでも全rangeを取れるようになったので、あとは煮るなり焼くなりお好きにしてください。

Discussion