[SwiftUI] インラインでTextにImageを埋め込むデザインを実装しようとして苦労した話


テキストの先頭にアイコンがあり、2行目のテキストの先頭をアイコンの下に潜り込ませるデザインを、あなたはどのように実装しますか?

f:id:bamboohero:20211225182415p:plain


SwiftUIのTextを使うと、テキストに画像をインラインで埋め込むことができるので、以下のように簡単に実装できます。

Text(Image(systemName: "star")) + Text("寿限無寿限無五劫のすり切れ海砂利水魚の水行末")


では、SF Symbolにない独自のアイコン画像を使いたい場合はどうでしょうか?

同じように、Textを使って実装してみます。

Text(Image("forward10")) + Text("寿限無寿限無五劫のすり切れ海砂利水魚の水行末")


f:id:bamboohero:20211225182432p:plain


なんかアイコンの位置がおかしい。。
ちょっと上になってる。。

そうなんです、思った通り配置してくれないんです。

他のModifier当ててみたり色々試してみたんですがうまくいかずでした。


そこで、UIKitで要件を実現するビューを作ってみることにしました。

色々試行錯誤してたどり着いた実装がこれです。

struct IconLabel: UIViewRepresentable {
    func makeUIView(context: Context) -> some UIView {
        let attachment = NSTextAttachment()
        attachment.image = UIImage(named: "forward10")!
        attachment.bounds = CGRect(x: 0, y: -6, width: 24, height: 24)
        let imageString = NSAttributedString(attachment: attachment)

        let mutableAttributedString = NSMutableAttributedString()
        mutableAttributedString.append(imageString)

        mutableAttributedString.append(NSAttributedString(string: "寿限無寿限無五劫のすり切れ海砂利水魚の水行末"))
        mutableAttributedString.addAttributes(
            [.font: UIFont.systemFont(ofSize: 18)],
            range: .init(location: 0, length: mutableAttributedString.length)
        )

        let label = UILabel()
        label.attributedText = mutableAttributedString
        label.numberOfLines = 2
        label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        return label
    }

    func updateUIView(_ uiView: UIViewType, context: Context) {
    }
}


いい感じに配置されています!

f:id:bamboohero:20211225183137p:plain


ところが、、

以下はプレビューのCanvasの図なんですが、UIViewRepresentableで実装したビューはコンテンツの上下に余白が入ってしまいます。

f:id:bamboohero:20211225183500p:plain


理想は下図のようにフレームがコンテンツサイズと一致することなんですが、いろいろ試してもここがうまくいきませんでした。

f:id:bamboohero:20211225183758p:plain


上下に不要な余白が入ってしまうことで、他のビューと組み合わせたときにマージンを調整するのに非常に苦労します。
高さを固定値にしないと使えなかったりと、なかなか自由度の低いビューになってしまいます。


ということで、今のところ要件を実現するビューが実装できていない状況です。

もし良い感じのやり方ご存じの方いたら、コメントで教えていただけると嬉しいです。