SwiftUIでTextのサイズや位置を正確に調整するならUIFontが必要。※2023/3時点

# 第1話 サイズ違いのTextを下揃えで並べるとずれる!？
次のようの要件で画面をSwiftUIで組んだ際のお話です。
### 要件 ~ストップウォッチ風~
- 小数点以下第二位まで表示。
- 小数点以下を少し小さく。

脳筋で並べるとこんな感じ。
```swift
HStack(alignment: .bottom, spacing: 0) {
    Text("22")
        .font(.system(size: 200))
        .monospacedDigit()
    Text(".22")
        .font(.system(size: 160))
        .monospacedDigit()
}
```

<img width="500" alt="before" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/750891/d9d3a90f-abf3-5fc3-678a-789972e2455c.png">

そうすると上のスクリーンショットのように、**下が揃いません💦**
どうやら、`Textの下`と`数字の下`とは別のようです。
じゃ **下を合わせる** とはいかに、、、
Fontの構造を知ると解決できたので、詳しく紹介していきます。

## Fontの構造
下記のようにFontは様々な要素で構成されています。

<img width="500" alt="構造" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/750891/ce543198-5ae8-cc3c-a309-849e9d612994.png">

参照元
https://developer.apple.com/library/archive/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/CustomTextProcessing/CustomTextProcessing.html

### UIFontを使うと必要な値が取得できる。
UIFontには各値を取得できるプロパティが用意されていて主に下記の値を見ていくと理想のレイアウトが組めそうです。
※2023/3現在はSwiftUI.Fontからは取得できなさそうです。

| 変数名 |説明  |
| :--- | :--- |
| **pointSize** | いわゆるfontSize |
| **lineHeight** | UILabelやTextのheightに相当 |
| **ascender** | Baselineから上 |
| **capHeight** | ローマ字の大文字の高さ |
| **xHeight** | ローマ字の小文字のメインの高さ |
| **descender** | Baselineから下 |

※注意: 右側の説明はただの私の脳内翻訳なので、適切な表現ではないかも。


## 上記の値でズレがどうなっているのか確認してみた。
- 元の2つのTextに対してUIFontを使ってFont指定。
- 各プロパティを視覚的に捉えるためのViewを並べてみる。

<img width="800" alt="細分化" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/750891/c9cd45e5-7340-e04c-6e87-55be8b11828a.png">

### 要件にあった実装方針が見えてきました。
- **下を合わせたい** とは **Baselineを揃えたい** ことだった。
- 今回は`descender`を使った`offset.y`の操作でうまくいきそう。
- 大きさの比率に明確な指定がない場合は **.22** の`capHeight`が **22** の`xHeight`にあたる高さになると良さそう。（おおよそローマ字の大文字と小文字の関係）

## 最終形

```swift
    let mainFont: UIFont = .systemFont(ofSize: 200)
    var subFont: UIFont { 
        .systemFont(ofSize: mainFont.xHeight * mainFont.pointSize / mainFont.capHeight)
    }

    var body: some View {
        HStack(alignment: .bottom, spacing: 0) {
            Text("22")
                .font(.init(mainFont))
                .monospacedDigit()
                .offset(y: -mainFont.descender) // mainFont.descender分下げる
            Text(".22")
                .font(.init(subFont))
                .monospacedDigit()
                .offset(y: -subFont.descender) // subFont.descender分下げる
        }
        .offset(y: mainFont.descender) // 全体的にmainFont.descender分上げる
    }
```

<img width="500" alt="after" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/750891/58ed3b4b-4209-292b-4fd4-3c6d40a60cd7.png">


綺麗に下が合いました♪
 
## [追記: 2022/3/13] HStack(alignment: .lastTextBaseline)で実現可能！！

twitterにてもっといい方法を教えていただきました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">この例だとHStack(alignment: .lastTextBaseline)としても揃えられますよ</p>&mdash; せみさぎ (@semisagi) <a href="https://twitter.com/semisagi/status/1635229070320635904?ref_src=twsrc%5Etfw">March 13, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

`VerticalAlignment`にはfirstTextBaselineとlastTextBaseline
Baselineを揃えるだけならこちらの方法を使うとSwiftUIのみでの実装が可能でした！！
| firstTextBaseline | lastTextBaseline |
| --- | --- |
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/750891/c3d51f52-293a-b5df-9b81-8aa13cf8fc77.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/750891/072baa50-01a2-cb2b-a407-51f441880b66.png) |
| https://developer.apple.com/documentation/swiftui/verticalalignment/firsttextbaseline | https://developer.apple.com/documentation/swiftui/verticalalignment/lasttextbaseline |

これを使うと
```swift
HStack(alignment: .lastTextBaseline, spacing: 0) {
    Text("22")
        .font(.system(size: 200))
        .monospacedDigit()
    Text(".22")
        .font(.system(size: 160))
        .monospacedDigit()
}
```
これでも実現できてしまう！！
せみさぎさんに感謝です🙏


# 第2話 Figma上で指定したlineHeightの値を使うと大きさが合わない！？
せっかく正確な値でデザインしてもらってもSwiftUIでそのまま値を使うとうまくいきませんでした。
そもそもSwiftUIではlineHeightを指定することはできず、lineSpacingを設定することになります。

### SwiftUI.Text.lineSpaceとは
`ある行の下端と次の行の上端の間のスペースの量 (ポイント単位)`

参考元
https://developer.apple.com/documentation/swiftui/view/linespacing(_:)

またも脳筋でそのまま計算するとこうなりそう。
| Figma  | SwiftUI |
| :--- | :--- |
| **Size**: 200 | **Size**: 200 |
| **Line height**: 240 | **lineSpacing**: 240 - 200 = 40 |


そうすると下記のようにずれます。

<img width="500" alt="before" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/750891/fcece3bd-1657-9ebd-f59a-3e2db1c6bf6b.png">

青・・・Figma
黒・・・SwiftUI.Text

この差は元のlineHeightがpointSizeとは違うことを考慮していなかったため起きていたずれでした。
`lineHeight` > `pointSize` なので
`lineHeight` + 40 > `pointSize` + 40（FigmaのlineHeight: 240）こうなってしまうのでした。

ではFigmaのlineHeightを正しく設定するにはどうするのか。
ここでもUIFontの登場です。

## UIFontで元のlineHeightからの差分をlineSpacingにする。
これが正解です。
`lineSpacing = Figma.lineHeight - UIFont.lineHeight`

<img width="500" alt="after" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/750891/d6498650-e170-4792-dcfc-4408ab6315e8.png">

綺麗に隙間があいました♪


## だがそれだけじゃない
**lineSpacing**は行間・・・二行だとlineSpacing**1つ分**
**lineHeight**は行高・・・各行の上下にlineSpacing1つ分、合計**2つ分**
つまり**leneHeight**を設定する場合は**行間** + **上下のpadding** も増やす必要があります。

特に単行の場合は**lineSpcing**を設定しても何も変化はありませんが、**lineHeight**を設定すると全体の高さが変わリます。
下図のように、単行・複行に限らず上下の**padding**による拡張が必要だとわかります。

<img width="800" alt="比較" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/750891/eac33393-b5de-7530-40af-da01e86d91f0.png">


## 結論

```swift
    let lineHeight: CGFloat = 240 // figmaのLine height
    let uiFont: UIFont = .systemFont(ofSize: 200)
    var lineSpacing: CGFloat { lineHeight - uiFont.lineHeight }
    var body: some View {
        Text("Happy\nHappy")
            .font(.init(uiFont))
            .lineSpacing(lineSpacing)
            .padding(.vertical, lineSpacing/2)
    }
```

### 以上!!

現状はUIFontでしか、こうした微調整ができません。
今後SwiftUI.FontやTextに生えるメソッドを使って同じような調整ができるようになって欲しいですね。

参考記事
https://developer.apple.com/library/archive/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/CustomTextProcessing/CustomTextProcessing.html
http://akisute.com/2016/09/ios.html
http://blog.eppz.eu/uilabel-line-height-letter-spacing-and-more-uilabel-typography-extensions/