SwiftUIでTextの高さを計算する
はじめに
アニメを管理するアプリを作りたいと思っていてSwiftUIを触り始めたんですが右も左も分からずアワアワしています。
今回のゴールは以下のWidget画像のようにTextの高さ(行数)に応じて左のRoundedRectangleの高さを可変にすることです。
SwiftUIでのサイズ取得
SwiftUIをついままで使ったことがなかったので一番苦労したのはここでした。
UIKItであればview.frame.siizeのように各Viewがサイズクラスを保持しているので簡単に大きさを取得できますが、SwifUIのコンポーネントは基本的にサイズクラスを保持していないのでText.frame.sizeのようにサイズを引っ張ることができません🙅♀️
ではどうやって計算をするのかというとSwiftUIにはGeometoryRenderというものが存在します。
例えばTextを上のようなサイズにしたい場合は以下のようにGeometoryRenderからサイズを取得してframeに設定することで指定できます。
上に少し空白がありますがこれはsafeareaなので.edgesIgnoringSafeArea(.all)をつけれsafeareaが無視されます。
var body: some View {
GeometryReader { geometry in
Text("hogehoge").frame(width: geometry.size.width,
height: geometry.size.height/2,
alignment: .center)
.background(Color.orange)
}
}
Textの高さでRectangleの高さを可変にしてみる
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
CustomView(title: "寿限無(じゅげむ) 寿限無(じゅげむ) 五劫(ごこう)のすりきれ 海砂利(かいじゃり)水魚(すいぎょ)の水行末(すいぎょうまつ) 雲来末(うんらいまつ) 風来末(ふうらいまつ) 食(く)う寝(ね)るところに 住(す)むところ やぶらこうじの ぶらこうじ パイポ パイポ パイポの シューリンガン シューリンガンの グーリンダイ グーリンダイの ポンポコピーのポンポコナの 長久命(ちょうきゅうめい)の長助(ちょうすけ)")
}
}
}
struct CustomView: View {
let title: String
let hStackSpaceing: CGFloat = 2
let rectWidht: CGFloat = 5
var body: some View {
GeometryReader { geometry in
HStack(spacing: hStackSpaceing) {
Rectangle()
.frame(width: rectWidht,
height: calculateTextHeight(geometry: geometry))
Text(title)
.font(.system(size: 13))
.background(Color.orange)
}
}
}
private func calculateTextHeight(geometry: GeometryProxy) -> CGFloat {
let width = geometry.size.width - (hStackSpaceing + rectWidht)
return title.boundingRect(
with: CGSize(
width: width,
height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
attributes: [.font: UIFont.systemFont(ofSize: 13)],
context: nil
).height
}
}
TextStyleを無理やり計算する
上記でSystemFontを使って言える時の計算方法を記載しましたが、SwiftUIでは基本的にTextStyleを使用することになると思います。
ですが、NSAttributedString.KeyにFont型を渡すことができないのでFontをUIFontに変換する必要があります。
UIFont.preferredFont(from: .caption)
Font -> UIFont
extension UIFont {
class func preferredFont(from font: Font) -> UIFont {
let uiFont: UIFont
switch font {
case .largeTitle:
uiFont = UIFont.preferredFont(forTextStyle: .largeTitle)
case .title:
uiFont = UIFont.preferredFont(forTextStyle: .title1)
case .title2:
uiFont = UIFont.preferredFont(forTextStyle: .title2)
case .title3:
uiFont = UIFont.preferredFont(forTextStyle: .title3)
case .headline:
uiFont = UIFont.preferredFont(forTextStyle: .headline)
case .subheadline:
uiFont = UIFont.preferredFont(forTextStyle: .subheadline)
case .callout:
uiFont = UIFont.preferredFont(forTextStyle: .callout)
case .caption:
uiFont = UIFont.preferredFont(forTextStyle: .caption1)
case .caption2:
uiFont = UIFont.preferredFont(forTextStyle: .caption2)
case .footnote:
uiFont = UIFont.preferredFont(forTextStyle: .footnote)
case .body:
fallthrough
default:
uiFont = UIFont.preferredFont(forTextStyle: .body)
}
return uiFont
}
}