Open2

SwiftUI: Text()の文中URLをクリッカブルにする

kabeyakabeya

SwiftUIのアプリで、メモ欄かなんかを作っていて、URLがそこに含まれていたらその部分をクリッカブル(クリックしたらSafariでそのページが開く)ようにしたいときがあります。

struct ContentView: View {
    var body: some View {
        VStack {
            let text = "こんなページがあった! https://zenn.dev"
            
            Text("\(text)")
            Text(text)
            Text(LocalizedStringKey("\(text)"))
            Text(LocalizedStringKey(text))
            
            Divider()
            let markdown = "[こんなページがあった!](https://zenn.dev)"
            
            Text("\(markdown)")
            Text(markdown)
            Text(LocalizedStringKey("\(markdown)"))
            Text(LocalizedStringKey(markdown))
            
            Divider()
            
            Text(getMarkdownString(text))
            Text(getMarkdownString(markdown))
        }
        .padding()
    }
    
    func getMarkdownString(_ text: String) -> AttributedString {
        do {
            return try AttributedString(markdown: text)
        }
        catch {
            return AttributedString(text)
        }
    }
}

まず、Text+LocalizedStringKeyがマークダウン記法を解釈します。
上記の例を見ると、TextだけとかLocalizedStringKeyに埋め込み文字列を渡すとかするとうまく行かないということが分かります。

埋め込み文字列("\(markdown)"みたいなの)は、Stringではなくて、StringInterpolationです。
StringInterpolationは、書く側はシンプルに書いてますが、実行時には変数の型に合わせて文字列にフォーマットする呼び出しが行われます。なのでそれの呼び出しと、マークダウン記法の解析処理が併用されないのではないかと想像します。

マークダウン記法で[表示文字列](URL)のように書くと表示文字列だけが表示されるのですが、マークダウン記法がなくても、URL文字列は勝手にクリッカブルになるということも分かります。

ただし使うのがLocalizedStringKeyなので、入力した文字列が普通に翻訳リソースとバッティングしてしまうとおかしなことになります。

そこで、サンプルコードの最後の2個のAttributedStringを使う方法が良いのではないかと思います。

ちなみにいずれにしても、使用可能なマークダウン記法について公式ドキュメントが見当たらない(探せてない)のが難点です。

↓のものがある程度合致してるんじゃないかと思ってはいますが。

https://developer.apple.com/library/archive/documentation/Xcode/Reference/xcode_markup_formatting_ref/index.html#//apple_ref/doc/uid/TP40016497-CH2-SW1

kabeyakabeya

AttributedStringを使う場合

let options: AttributedString.MarkdownParsingOptions = .init(allowsExtendedAttributes: true, interpretedSyntax: .inlineOnlyPreservingWhitespace, appliesSourcePositionAttributes: true)
return try AttributedString(markdown: markdown, options: options)

のように明示的にオプションを指定しないと、改行とかなくなってしまうようですね。