[SwiftUI]正規表現によるネイティブUIでのマルチメディア

# 実現するもの
### 全体のイメージ
![multimedia.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/664005/51ab4ef4-ab76-65f9-e321-af7532c5f1f2.gif)

### 詳しく見ましょう

```speciesData.json
"detailText": [
    "コガタペンギンは保護色があり、青い背中は上から見れば海と同じに見え、白い腹は下の捕食者と獲物を迷惑させることができます。したがって、夜に波から上陸するとき、海岸に打ち寄せる白い波が突然立ち上がって、コガタペンギンの群れになったように見えます。",
    "これはまさにおとぎ話のようで、英語では「フェアリーペンギン(Fairy Penguin)」とも呼ばれています。",
    "<image>02</image>",
    "<caption>夜に帰ってくるコガタペンキンの群れ。画像：Phillipislandtourism / Wikimedia Commons</caption>",
    "コガタペンギンの平均寿命は６〜７年くらいですが、飼育下では20年を超えることもあります。",
]
```

上記のコードで生成されたのはこちら⬇️
<img width="360" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/664005/c12b5d57-6a8a-1e0a-a2e2-f57fe56d8a2a.png">
このように、**文字列（String）**だけのデータをもとに、

| コンテンツ | 記述 |
|:-:|:-:|
| 本文（Text）        |  本文                    |
| 画像（Image）       |  \<image>画像番号\</image>   |
| 画像の説明文（Text)  |  \<caption>説明文\</caption> |
が混在している**マルチメディア**のコンテンツを**ネイティブUI**で自動生成します。

ここは例示なので、コンテンツの種類と記述方法は簡単にカスタマイズできます。

# 実装方法

## 正規表現で記述を認識
正規表現に詳しくなくても大丈夫です。
まず、ある文字列が指定したパターンと一致しているか、を教えてくれるものとして覚えましょう。

例えば、"\<image>"で始まり、"\</image>"で終わる文字列であるか、ということを判断してくれます。

### パターンの定義
```RegularExpression.swift
enum RegexPattern: String {
    case image = "^<image>.*</image>$"
    case caption = "^<caption>.*</caption>$"
}
```
"^\<image>.*\</image>$"というパターンは、
"\<image>"で始まり、"\</image>"で終わることを意味しています。
この部分を書き換えれば、記述方法をカスタマイズできます。

### 一致判断とタグ削除
```RegularExpression.swift
import Foundation

extension String {
    //正規表現の一致判断
    func match(pattern:RegexPattern) -> Bool {
        let pattern = pattern.rawValue
        let regex = try! NSRegularExpression(pattern:pattern)
        return regex.firstMatch(in:self, range:NSRange(self.startIndex..., in:self)) != nil
    }
    
    //両端のタグの削除
    func strip() -> String {
        let lstrip = self.replacingOccurrences(of:"^<\\w+>", with:"", options:NSString.CompareOptions.regularExpression, range:self.range(of:self))
        return lstrip.replacingOccurrences(of:"</\\w+>$", with:"", options:NSString.CompareOptions.regularExpression, range:lstrip.range(of:lstrip))
    }
}
```

ここでは、呼び出しやすくするために、String型のメソッドとして実装します。

## ネイティブUIを生成
```SpeciesDetail.swift
import SwiftUI

struct SpeciesDetail: View {
    var species: Species

    var body: some View {
        ScrollView {
            VStack {
                //文字列ごと処理
                ForEach(self.species.detailText, id: \.self) { text -> AnyView in
                    //imageのパターンと一致した場合
                    if text.match(pattern: .image) {
                        return AnyView(
                            //SwiftUIのImageを利用し、画像コンテンツの表示をカスタマイズ
                            Image("\(self.species.imageName)_\(text.strip())")
                            .resizable()
                            .scaledToFit()
                        )
                    //captionのパターンと一致した場合
                    } else if text.match(pattern: .caption){
                        return AnyView(
                            //SwiftUIのTextを利用し、画像の説明文の表示をカスタマイズ
                            Text(text.strip())
                            .foregroundColor(Color.gray)
                            .font(.custom("Baskerville", size: 16))
                        )
                    //どのパターンとも一致しない場合
                    } else {
                        return AnyView(
                            //SwiftUIのTextを利用し、本文の表示をカスタマイズ
                            Text("　" + text)
                            .padding(.top, 18)
                            .font(.custom("Baskerville", size: 20))
                            .lineSpacing(12)
                            .fixedSize(horizontal: false, vertical: true)
                        )
                    }
                }
            }
            .padding()
        }
    }
}

struct SpeciesDetail_Previews: PreviewProvider {
    static var previews: some View {
        SpeciesDetail(species: speciesData[0])
    }
}
```

# フルコード
以上は自分のアプリの一部として実装しています。
詳しくは[こちら（GitHubへ）](https://github.com/grmagicdog/SpeciesCollection)をご覧ください。