TransWikia.com

How can I vertically align an image to the center of the first line of some text?

Stack Overflow Asked by DanubePM on December 9, 2021

I have a bullet point and some long, multiline text. I want the bullet point to be aligned with the center of the first line of text. Obviously, If the string is sufficiently short and one line long then the two views are automatically center aligned. I’m interested in cases where text is more than one line long.

var body: some View {
    HStack {
        Image(systemName: "circle.fill")
            .font(.system(size: 8))
        Text("Insert really long multiline string that wraps.")
    }
}

Is this possible?

Update 1:

Setting the HStack alignment to top aligns the top of the image with the top of the text, like this…

var body: some View {
    HStack(alignment: .top) {
        Image(systemName: "circle.fill")
            .font(.system(size: 8))
        Text("This is really really really really really really really really really really really really really really really really really really really really really really really really long string.")
    }
}

image 1

Update 2:

The only option I can think of is something like this, except this is UIKit…

// Aligns the icon to the center of a capital letter in the first line
let offset = label.font.capHeight / 2.0

// Aligns the icon to the center of the whole line, which is different
// than above. Especially with big fonts this makes a visible difference.
let offset = (label.font.ascender + label.font.descender) / 2.0

let constraints: [NSLayoutConstraint] = [
  imageView.centerYAnchor.constraint(equalTo: label.firstBaselineAnchor, constant: -offset),
  imageView.trailingAnchor.constraint(equalTo: label.leadingAnchor, constant: -10)
]
NSLayoutConstraint.activate(constraints)

3 Answers

iOS 14 you can use Label for that.

enter image description here

Label {
    Text("This is really really really really really really really really really really really really really really really really really really really really really really really really long string.")
} icon: {
    Image(systemName: "circle.fill")
                .font(.system(size: 8))
}

Answered by DanSkeel on December 9, 2021

Assuming that all lines of text should have equal heights/distance between the baselines, we can benefit from the fact that dimensions provided to .alignmentGuide have both first and last baselines, and get the height of the first (in fact, any) line simply by subtracting the distance between the last and the first baselines from the view height.

Reusing the terms of the answer provided by @Asperi, here's the solution that does not require introducing ZStack/fake view:

extension VerticalAlignment {
    private enum XAlignment : AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            return d[VerticalAlignment.top]
        }
    }
    static let xAlignment = VerticalAlignment(XAlignment.self)
}

struct DemoAlignFirstLine: View {
    
    var body: some View {
        HStack(alignment: .xAlignment) {
            Image(systemName: "circle.fill")
                .font(.system(size: 8))
                .alignmentGuide(.xAlignment) { $0.height / 2.0 }

            // main text
            Text("This is really really really really really really really really really really really really really really really really really really really really really really really really long string.")
                .alignmentGuide(.xAlignment) {
                     ($0.height - ($0[.lastTextBaseline] - $0[.firstTextBaseline])) / 2
                }
        }
    }
}

Answered by Grigory Entin on December 9, 2021

Here is a solution based on custom alignment. Tested with Xcode 11.4 / iOS 13.4

demo

extension VerticalAlignment {
    private enum XAlignment : AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            return d[VerticalAlignment.top]
        }
    }
    static let xAlignment = VerticalAlignment(XAlignment.self)
}

struct DemoAlignFirstLine: View {

    var body: some View {
        HStack(alignment: .xAlignment) {
            Image(systemName: "circle.fill")
                .font(.system(size: 8))
                .alignmentGuide(.xAlignment) { $0.height / 2.0 }

            ZStack(alignment: .topLeading) {
                // invisible anchor, should be of same font as text
                Text("X").foregroundColor(.clear)  
                    .alignmentGuide(.xAlignment) { $0.height / 2.0 }

                // main text
                Text("This is really really really really really really really really really really really really really really really really really really really really really really really really long string.")
            }
        }
    }
}

Answered by Asperi on December 9, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP