Sponsor Link
環境&対象
- macOS Monterery beta 5
- Xcode 13 beta5
- iOS 15 beta
テキストと画像の高さを合わせて表示したい
Image ビューの高さを Text に合わせることを考えます。
ユースケースのイメージは List 中に表示する時に、Text はきちんと見せたいが、画像は、すこし小さめでも構わないというような時です。以下のような表示がゴールです。
テキストと画像のサイズの決まり方
SwiftUI のビュー Text, Image を使うと、それぞれのビューのサイズは以下のように決まります。
- Text ビュー: 指定された文字列を表示するために必要なサイズ
- Image ビュー: 指定されたイメージの持つサイズ
以降では、”Hello, world!” を表示する Text ビューと 150×150 サイズの画像を表示する Image ビューを使っていきます。
調整しないまま表示したテキストと画像
特に調整せずに、Text と Image を横に並べてみます。
//
// ContentView.swift
//
// Created by : Tomoaki Yagishita on 2021/09/03
// © 2021 SmallDeskSoftware
//
import SwiftUI
struct ContentView: View {
var body: some View {
List {
ForEach(0..3) { _ in
HStack {
Text("Hello, world!")
Image("Image150x150")
}
}
}
.padding()
}
}
以下のようなレイアウトになります。
.resizable 指定した画像の表示
Image には、表示サイズを調整可能と指定する .resizable という View Modifier がありますので、適用してみます。
//
// ContentView.swift
//
// Created by : Tomoaki Yagishita on 2021/09/03
// © 2021 SmallDeskSoftware
//
import SwiftUI
struct ContentView: View {
var body: some View {
List {
ForEach(0..3) { _ in
HStack {
Text("Hello, world!")
Image("Image150x150")
.resizable()
}
}
}
.padding()
}
}
以下のような表示になります。横方向には調整しているようですが、縦方向は変化ありません。
ちなみに、.scaleToFit を追加指定すると、以下のようになります。
resizable 指定することで、Text の右側の余白を幅いっぱいに使おうとして、.scaleToFit 指定することで、その幅に合わせた高さに調整されているという動作になっています。
Text がもっと横幅をとるものであれば、それに応じて 余白が小さくなり、その結果として画像も小さくなりそうです。
ですが、画像の大きさを調整するために、テキストの横幅(文字数)を調整するというのは、本末転倒です。
画像のサイズを指定してみる
Image は、.resizable と .frame を組み合わせることで、外部から表示サイズを指定することができます。
以下では、表示サイズを 100 x 100 に指定しています。
//
// ContentView.swift
//
// Created by : Tomoaki Yagishita on 2021/09/03
// © 2021 SmallDeskSoftware
//
import SwiftUI
struct ContentView: View {
var body: some View {
List {
ForEach(0..3) { _ in
HStack {
Text("Hello, world!")
Image("Image150x150")
.resizable().scaledToFit()
.frame(width: 100, height: 100)
}
}
}
.padding()
}
}
レイアウトは以下のようになります。
.resizable と .frame を組み合わせることで、表示サイズの指定はできますが、テキストと高さを合わせることはできていません。
Text は、.frame で表示サイズを調整できない
では、Text も .frame を使用して表示サイズを指定すればよいと思われがちですが、Text は、.frame で指定されたサイズを採用しません。
先ほどの画像の高さ 100 を Text にも指定してみます。
//
// ContentView.swift
//
// Created by : Tomoaki Yagishita on 2021/09/03
// © 2021 SmallDeskSoftware
//
import SwiftUI
struct ContentView: View {
var body: some View {
List {
ForEach(0..3) { _ in
HStack {
Text("Hello, world!")
.frame(height: 100)
Image("Image150x150")
.resizable().scaledToFit()
.frame(width: 100, height: 100)
}
}
}
.padding()
}
}
比べてみるとわかりますが、Text に .frame を指定せずに、Image に .frame を指定したレイアウトと同じです。
内部的には、Text は、高さ 100 の 見えないフレーム中の .center に配置されています。レイアウト的には、HStack のデフォルトの動作と同じであるため、結果のレイアウトに差がでていません。
ポイントは、Text は、.frame 指定されても、Text 自身の大きさを変更するわけではない という点です。
つまり、Text と Image の表示高さを合わせるのであれば、Image を調整する方が簡単にできるということです。
Image の高さを指定するには、.frame で指定することが必要
Image の高さを指定することは簡単です。 .resizable 指定してから、.frame 指定すると、指定されたサイズに合わせた表示になります。(実際、なっていました)
問題は、合わせる高さがわからない ということです。Text は、自分の高さを表示する時点で確定されますので、事前に外部から与えられているわけではありません。
ですので、Image を高さ指定するための高さを指定することが難しいのです。
Preference を使用する
このような時に便利に使えるのが、Preference です。
Preference を使うことで、子ビューからの情報を親ビューで取得することができます。
親ビューから子ビューに情報を伝達することは簡単ですので、以下のようなビュー構造とデータの流れで、問題を解決できます。
ビュー構成
- 親ビュー: HStack
- 子ビュー: Text と Image
高さ指定
- Text から Preference を使って、Text 高さを HStack に伝える
- HStack は、Text 高さを使って Image の高さを指定する
実装
実装してみると、以下のような表示になります。
以下のような実装になります。
//
// ContentView.swift
//
// Created by : Tomoaki Yagishita on 2021/09/03
// © 2021 SmallDeskSoftware
//
import SwiftUI
struct ContentView: View {
// (1)
@State private var textHeight: CGFloat = 10
var body: some View {
List {
ForEach(0..3) { _ in
HStack {
Text("Hello, world!")
// (2)
.background(FrameViewRectPreferenceSetter(prefName: "HelloWorldHeight"))
Image("Image150x150")
.resizable().scaledToFit()
// (3)
.frame(height: textHeight)
}
// (4)
.onPreferenceChange(FrameViewRectPreferenceKey.self, perform: { prefs in
for pref in prefs {
if pref.name == "HelloWorldHeight" {
self.textHeight = pref.rect.size.height
}
}
})
}
}
.padding()
}
}
- Text, Image で使用する高さを @State 変数で定義します(View 内部で変更するため @State 定義が必要です)
- Text の高さを Preference に設定します
- textHeight を Image にも指定します
- Preference の変更通知を受けて、textHeight 変数を更新します
まとめ:Scalable な要素を Text に合わせた高さで表示する
- Text の高さを Preference で取得する
- Text 高さを使って、Scalable な要素の高さを設定する
説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。
Sponsor Link