Search code examples
iosswiftuiswiftui-text

How to centrally align one character within a string in a frame/view?


I'd like to centrally align the numerical digit within a frame, so that the sign is ignored. E.g. In a circular shape, I'd like to align the string "+9" so that the "9" is dead-centre and not the position between the "+" and the "9".

I used a ZSTACK to align the digit and the sign separately, but this is messy and fails when rendering on different devices with different screen estates, as well as Dynamic Type.

Here is example code - the red circle is with default alignment, the green with an approximate algorithm.

                let diam = 75.0
        
        ZStack {
            Rectangle()
                            .frame(width: 1, height: .infinity)
                            .foregroundColor(.yellow)
            
            // Aproximately correct!
            VStack {
                
                ForEach(9..<11) { i in
                    ZStack {
                        Circle()
                            .frame(width: diam, height: diam)
                            .foregroundColor(.green)
                        
                        Text("\(i)")
                        Text("+").font(.title)
                            .frame(width: diam * 0.75, height: diam * 0.75, alignment: .leading)
                    }
                    .foregroundColor(  .black).font(.title)
                }
                // Wrong!
                ZStack {
                    Circle()
                        .frame(width: diam, height: diam)
                        .foregroundColor(.red)
                    
                    Text("+23")
                }
                .foregroundColor(  .black).font(.title)
            }

        }

enter image description here

There has to be a better solution.


Solution

  • If you want the numerical part of the string to be centered (as opposed to the first digit, the title of the question confused me for a while), then this can be done using an HStack:

    • build the text as prefix + numeric part + prefix
    • the trailing part is kept hidden
    • use spacing: 0 to avoid gaps between the parts.
    var body: some View {
        let vals = [9, 10, 23]
        let diam = 75.0
    
        ZStack {
            Rectangle()
                .frame(width: 1)
                .frame(maxHeight: .infinity)
                .foregroundStyle(.yellow)
    
            VStack {
                ForEach(vals, id: \.self) { i in
                    ZStack {
                        Circle()
                            .frame(width: diam, height: diam)
                            .foregroundStyle(.green)
                        HStack(spacing: 0) {
                            Text("+")
                            Text("\(i)")
                            Text("+").hidden()
                        }
                    }
                    .font(.title)
                    .foregroundStyle(.black)
                }
            }
        }
    }
    

    Screenshot