Reuse custom alignment in nested views

I have a SwiftUI view for math expressions. It shows divisions as VStacks with a divider in the middle and additions as HStacks with a "+" sign in the middle. See the images in this GitHub gist: gist.github.com/Dev1an/668978dc4b84b8c6213ba10905d57c1d#proper-alignment


But I am having trouble aligning the Additions to the Divisions. I can create multiple custom alignment guides beforehand for each division and then everything works fine. But the problem is that I am generating these mathematical expressions dynamically and cannot create new guides (a Type conforming to

AlignmentID.Type
) for every division in the expression. Is there a way to reuse alignment guides in nested views so that I can reuse the same custom VerticalAlignment multiple times?


When I try to reuse the same guide, the HStack's just align to the center (as shown in the gist).

Here is an example hierarchy:


Addition(alignment: .custom)
  Division
    Addition(alignment: .custom)
      Number
      +
      Division
        Number
        / (guide: .custom)
        Number
    / (guide: .custom)
    Number
  +
  Number

Is there an easy way to solve this? I think it should work a bit similar to VerticalAlign.firstBaseline but it should align to the first division guide down the view hierarchy (when traversing breadth-first) instead. So the Addition on line 01 should use the guide on line 10, and the Addition on line 03 should use the guide on line 08.


Any thoughts how to accomplish this?

Accepted Reply

@Claude31: Indeed your answer doesn't help me in any way. I don't care whether you can create custom views or not, I was asking people to help me build my own custom views. But anyway...


The VStack and HStack are actually quite a good match for this use case. The only thing I didn't understood yet was the way in which alignment guides were passed up the view hierarchy. I figured it out now and it turns out that I had to put the guide on the dividend instead of the divider line.


So to summarize, here is the code I ended up with:

struct AdditionView: View {
  let left: Left
  let right: Right


  var body: some View {
    HStack(alignment: .division) {
      left
      Text("+")
      right
    }
    .fixedSize()
  }
}


struct DivisionView: View {
  let dividend: Dividend
  let divisor: Divisor
  let spacing: CGFloat = 3
  
  var body: some View {
    VStack(spacing: spacing) {
      aligningDividend
      Divider().overlay(Color.primary)
      divisor
    }
    .fixedSize()
  }
  
  var aligningDividend: some View {
    dividend.alignmentGuide(.division) { dividend in
      dividend[.bottom] + self.spacing
    }
  }
}


extension VerticalAlignment {
  private enum Divider: AlignmentID {
    static func defaultValue(in context: ViewDimensions) -> CGFloat {
      context[VerticalAlignment.center] + 1
    }
  }
  static let division = Self(Divider.self)
}


And that gives me nice and aligned divisions in additions 😎.

Replies

iisI've always found that stacks are not very flexible (SwiftUI or not) when it comes to precise positionning or sizing.


In such a case, I could probably design custom views. Sorry, I know that's not the answer you expect.

@Claude31: Indeed your answer doesn't help me in any way. I don't care whether you can create custom views or not, I was asking people to help me build my own custom views. But anyway...


The VStack and HStack are actually quite a good match for this use case. The only thing I didn't understood yet was the way in which alignment guides were passed up the view hierarchy. I figured it out now and it turns out that I had to put the guide on the dividend instead of the divider line.


So to summarize, here is the code I ended up with:

struct AdditionView: View {
  let left: Left
  let right: Right


  var body: some View {
    HStack(alignment: .division) {
      left
      Text("+")
      right
    }
    .fixedSize()
  }
}


struct DivisionView: View {
  let dividend: Dividend
  let divisor: Divisor
  let spacing: CGFloat = 3
  
  var body: some View {
    VStack(spacing: spacing) {
      aligningDividend
      Divider().overlay(Color.primary)
      divisor
    }
    .fixedSize()
  }
  
  var aligningDividend: some View {
    dividend.alignmentGuide(.division) { dividend in
      dividend[.bottom] + self.spacing
    }
  }
}


extension VerticalAlignment {
  private enum Divider: AlignmentID {
    static func defaultValue(in context: ViewDimensions) -> CGFloat {
      context[VerticalAlignment.center] + 1
    }
  }
  static let division = Self(Divider.self)
}


And that gives me nice and aligned divisions in additions 😎.