Robert Gummesson
Robert Gummesson

Reputation: 5666

Aligning HStack without equal sizing in SwiftUI

I have a custom chart with some texts as seen below. It aligns the first text correctly, it aligns the last text incorrectly. I need the VStack with the texts to stretch like an accordion so that the last text's centerY aligns with the bottom of the chart.

As seen with the blue Xcode highlight, the VStack receives the same size as the chart with an offset.

chart image

To align the first text, I extended VerticalAlignment as seen in WWDC 2019 - Session 237.

extension VerticalAlignment {
   private enum TopChartAndMidTitle: AlignmentID {
      static func defaultValue(in dimensions: ViewDimensions) -> Length {
         return dimensions[.top]
      }
   }
   static let topChartAndMidTitle = VerticalAlignment(TopChartAndMidTitle.self)
}

Then I used it like this:

var labels = ["1900", "1800", "1700", "1600", "1500", "1400"]

var body: some View {
    HStack(alignment: .topChartAndMidTitle) {
        chart()
            .alignmentGuide(.topChartAndMidTitle) { $0[.top] }
        valueLabels()
    }
}

private func valueLabels() -> AnyView {
    AnyView(VStack {
        Text(labels[0]).font(.footnote)
            .alignmentGuide(.topChartAndMidTitle) { $0[.bottom] / 2 }
        ForEach(labels.dropFirst().identified(by: \.self)) { label in
            Spacer()
            Text(label).font(.footnote)
        }
    })
}

Question: How do I align the last text against the chart bottom and thereby stretch the VStack?

To test this, just replace my custom chart with Rectangle().fill(Color.red). It will result in the same problem.

Upvotes: 1

Views: 2122

Answers (1)

Asperi
Asperi

Reputation: 257711

Today I would do it with the following approach...

Result demo

SwiftUI graph scale custom alignment

Code (TopChartAndMidTitle used from original question "as is")

struct TestAlignScales: View {
    var labels = ["1900", "1800", "1700", "1600", "1500", "1400"]

    @State private var graphHeight = CGFloat.zero
    var body: some View {
        HStack(alignment: .topChartAndMidTitle) {
            Rectangle().fill(Color.red)
                .alignmentGuide(.topChartAndMidTitle) { d in
                    if self.graphHeight != d.height {
                        self.graphHeight = d.height
                    }
                    return d[.top]
                }
            valueLabels()
        }
    }

    @State private var delta = CGFloat.zero
    private func valueLabels() -> some View {
        VStack {
            ForEach(labels, id: \.self) { label in
                VStack {
                    if label == self.labels.first! {
                        Text(label).font(.footnote)
                            .alignmentGuide(.topChartAndMidTitle) { d in
                                if self.delta != d.height {
                                    self.delta = d.height
                                }
                                return d[VerticalAlignment.center]
                            }
                    } else {
                        Text(label).font(.footnote)
                    }
                    if label != self.labels.last! {
                        Spacer()
                    }
                }
            }
        }
        .frame(height: graphHeight + delta)
    }

}

struct TestAlignScales_Previews: PreviewProvider {
    static var previews: some View {
        TestAlignScales()
            .frame(height: 400)
    }
}

Upvotes: 1

Related Questions