[SwiftUI] Text を特定の幅に合わせて表示する

SwiftUI2021

     

TAGS:

特定の幅に合わせて Text を表示する方法を考えてみます。

環境&対象

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

  • macOS Monterey 12.5 Beta
  • Xcode 14.0 Beta3
  • iOS 16.0 beta

Text

文字列を表示するために、以下のようなコードが SwiftUI の説明文章によく出てきます。


struct ContentView: View {
  var body: some View {
      Text("Hello, world!")
  }
}

いわゆる "Hello, world!" が画面上に表示されます。

すごくシンプルな使い方です。

文字を大きく表示したいときや、小さく表示したいときは、ViewModifier の .font を使用して調整できます。


import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Size50").font(.system(size: 50)).border(.red)
            Text("Size30").font(.system(size: 30)).border(.red)
            Text("Size12").font(.system(size: 20)).border(.red)
            Text("Size10").font(.system(size: 10)).border(.red)
        }
    }
}
TextWithFont

指定された大きさの文字になって表示されます。

Text は、表示サイズに対しての制約がないときはシンプルに使うことができます。

ですが、特定の大きさに合わせて表示したいときに 問題となります。

Image の .resizable のような 表示領域に合わせて拡大縮小してくれる便利な ViewModifier がないため、少し工夫することが必要となります。

.frame で表示領域が制限された時の振る舞い

Text を resizable(相当)にしていく前に、現在の Text と .frame との組み合わせでの振る舞いを確認します。

横方向が狭い

.frame で文字列が必要とするよりも小さい幅(width) を指定された時の動きを確認してみます。
.font(.system(size: 50)) と指定した Text に対して、.frame で width: 100 と指定してみます。


import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("FontSize50").font(.system(size: 50))
                .frame(width: 100)
                .border(.green)
        }
    }
}

以下のような表示になります。

NarrowWidth

Text は、本来必要な幅より小さい幅が指定された時には、複数行にして表示しようとするようです。

lineLimit との組み合わせ

Text には、行数を制限するための ViewModifier .lineLimit もありますので、 .frame との組み合わせをみてみます。


import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("FontSize50").font(.system(size: 50))
                .lineLimit(2)
                .frame(width: 100)
                .border(.green)
        }
    }
}

以下のような表示になります。

NarrowWidthWithLineLimit

当然ですが、指定された 行数を使っての表示になります。

ポイントとしては、以下です。
・収まりきらなければ、truncate (... のことです) が 使われる
つまり、最終的に 横幅が足りなければ truncate 表示に切り替わる
・Text の縦方向への広がりは、lineLimit で制御する

ちなみに、truncate の位置は、.truncationMode で 変更することが可能です。

縦方向が狭い

次に、縦方向に大きさが不足している時の動きを見てみます。

複数行になる Text に対して横幅を制限したもので、確認します。


struct ContentView: View {
    var body: some View {
        VStack {
            Text("FontSize50").font(.system(size: 50))//.border(.red)
                .lineLimit(4)
                .frame(width: 100)
        }
    }
}

上記が横方向だけを制限したもので、Text は3行を使用して表示されます。

この3行を表示できないほどの 高さを指定してみます。


struct ContentView: View {
    var body: some View {
        VStack {
            Text("FontSize50").font(.system(size: 50))//.border(.red)
                .lineLimit(4)
                .frame(width: 100)
                .frame(height: 30)
        }
    }
}

以下のような表示になります。

narrowWidthWithLowHeight

このことから、.lineLimit で制限された時と同様に、文字を表示する領域が不足していると判断したときには、truncate 表示になることがわかります。

なお、本来 size 50 での文字表示は、高さ 30 には収まりませんので、.clipped をつけると、文字が欠ける表示になります。このことから、Text に与えられた高さが 1行分に足りなくても、1行は表示することがわかります。

つまり、確実に 表示領域内におさめるためには、(縦方向を考慮すると) .clipped をつけておかないといけないことがわかります。
ただし、文字が欠けてしまうことがあり得るので、文字欠けを許さないのであれば、別途 考える必要があります。

言い方を変えると、縦方向が狭い表示領域に収めるように Text を表示するには、少し複雑な手法が必要となりそうです。(ですので、この記事では扱いません)

ユースケース

上で見た .frame で指定された領域に対しての Text の振る舞いを考慮して、
Text 表示を領域に合わせたい時の場合わけを以下のようにしました。

縮小表示して欲しいケース:
表示領域が必要領域よりも小さい箇所に Text を(縮小して)表示したい
拡大表示して欲しいケース:
表示領域が必要領域よりも大きい箇所に Text を(拡大して)表示したい

Note: 領域が横幅が狭い領域に、文字を省略して表示したいというケースは、truncate 表示で解決済みなので、ユースケースとして列挙していません。

縮小表示

Text に用意されている ViewModifier の . minimumScaleFactor を使用することで、縮小表示が可能となります。

.minimumScaleFactor なし

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("FontSize50").font(.system(size: 50))
                .lineLimit(1)
                .frame(width: 100)
        }
    }
}

minimumScaleFactor 指定

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("FontSize50").font(.system(size: 50))
                .lineLimit(1)
                .minimumScaleFactor(0.01)
                .frame(width: 100)
        }
    }
}
WithoutMinimumScaleFactor
WithMinimumScaleFactor

minimumScaleFactor を使用すると縮小される時の最小の倍率に制限をかけることができるので、十分に小さな値を指定しておけば、領域に収まるように縮小されることになります。

ただし、minimumScaleFactor を使用する時には、以下の点に気をつける必要があります。
・大きくする方向には調整されない
・縦横をあわせる scaleToFill はできない

拡大表示

Text を臨機応変に拡大することは、難しいです。

逆説的ですが、上記の縮小表示をうまく使って、拡大・縮小に対応するのが良さそうです。

つまり、.font で 十分大きなサイズの指定をしておき、.minimumScaleFactor を使って、実際の領域に合うように縮小表示するという組み合わせで (領域に合うように)拡大して表示するということが可能となります。

FitText

幅を合わせて Text を表示することは、ViewModifier を組み合わせることで実現できそうだとわかりました。

ということで、再利用しやすいように、View にしてみました。



struct FitText: View {
    let text: String
    let font: Font
    
    init(_ text: String,_ font: Font = .system(size: 200)) {
        self.text = text
        self.font = font
    }
    var body: some View {
        Text(text).font(self.font)
            .lineLimit(1)
            .minimumScaleFactor(0.001)
    }
}

工夫もなにもないですが、.lineLimint や .minimumScaleFacotor を外部から与えるようにしてもよいかもしれません。


import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            FitText("Hello")
                .frame(width: 80)
            FitText("Hello, world!")
                .frame(width: 80)
            FitText("Hello Hello Hello")
                .frame(width: 80)
            FitText("Hi")
                .frame(width: 80)
        }
    }
}

以下のような表示となり、それぞれ 幅に合わせて表示されていることがわかります。

FitText

まとめ

Text を領域に合わせるように表示する方法

Text を領域に合わせるように表示する方法
  • .font で文字の大きさをコントロールできる
  • .lineLimit は、表示に使用する行数をコントロールできる
  • .minimumScaleFactor は、縮小表示時の 最小倍率をコントロールする

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

SwiftUI おすすめ本

SwiftUI を理解するには、以下の本がおすすめです。

# SwiftUI は、毎年大きく改善されていますので、少し古くなってしまいましたが、いまでも 定番本です。

コメントを残す

メールアドレスが公開されることはありません。