1

I want to use alignmentGuide to align center a Text view in ZStack to implement view as bellow, blue block is align traling, Text "abc" and "123" is align center of view.

enter image description here

My code is as bellow:

extension HorizontalAlignment {
    private enum MyHorizontalAlignment : AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            return d[HorizontalAlignment.center]
        }
    }
    
    static let myHorizontalAlignment = HorizontalAlignment(MyHorizontalAlignment.self)
}

extension Alignment {
    static let myAlignment = Alignment(horizontal: .myHorizontalAlignment, vertical: .center)
}


struct ContentView: View {
    var body: some View {
        VStack() {
            VStack() {
                ZStack(alignment: .myAlignment) {
                    Rectangle()
                        .foregroundColor(.gray)
                        .frame(height: 100)
                        .padding(.horizontal, 20)
                    VStack() {
                        Rectangle()
                            .frame(width: 100, height: 30)
                            .foregroundColor(.blue)
                        //Text("12345678")
                            .alignmentGuide(.myHorizontalAlignment) { d in
                                d.width/2.0 - (160-100)/2.0
                            }
                        HStack() {
                            Text("123")
                            Text("456")
                        }
                    }
                }
            }
            .frame(width: 200, height: 200)
            .background(Color.gray.opacity(0.4))
        }
        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
        .background(Color.white)
    }
}

But the "abc" and "123" in not center.

enter image description here

I know that it can use Spacer and offset to implement it. But how to use alignmentGuide to implement it?

mars
  • 109
  • 5
  • 21

2 Answers2

1

While this may not be the exact answer using custom alignmentGuide, I believe it's a much more straightforward, simpler and concise approach. Instead, I utilized the built-in frame alignments, which allow you to adjust the frame sizes, paddings, and other constants of elements, ensuring consistent alignment.

struct ContentView: View {
    var body: some View {
        ZStack(alignment: .center) {
            Rectangle()
                .foregroundColor(.gray)
                .frame(height: 100)
            VStack(alignment: .center) {
                Rectangle()
                    .frame(width: 100, height: 30)
                    .foregroundColor(.blue)
                    .frame(maxWidth: .infinity, alignment: .trailing)
                HStack() {
                    Text("123")
                    Text("456")
                }
                .frame(maxWidth: .infinity, alignment: .center)
            }
        }
        .padding(.horizontal, 20)
        .frame(width: 200, height: 200)
        .background(Color.gray.opacity(0.4))
    }
}

Image with original frame sizes and padding. desired result

Image with different frame sizes and padding. desired result with different frame size and padding

Hollycene
  • 287
  • 1
  • 2
  • 12
0

Analysis

Using frame alignment is probably an easier way to achieve the layout in the question, as explained in the answer by Hollycene. But if you really want to use alignment guides then this is possible too.

This case is a bit complicated for the following reasons:

  1. The blue rectangle is expected to align with the dark gray rectangle that comes before it in the ZStack, but that rectangle has padding.
  2. The blue rectangle and the text are both inside the same VStack. If either of these views are shifted, the horizontal center of the VStack will (probably) change.

Changing the way the dark gray rectangle is padded, or splitting the blue rectangle and the text into separate containers, would therefore make the problem easier.

If the padding and the container structure are kept unchanged, I think the only way to make it work is to include knowledge of the container size and the padding in the alignment guide. However, the good news is, you do not need a bespoke Alignment implementation for this. In other words, myHorizontalAlignment and myAlignment are not needed.

Solution

The following solution uses .trailing alignment for the ZStack and also for the VStack inside it. Then, for the content inside the VStack:

  • an alignment guide is applied to the blue rectangle to compensate for the padding on the gray rectangle (knowledge of the padding size is needed here);
  • another alignment guide is applied to the HStack containing the text, in order to make it align with center of the container (knowledge of the container width is needed here).
struct ContentView: View {
    let containerWidth = CGFloat(200)
    let paddingSize = CGFloat(20)

    var body: some View {
        VStack() {
            VStack() {
                ZStack(alignment: .trailing) {
                    Rectangle()
                        .foregroundColor(.gray)
                        .frame(height: 100)
                        .padding(.horizontal, paddingSize)
                    VStack(alignment: .trailing) {
                        Rectangle()
                            .frame(width: 100, height: 30)
                            .foregroundColor(.blue)
                            .alignmentGuide(.trailing) { d in
                                d[.trailing] + paddingSize
                            }
                        HStack() {
                            Text("123")
                            Text("456")
                        }
                        .alignmentGuide(.trailing) { d in
                            d[HorizontalAlignment.center] + containerWidth / 2
                        }
                    }
                }
            }
            .frame(width: containerWidth, height: containerWidth)
            .background(Color.gray.opacity(0.4))
        }
        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
        .background(Color.white)
    }
}

The screenshot below includes a yellow background behind the HStack containing the Text items and a dotted red border around the VStack containing the blue rectangle and the HStack, to show how the views are aligned.
Alignment

Additional notes

Here is a summary of the changes, compared to your original code:

  • the container width and the padding size have been parameterized
  • the alignment of the ZStack is now .trailing instead of .myAlignment
  • the VStack inside the ZStack also has .trailing alignment
  • the alignment guide for the blue rectangle has been changed
  • an alignment guide has been added to the HStack containing the text
  • myHorizontalAlignmentand myAlignment are not needed.

In fact, it doesn't matter what horizontal alignment is applied to the VStack, as long as the alignment guides refer to the same alignment. But I find it is easiest to understand if the ZStack and VStack use the same horizontal alignment.

The parameters defining the container width and padding size can be changed, if required. The guides work reliably as long as there is sufficient space inside the container to fit the blue rectangle.

Benzy Neez
  • 1,546
  • 2
  • 3
  • 10