25
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-03-13

第1話 サイズ違いのTextを下揃えで並べるとずれる!?

次のようの要件で画面をSwiftUIで組んだ際のお話です。

要件 ~ストップウォッチ風~

  • 小数点以下第二位まで表示。
  • 小数点以下を少し小さく。

脳筋で並べるとこんな感じ。

HStack(alignment: .bottom, spacing: 0) {
    Text("22")
        .font(.system(size: 200))
        .monospacedDigit()
    Text(".22")
        .font(.system(size: 160))
        .monospacedDigit()
}
before

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

Fontの構造

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

構造

参照元
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を並べてみる。
細分化

要件にあった実装方針が見えてきました。

  • 下を合わせたい とは Baselineを揃えたい ことだった。
  • 今回はdescenderを使ったoffset.yの操作でうまくいきそう。
  • 大きさの比率に明確な指定がない場合は .22capHeight22xHeightにあたる高さになると良さそう。(おおよそローマ字の大文字と小文字の関係)

最終形

    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分上げる
    }
after

綺麗に下が合いました♪

[追記: 2022/3/13] HStack(alignment: .lastTextBaseline)で実現可能!!

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

VerticalAlignmentにはfirstTextBaselineとlastTextBaseline
Baselineを揃えるだけならこちらの方法を使うとSwiftUIのみでの実装が可能でした!!

firstTextBaseline lastTextBaseline
image.png image.png
https://developer.apple.com/documentation/swiftui/verticalalignment/firsttextbaseline https://developer.apple.com/documentation/swiftui/verticalalignment/lasttextbaseline

これを使うと

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

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

before

青・・・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

after

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

だがそれだけじゃない

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

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

比較

結論

    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/

25
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?