1
votes

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.

1
I don't think you could make a custom alignment for each label, you would need an HStack for each couple (chartRow, label). What you can try is to set the correct alignment even for the last label (you did only for the first one) and hope the labels will arrange automatically covering the free spaces in between. You can also try with space modifier in the labels VStackAndrea Miotto
As long as the first and last labels are aligned, the others will be aligned as well. I only did the first one because I couldn't find a way to do both the first and last. I'm open to anything that works.Robert Gummesson

1 Answers

1
votes

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