【SwiftUI】文字を表示するベストプラクティス（Text・Label / TextEditor・TextField）

SwiftUIにはテキストを表示する方法がいくつかあります。
今回はそれぞれの使い分けや使い勝手を考えていきます。

# 各コンポーネントのできること

|　| 編集 |　コピー |　選択 |　アイコン |
|---|---|---|---|---|
|Text  | × | ⚪︎ |×  |×  |
|Label  |×  |⚪︎  |×  |⚪︎ |
|TextEditor  |⚪︎  |⚪︎ |×  |× |
|TextField  |⚪︎  |⚪︎  |×  |× |

まず入力があるかないかで大きく分かれます。細かく使い方を見ていきます。

# TextEditorとTextFieldの使い分け
TextFieldは改行できないため、改行して入力が必要な場合はTextEditorを使う必要があります。

## TextEditor
複数行のスクロール可能なテキストを表示および編集できます。

```swift
struct TextEditingView: View {
    @State private var text: String = ""

    var body: some View {
        TextEditor(text: $text)
    }
}
```

文字の色やフォントなどは、Textでも使用している`forgroundStyle`や`font`で設定できます。
1つ注意点はプレースホルダーの機能はないため自分で実装する必要があります。

```swift
// allowsHitTesting(false)をつけないとプレースホルダーの部分はタップできなくなるので注意です
struct TextEditingView: View {
    @State private var text: String = ""

    var body: some View {
        TextEditor(text: $text)
            .overlay(alignment: .topLeading) {
                if text.isEmpty {
                    Text("プレースホルダー")
                        .allowsHitTesting(false)
                }
            }
    }
}
```

`ZStack`でも問題ないですが、私はこちらの方が好きです。

[参考](https://developer.apple.com/documentation/swiftui/texteditor)

## TextField
1行の文字入力に適しており、UIKitのUITextFieldに近いです。
上記で指摘したプレースホルダーも標準で設定することができ、`SecureField`を使用すればセキュアな入力もできます。

```swift
struct TextEditingView: View {
    @State private var username: String = ""

    var body: some View {
        TextField(
            "User name (email address)",
            text: $username
        )
    }
}
```

複数行に対応する必要がない場合は、TextFieldを使用する方がいいでしょう。

# LabelとTextの使い分け
TextとLabelではアイコンが設定できるかどうかが主な差分です。

## Label
テキスト＋アイコンで構成される標準的なUI。

```swift
// 両方表示
Label("Lightning", systemImage: "bolt.fill")

// タイトルだけ
Label("Lightning", systemImage: "bolt.fill")
    .labelStyle(.titleOnly)

// アイコンだけ
Label("Lightning", systemImage: "bolt.fill")
    .labelStyle(.iconOnly)
```

`LabelStyle`を定義することで余白などより柔軟なレイアウト変更が可能。

```swift
// アイコンとラベルのスペースを横幅いっぱいに広げる
struct Test1LabelStyle: LabelStyle {
    func makeBody(configuration: Configuration) -> some View {
        HStack {
            configuration.icon
                .scaledToFit()
                .frame(width: 24, height: 24)
            Spacer()
            configuration.title
                .font(.title)
                .frame(maxWidth: .infinity, alignment: .trailing)
        }
    }
}

// アイコンとラベルの位置を反転させる
struct Test2LabelStyle: LabelStyle {
    func makeBody(configuration: Configuration) -> some View {
        HStack(spacing: 8) {
            configuration.title
                .font(.title)
                .frame(maxWidth: .infinity, alignment: .leading)
            configuration.icon
                .scaledToFit()
                .frame(width: 24, height: 24)
        }
    }
}
```
このオプションのところのようなレイアウトも再現できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/305854/04d94660-4bd0-5570-1b1d-6ae64445cd47.png" width=50%>


## Text
テキストのみを表示する標準的なUI。

```swift
Text("Hamlet")
```

ここまで見てもLabelの有効な活用があまり見えてきません。

## Human Interface Guidline
ガイドラインをチェックしてみました。

参考： https://developer.apple.com/design/human-interface-guidelines/labels

**Labels - Best practices**

> Use a label to display a small amount of text that people don’t need to edit. If you need to let people edit a small amount of text, use a text field. If you need to display a large amount of text, and optionally let people edit it, use a text view.


> 編集する必要のない少量のテキストを表示するには、ラベルを使用します。少量のテキストを編集させる必要がある場合は、テキストフィールドを使用します。大量のテキストを表示し、任意で編集させる必要がある場合は、テキストビューを使用します。

**Developer note**
> To display uneditable text, SwiftUI defines two components: Label and Text.

> 編集不可能なテキストを表示するために、SwiftUI は2つのコンポーネントを定義します：ラベルとテキストです。

ここで出てくるLabelというのはコンポーネントのことではなく、おそらく⬇︎の総称。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/305854/23b9fb18-8747-27b9-8e68-5579b528318b.png" width=50%>

Labelはどういう時に使うのがより有効的なのか考えてみます。

## Labelの有効な使い方
私が思いついたLabelの有用な使い道です。

- サービスのデザインルールとして保存ボタンが`星アイコン＋保存`で表現されている場合

場所によっては文字だけやアイコンだけ表示することもあると思います。
この3パターンある保存ボタンを共通のコンポーネントとして実装するのにLabelは最適です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/305854/cb6c89c2-fc8f-e3de-bae9-26af775b8417.png" width=50%>

## 残る問題
SwiftUIのコンポーネントには文字列の部分選択をできるコンポーネントは存在しません。
そのため、もし文字列の部分選択を実現したい場合はUIKitで対応するしかありません。意外に不便に思います。