Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI Leading & Trailing Alignment

I'm trying to create a simple chat bubble, with the message left (leading) aligned and the timestamp right aligned.

It should have a minimum width of 100 and dynamically grow based on the size of the message (with extra padding to the right/end/trailing to push it to the left/start).

Basically, I'm trying to replicate this:

enter image description here

struct ReceivedMessageRow: View {
    var text:String
    var messageTime: String
    var body: some View {
        HStack{
            VStack(alignment: .leading){
                Text(text)
                Text(messageTime)
                    .font(.caption)
            }
            .frame(minWidth: 100, alignment: .leading)
            .padding(8)
            .border(.red, width: 3)
            .cornerRadius(8)
            Spacer()
        }
        .border(.blue)
        .padding(.leading, 8)
        .padding(.trailing, 48)
        .padding(.vertical, 2)
        .frame(maxWidth: .infinity)
    }
}
    

I have it mostly there, but cannot get the timestamp to right align. enter image description here

If I wrap it in an HStack with a spacer, it grows the whole bubble, which I don't want

...
 VStack(alignment: .leading){
                Text(text)
                HStack{
                    Spacer()
                    Text(messageTime)
                        .font(.caption)
                }
               
            }
...

enter image description here

This should be simple but I've been scratching my head for hours - please someone put me out of my misery

like image 727
Charlie Walker Avatar asked Oct 25 '25 01:10

Charlie Walker


1 Answers

Using an overlay can get you the behavior you want (which will let you align to the right without affecting the parent size).

One important element is dealing with the size you need to add to the stack in order to make it "fit" that new element. I've chosen to include the messageTimeView twice to solve this -- once with an opacity of 0 to just make sure the Stack is tall enough to accommodate the element and once in the real overlay.

This strategy would allow you to change the font size of the timestamp and not worry that the layout would break.

struct ReceivedMessageRow: View {
    var text:String
    var messageTime: String
    
    @ViewBuilder
    private var messageTimeView: some View {
        Text(messageTime)
            .font(.caption)
    }
    
    var body: some View {
        HStack{
            VStack(alignment: .leading) {
                Text(text)
                messageTimeView
                    .opacity(0)
            }
            .frame(minWidth: 100, alignment: .leading)
            .overlay(
                messageTimeView
                    .frame(maxWidth: .infinity,
                           maxHeight: .infinity,
                           alignment: .bottomTrailing)
            )
            .padding(8)
            .border(.red, width: 3)
            .cornerRadius(8)
            Spacer()
        }
        .border(.blue)
        .padding(.leading, 8)
        .padding(.trailing, 48)
        .padding(.vertical, 2)
        .frame(maxWidth: .infinity)
    }
}

enter image description here

like image 137
jnpdx Avatar answered Oct 27 '25 05:10

jnpdx