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
    }
}

参考文献

SwiftUI – how know number of lines in Text?

Convert from SwiftUI.Font to UIFont

次の記事

2点間の距離とsort