swiftui

How to right align content in an intrinsic sized VStack in SwiftUI


This is probably a simple question but I am struggling hard.

Lets assume I have the following View Layout in SwiftUI

I want the Date String 10.5.2020 to be right aligned in the parent VStack.

But its important that by doing that, the intrinsic size of the VStack is not broken.

I am telling that because the two most obvious solutions are doing that.

  1. Surround with HStack and put a Spacer on the left.
  2. Use frame with maxWidth: .infinity and alignment: .trailing

I had a deeper look into alignment guides but couldn't fix it with that.

My best results so far I have with GeometryReader, setting width of the Text Label hardly to the width of the surrounding VStack.

But unfortunately my target structure is much more complex and this approach leads to issues in that structure. Also GeometryReader seems inappropriate to do alignment.

struct ContentView: View {
    var body: some View {
        ScrollView {
            VStack(alignment: .leading) {
                
                HStack {
                    VStack(alignment: .leading) {
                        Text("Alignment Test")
                        Text("This is a longer text left aligned")
                         
                        
                        Text("10.5.2020")
                    }
                    .padding(16)
                    .background(Color.blue)
                    
                    Spacer(minLength: 50)
                }
                
                
                HStack {
                    VStack(alignment: .leading) {
                        Text("left")
                        Text("Date should rigzht align to here <-")
                        Text("10.5.2020")
                    }
                    .padding(16)
                    .background(Color.blue)
                    
                    Spacer(minLength: 50)
                }
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .border(.red)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.gray.opacity(0.5))
    }
}

Previews:

enter image description here


Solution

  • The alignment can be achieved using a combination of .frame and .fixedSize modifiers.

    1. Apply .frame(maxWidth: .infinity, alignment: .trailing) to the text(s) you want right-aligned. This causes the text to extend to the maximum width available, with the content aligned to the right.

    Normally, this would cause the container to extend to the full width available too. So to prevent this from happening:

    1. Apply .fixedWidth() to the VStack. This tells it to adopt the ideal width of its contents.

    So the way it works is that the VStack is sized to the ideal width of its contents, then, within this constraint, the maxWidth: .infinity on the text takes effect.

    ScrollView {
        VStack(alignment: .leading) {
    
            HStack {
                VStack(alignment: .leading) {
                    Text("Alignment Test")
                    Text("This is a longer text left aligned")
                    Text("10.5.2020")
                        .frame(maxWidth: .infinity, alignment: .trailing) // πŸ‘ˆ added
                }
                .padding(16)
                .fixedSize() // πŸ‘ˆ added
                .background(Color.blue)
    
                Spacer(minLength: 50)
            }
    
            HStack {
                VStack(alignment: .leading) {
                    Text("left")
                    Text("Date should right align to here ->")
                    Text("10.5.2020")
                        .frame(maxWidth: .infinity, alignment: .trailing) // πŸ‘ˆ added
                }
                .padding(16)
                .fixedSize() // πŸ‘ˆ added
                .background(Color.blue)
    
                Spacer(minLength: 50)
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .border(.red)
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity)
    .background(Color.gray.opacity(0.5))
    

    Screenshot


    EDIT In a comment, you said the longer text might be so long that it will need to wrap. But applying .fixedSize() will prevent wrapping.

    As an alternative approach, you could consider using a 1-column Grid instead:

    ScrollView {
        VStack(alignment: .leading) {
    
            HStack {
                Grid(alignment: .leading) { // πŸ‘ˆ VStack replaced
                    Text("Alignment Test")
                    Text("This is a longer text left aligned")
                    Text("10.5.2020")
                        .gridCellAnchor(.trailing) // πŸ‘ˆ added
                }
                .padding(16)
                .background(Color.blue)
    
                Spacer(minLength: 50)
            }
    
            HStack {
                Grid(alignment: .leading) {
                    Text("left")
                    Text("The quick brown fox jumps over the lazy dog")
                    Text("10.5.2020")
                        .gridCellAnchor(.trailing)
                }
                .padding(16)
                .background(Color.blue)
    
                Spacer(minLength: 50)
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .border(.red)
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity)
    .background(Color.gray.opacity(0.5))
    

    Screenshot