align text in HStack

44 Views Asked by At

I need to make it so that no matter what the item.number is 1, 10, 100, 1000. and whether the number is highlighted - the item.userName is always exactly under each other, and the numbers are right-aligned.

struct ContentFirst: Identifiable {
        var id = UUID()
        let number: Int
        let userName: String
        let info: String
    }
    
    struct FirstViewFourthSegment: View {
        var contentFirst: [ContentFirst] = [ContentFirst(number: 5, userName: "user5", info: "123"), ContentFirst(number: 11, userName: "user11", info: "123"), ContentFirst(number: 3, userName: "user3", info: "123"),ContentFirst(number: 7, userName: "user7", info: "123"),ContentFirst(number: 8, userName: "user8", info: "123"),ContentFirst(number: 10, userName: "user10", info: "123"),ContentFirst(number: 2, userName: "user2", info: "123"),ContentFirst(number: 4, userName: "user4", info: "123"),ContentFirst(number: 6, userName: "user6", info: "123"),ContentFirst(number: 9, userName: "user9", info: "123"),ContentFirst(number: 12, userName: "user12", info: "123"),ContentFirst(number: 13, userName: "user13", info: "123"),ContentFirst(number: 15, userName: "user15", info: "123"),ContentFirst(number: 14, userName: "user14", info: "123"),ContentFirst(number: 16, userName: "user16", info: "123"),ContentFirst(number: 1, userName: "user1", info: "123")]
    
        var body: some View {
            VStack {
                List(contentFirst) { item in
                    HStack {
                        Text("\(item.number)")
                            .font(.system(size: 12, weight: .regular))
                            .padding([.top, .bottom, .leading, .trailing], 7.5)
                            .background(item.number <= 3 ? Color(Const.grayText) : Color.clear)
                            .clipShape(Circle())
                            .foregroundColor(item.number <= 3 ? Color.white : Color(Const.grayText))
                        
                        Text(item.userName)
                            .font(.system(size: 12, weight: .regular))
                            .foregroundColor(Color.black)
                        
                        Spacer()
                        
                        Text(item.info)
                            .font(.system(size: 16, weight: .bold))
                            .foregroundColor(Color.black)
                            .padding(.trailing, 18)
                    }
                    .listRowSeparator(.hidden)
                }
            }
        }
    }

now, depending on how wide the number is, the more indentation to userName.

2

There are 2 best solutions below

0
Benzy Neez On BEST ANSWER

You could use a hidden placeholder to reserve the space needed for displaying the largest number, then show the actual number in an overlay with the alignment required. This way, the first column always has a fixed width, so the usernames will also be aligned.

Like this:

HStack {
    Text("9999")
        .hidden()
        .overlay(alignment: .trailing) {
            Text("\(item.number)")
                // all modifiers except font as before
        }
        .font(.system(size: 12, weight: .regular))
    Text(item.userName)
        // modifiers as before

    Spacer()

    Text(item.info)
        // modifiers as before
}

Screenshot

0
son On

You have to calculate the maximum width of the item.number, then set maxWidth for each item in the list. In your example, it's a fixed array size so I will calculate it with a computed property. If you get a dynamic array here, you should calculate when fetched data.

extension String {
   func widthOfString(usingFont font: UIFont) -> CGFloat {
        let fontAttributes = [NSAttributedString.Key.font: font]
        let size = self.size(withAttributes: fontAttributes)
        return size.width
    }
}

struct ContentFirst: Identifiable {
    ...
    var textWidth: CGFloat {
        "\(number)".widthOfString(usingFont: .systemFont(ofSize: 12, weight: .regular))
    }
}
...

struct FirstViewFourthSegment: View {
    ....
    var maxWidth: CGFloat {
        var max: CGFloat = 0
        for each in contentFirst {
            max = each.textWidth > max ? each.textWidth : max
        }
        return max + 15 //(padding leading & trailing are 7.5 each)
    }
    
    var body: some View {
        VStack {
            List(contentFirst) { item in
                HStack {
                    HStack {
                        Text("\(item.number)")
                            .font(.system(size: 12, weight: .regular))
                            .padding([.top, .bottom, .leading, .trailing], 7.5)
                            .background(item.number <= 3 ? Color.green : Color.clear)
                            .clipShape(Circle())
                            .foregroundColor(item.number <= 3 ? Color.white : Color.red)
                            .frame(maxWidth: maxWidth, alignment: .leading)
                        
                        Text(item.userName)
                            .frame(maxWidth: .infinity, alignment: .leading)
                            .font(.system(size: 12, weight: .regular))
                            .foregroundColor(Color.black)
                    }
                    
                    Spacer()
                    
                    Text(item.info)
                        .font(.system(size: 16, weight: .bold))
                        .foregroundColor(Color.black)
                        .padding(.trailing, 18)
                }
                .listRowSeparator(.hidden)
            }
        }
    }
}