[SwiftUI] SwiftUI の layout システムを理解する (Text)

SwiftUI2021

     

TAGS:

SwiftUI での Text のレイアウトを調べてみました。(ちょっと長いです)

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Monterey 12.1 RC
  • Xcode 13.2 RC
  • iOS 15.2

Text の中の文字列の 配置方法

VStack, Text, Color を使って、SwiftUI での要素のレイアウト方法を確認しました。
SwiftUI2021[SwiftUI] SwiftUI の layout システムを理解する (準備編: border と GeometryReader )
SwiftUI2021[SwiftUI] SwiftUI の layout システムを理解する (基礎編: レイアウトシーケンス)
SwiftUI2021[SwiftUI] SwiftUI の layout システムを理解する ( .frame の働き)
SwiftUI2021[SwiftUI] SwiftUI の layout システムを理解する (push-out タイプビュー, pull-in タイプビュー とレイアウトの調整)
SwiftUI2021[SwiftUI] SwiftUI の layout システムを理解する (push-out タイプビューの調停)
SwiftUI2021[SwiftUI] SwiftUI の layout システムを理解する (コンテナビューに含まれる push-out / pull-in タイプビューの調停)
SwiftUI2021[SwiftUI] SwiftUI の layout システムを理解する (領域が不足するケースの調停)

ビューの配置がおおよそ理解できたところで、よくつまづきそうな Text の文字列の配置方法を確認してみました。

特に、領域が足りない時の挙動を確認していきます。

準備:Text の高さを測る

.frame で height 指定する時に、height の数値と 文字列の1行の高さの関係がポイントとなる時がありますので、事前に測っておきます。

TextHeight

測定してみたところ、iPhone11のシミュレータで、 フォントサイズ body の Text は、高さが 20.5 でした。

確認のために、以下のコードを使用しました。


//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2021/12/10
//  © 2021  SmallDeskSoftware
//

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Long long Text")
            .overlay {
                GeometryReader { geom in
                    Text("\(geom.size.height)")
                        .offset(x: 0, y: 100)
                }
            }
    }
}

Text の文字列配置(無指定時)

幅 高さ いずれも指定していない時の配置を確認します。

通常は1行で表示しようとしますが、おさまらなければ、複数行にわたって表示します。

SuperLongText

使用したコードは以下のコードです。


//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2021/12/10
//  © 2021  SmallDeskSoftware
//

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Long long long long long long long long long long long long Text")
    }
}

行数指定

Text には .lineLimit という View Modifier を適用することができ、Text として 何行までの表示を許すかを制御できます。

Apple のドキュメントは、こちら

タイプは、Int? で、デフォルトでは、nil になっています。最大行数を指定します。nil を指定すると、行数は無制限という意味になります。

試しに、.lineLimit(1) として、1行となるように指定してみます。(領域を確認するために、border に .red を指定しています)

LongTextIn1Line

1行では表示しきれないために、行末が … (truncate表示)に変更されていることがわかります。


//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2021/12/10
//  © 2021  SmallDeskSoftware
//

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Long long long long long long long long long long long long Text")
            .lineLimit(1)
            .border(.red)
    }
}

幅指定

.frame を使用して、幅のみを指定してみます。

.frame で幅のみを指定すると、指定幅で表示しきれない時には 行数が増え、高さが増えていくことになりました。

WidthText

以下のコードで確認しています。


//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2021/12/10
//  © 2021  SmallDeskSoftware
//

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Long long long long long long long long long long long long Text")
            .frame(width: 200)
            .border(.red)
    }
}

高さ指定

.frame を使用して、高さのみを指定してみます。

現在つかっている文字列は1行では表示しきれない大きさが必要となるので、高さ指定に “1行分の高さ以下” と “2行分の高さ以下”、”5行分の高さ以下” を指定していました。(1行の高さは、20.5 でしたので、 15, 40, 120 を指定しました。ただし、20.5 には spacing 分は含まれていません)

HeightText

高さ 15 (1行分に満たない高さ)指定の結果からは、表示に十分でない高さを与えても、Text は1行は表示するということがわかります。(親ビューが Clip してしまうと、見切れてしまうことになります。)

高さ 40 (2行分に満たない高さ)指定の結果からは、2行表示に十分な高さがない場合は、1行しか表示しないことがわかります。おそらく、N+1行に十分な高さがなければ、N行表示になると思われます。

高さ 120 (表示に十分な行数の高さ以上の高さ)指定の結果からは、これまでの理解通り、必要以上の高さを使わずに、領域の高さ中央に配置されていることが確認できます。

以下のコードで確認しています。


//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2021/12/10
//  © 2021  SmallDeskSoftware
//

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Long long long long long long long long long long long long Text")
            .frame(height: 15)
            .border(.red)
        Text("Long long long long long long long long long long long long Text")
            .frame(height: 40)
            .border(.red)
        Text("Long long long long long long long long long long long long Text")
            .frame(height: 120)
            .border(.red)
    }
}

幅高さ指定

.frame を使用して、幅と高さを指定してみます。

.frame で設定する高さは、表示行数に影響を与えました。幅を同時指定する時には、幅から暗に指定される1行文字数と高さから暗に指定される行数で制御されることが予想できます。

高さは 前回と同じ3種類指定、幅は 200 で指定してみました。

WidthHeightText

height 指定から来る制約が、width 指定により横幅が狭くなった状態で適用されていることがわかります。

具体的には、
高さ 15 (1行分に満たない高さ)指定からは、表示に十分でない高さを与えても、Text は1行で表示します。幅も制限されているので、… 表示前の文字数は少なくなっています。

高さ 40 (2行分に満たない高さ)指定からは、幅指定が加えられても 2行表示に十分な高さはありませんので、1行しか表示されません。

高さ 120 (表示に十分な行数の高さ以上の高さ)指定からは、幅指定により必要な行数が2行が3行に増えていますが、3行表示に十分な高さがありますので、そのまま表示されていることがわかります。

以下のコードで確認しています。


//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2021/12/10
//  © 2021  SmallDeskSoftware
//

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Long long long long long long long long long long long long Text")
            .frame(width: 200, height: 15)
            .border(.red)
        Text("Long long long long long long long long long long long long Text")
            .frame(width: 200, height: 40)
            .border(.red)
        Text("Long long long long long long long long long long long long Text")
            .frame(width: 200, height: 120)
            .border(.red)
    }
}

ここまでの考察まとめ

ここで Text のレイアウトを少しまとめてみます。

.lineLimit は、最大行数を制約するものです。レイアウト的には 最大行数はそのまま最大高さを意味することになります。実際の1行の高さは、フォントの大きさ等に依存しますが、.lineLimit は、行数で指定する View Modifier です。

.frame は 幅と高さを指定する View Modifier です。

ただし、Text はできるだけ文字を表示しようと可能であれば複数行表示をしようとしますので、幅だけ もしくは 高さだけ を指定すると、指定されていない側を変更して 文字をレイアウトしようとします。

例えば、幅を(せまくなるように)制約されると、複数行を使って表示しようとします。つまり、高さを高くしようとします。

高さは少し特殊で、必ず1行は表示されますが、2行目以降は 表示するために十分な高さがあるかどうかで判断されます。

.lineLimit、.frame いずれの指定でも、文字列がすべて表示しきれない時には、文字列の表示しきれない箇所が “…” に置き換わります。(truncate と呼ばれる要素です)

.frame と .lineLimit との組み合わせ

.frame と .lineLimit を組み合わせて使用してみます。

.frame と .lineLimit で矛盾が発生しそうなケースを確認してみます。

lineLimit は高さを許容し、.frame は 高さを抑えるケース

具体的には、.lineLimit は2行を許可するが .frame は 高さ 10 が指定されているケースを確認しました。

lineLimit2Frame10

.frame で 高さ 10 になっている領域で、.lineLimit で2行が許容されても 配置できないので、1行になっていると考えられます。

テストしたコードは以下です。


//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2021/12/10
//  © 2021  SmallDeskSoftware
//

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long Text")
            .lineLimit(2)
            .frame(height: 10)
            .border(.red)
    }
}

.lineLimit と .frame の順番を変えてみました。

Frame10LineLimit2

先ほどと同じ結果に見えます。

こちらは、”最大2行で”という制約が来るのですが、そもそも .frame により 1行しか描画できないので、このような結果になっていると考えられます。

このことは、順番を入れ替えても同じということではなく、それぞれの順番で適用していったところ結果が同じになったと考えるのが妥当です。

テストしたコードは以下です。


//
//  ContentView.swift
//
//  Created by : Tomoaki Yagishita on 2021/12/10
//  © 2021  SmallDeskSoftware
//

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long Text")
            .frame(height: 10)
            .lineLimit(2)
            .border(.red)
    }
}

そのほかのパターンも調べてみたのですが、それぞれが、これまでの延長線上の結果になりました。

つまり、指定した Modifier を 指定した順番で適用していくとそうなるであろうレイアウトでした。

言い方を変えると、Text が文字列を適切に表示するために特殊なロジックを内部に持っているのではなく、用意されている View Modifier がそれぞれに処理をしてレイアウトがされるという なんとも当たり前のことを確認できただけでした・・・・

関係しそうな View Modifier

以下の View Modifier をどう指定するかによってもレイアウトが変わるケースがありますが、今回は対象外としています。

  • allowsTightening
  • minimumScaleFactor
  • truncationMode
  • lineSpacing

まとめ: Text のレイアウト

Text のレイアウト
  • 文字が表示しきれない時は、truncate 表示(…) がされる
  • .lineLimit を使うと、最大表示行数を制御できる
  • .frame を使うと、できるだけ文字を表示しようと回り込み表示がされる
  • .frame で高さが制限されると、表示可能な行数で表示を行う(ただし、最低1行は使用する)
  • truncate 表示は、View Modifier( .truncate) で文字集のどの位置を省略表示できるか制御できる

SwiftUI2021[SwiftUI] SwiftUI の layout システムを理解する (準備編: border と GeometryReader )
SwiftUI2021[SwiftUI] SwiftUI の layout システムを理解する (基礎編: レイアウトシーケンス)
SwiftUI2021[SwiftUI] SwiftUI の layout システムを理解する ( .frame の働き)
SwiftUI2021[SwiftUI] SwiftUI の layout システムを理解する (push-out タイプビュー, pull-in タイプビュー とレイアウトの調整)
SwiftUI2021[SwiftUI] SwiftUI の layout システムを理解する (push-out タイプビューの調停)
SwiftUI2021[SwiftUI] SwiftUI の layout システムを理解する (コンテナビューに含まれる push-out / pull-in タイプビューの調停)
SwiftUI2021[SwiftUI] SwiftUI の layout システムを理解する (領域が不足するケースの調停)

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

SwiftUI 学習におすすめの本

SwiftUI 徹底入門

SwiftUI は、グラフィカルなライブラリということもあり、文字だけのテキストよりは、画像が多く入れられた書籍を読むと理解が進みやすいです。

自分で購入した中でおすすめできるものとしては、以下のものです。

2019 年発表の SwiftUI 1.0 相当を対象にしているので、それ以降に追加された一部の機能は、説明されていません。

ですが、SwiftUI 入門書としては、非常によくできていますし、わかりやすいです。 この本で学習した後に、追加分を学習しても良いと思います。

SwiftUIViewsMastery

英語での説明になってしまいますが、以下の本もおすすめです。

1ページに、コードと画面が並んでいるので、非常にわかりやすいです。

View に適用できる modifier もわかりやすく説明されているので、ビューの理解だけではなく、どのような装飾ができるかも簡単にわかります。

超便利です

SwiftUIViewsMastery

販売元のページは、こちらです。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です