79741741

Date: 2025-08-21 01:33:48
Score: 0.5
Natty:
Report link

This works great with custom fonts and updating the view's frame causing layout changes:

enter image description here

Here's the code:

public struct FirstLineCenterID: AlignmentID {
    static func defaultValue(in d: ViewDimensions) -> CGFloat {
        d[VerticalAlignment.center]
    }
}

/// Custom vertical alignment used to coordinate views against the **first line center**.
extension VerticalAlignment {
    static let firstLineCenter = VerticalAlignment(FirstLineCenterID.self)
}

// MARK: - FirstLineCenteredLabel

/// A `Label`-like view that displays a leading icon and a text label, aligning the icon
/// to the **vertical midpoint of the text’s first line**.
struct FirstLineCenteredLabel<Icon>: View where Icon : View {
    let text: String
    let spacing: CGFloat?
    let icon: Icon

    /// Cached measured height of a single line for the current font.
    @State private var firstLineHeight: CGFloat?

    /// The effective font pulled from the environment; used by both visible and measuring text.
    @Environment(\.font) var font

    init(
        _ text: String,
        spacing: CGFloat? = nil,
        @ViewBuilder icon: () -> Icon
    ) {
        self.text = text
        self.spacing = spacing
        self.icon = icon()
    }

    var body: some View {
        HStack(alignment: .firstLineCenter, spacing: spacing) {
            let text = Text(text)

            icon
                // aligns by its vertical center
                .alignmentGuide(.firstLineCenter) { d in d[.top] + d.height / 2 } 
                .font(font)

            text
                .font(font)
                .fixedSize(horizontal: false, vertical: true)
                // aligns by the first line's vertical midpoint
                .alignmentGuide(.firstLineCenter) { d in                                                    
                    let h = firstLineHeight ?? d.height
                    return d[.top] + h / 2
                }
                // Measure the natural height of a single line **without impacting layout**:
                // a 1-line clone in an overlay with zero frame captures `geo.size.height` for the
                // current environment font. This avoids the `.hidden()` pitfall which keeps layout space.
                .overlay(alignment: .topLeading) {
                    text.font(font).lineLimit(1).fixedSize()
                        .overlay(
                            GeometryReader { g in
                                Color.clear
                                    .onAppear { firstLineHeight = g.size.height }
                                    .onChange(of: g.size.height) { firstLineHeight = $0 }
                            }
                        )
                        .opacity(0)
                        .frame(width: 0, height: 0)
                        .allowsHitTesting(false)
                        .accessibilityHidden(true)
                }
        }
    }
}

Usage:

var body: some View {
    VStack {
        FirstLineCenteredLabel(longText, spacing: 8) {
            Image(systemName: "star.fill")
        }

        FirstLineCenteredLabel(shortText, spacing: 8) {
            Image(systemName: "star.fill")
        }

        Divider()

        // Just to showcase that it can handle various font sizing.

        FirstLineCenteredLabel(longText, spacing: 8) {
            Image(systemName: "star.fill")
                .font(.largeTitle
        }
        .font(.caption)
    }
}

private var longText: String {
    "This is a new label view that places an image/icon to the left of the text and aligns it to the text's first line vertical midpoint."
}

private var shortText: String {
    "This should be one line.
}
Reasons:
  • Probably link only (1):
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (1):
Posted by: MarlonJames