35

I'm creating an iOS minesweeper game and want to have a bar at the top with three pieces of information:

  • The high score (next to a trophy symbol) [LEFT]
  • The current time (next to a timer symbol) [CENTER]
  • The number of bombs left (next to a bomb symbol) [RIGHT]

However, the elements are not evenly aligned as you can see in the picture at the bottom- I'm guessing the spacers are automatically the same size. So, because the text on the left side of the screen is wider than that on the right, the middle text is offset to the right.

How can I align these items so the center time/image is perfectly in the middle with the other objects on the left/right?

My code looks like:

struct GameView: View {
    @EnvironmentObject var game: Game
    @State var labelHeight = CGFloat.zero
    
    var body: some View {
        VStack(alignment: .center, spacing: 10) {
            Text("MINESWEEPER")
                .font(.system(size: 25, weight: .bold, design: .monospaced))
                .kerning(3)
                .foregroundColor(.red)
            
            HStack(alignment: .center, spacing: 5) {
                Image(systemName: "rosette")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .foregroundColor(.black)
                Text(game.highScoreString)
                    .font(.system(size: 15, weight: .bold, design: .monospaced))
                    .foregroundColor(.black)

                Spacer()
                
                Image(systemName: "timer")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .foregroundColor(.black)
                Text(game.timerString)
                    .font(.system(size: 15, weight: .bold, design: .monospaced))
                    .foregroundColor(.black)
                
                Spacer()
                
                Image("top_bomb")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                Text(String(game.bombsRemaining))
                    .font(.system(size: 15, weight: .bold, design: .monospaced))
                    .foregroundColor(.black)
                    .overlay(GeometryReader(content: { geometry in
                        Color.clear
                            .onAppear(perform: {self.labelHeight = geometry.frame(in: .local).size.height})
                    }))
            }
            .frame(width: UIScreen.main.bounds.width * 0.9, height: labelHeight, alignment: .center)

            BoardView()
        }
    }
}

Game View Image

llllbbbbllll
  • 443
  • 1
  • 3
  • 10

4 Answers4

51

Instead of aligning by spacers, move each part into separate view, say LeftHeader, CenterHeader, and RightHeader and use frame alignment, which gives exact separation by equal sizes, like

HStack {
  LeftHeader()
    .frame(maxWidth: .infinity, alignment: .leading)
  CenterHeader()
    .frame(maxWidth: .infinity, alignment: .center)
  RightHeader()
    .frame(maxWidth: .infinity, alignment: .trailing)
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • 1
    Using this approach, I found that the default `HStack` spacing value can cause the center header to be off-center, so explicitly setting `HStack(spacing: 0)` may be helpful – Aaron T Jan 23 '22 at 21:00
  • What if I want to customize the alignment of the middle view? I think this solution does not help a lot compared to this answer: https://stackoverflow.com/a/74927217/7128177 – Mostafa Al Belliehy Feb 04 '23 at 15:18
  • Messing with ZStack means you can end up having one of the elements overlapping, which is not the case with this solutions. The 3 elements have the same space and can grow in Vertical if needed – itMaxence Jul 27 '23 at 17:14
9

This can be achieved using a ZStack , HStack and Spacers.

     ZStack{
            
            HStack{
                Text("Left")
                Spacer()
            }
            
            HStack{
                Text("Center")
            }

            HStack{
                Spacer()
                Text("Right")
            }
            
        }

Even if you remove left ( or right ) HStack, it won't affect the other components or the layout.

You can make it look bit nicer by adding paddings like this,

ZStack{
            
            HStack{
                Text("Left").padding([.leading])
                Spacer()
            }
            
            HStack{
                Text("Center")
            }

            HStack{
                Spacer()
                Text("Right").padding([.trailing])
            }
            
        }
Rukshan
  • 7,902
  • 6
  • 43
  • 61
  • 1
    I preferred this solution and voted for it. However, no need for the `HStack` for the middle `Text` view. I also used the `alignmentGuide()` modifier to customize the alignment of the middle view. – Mostafa Al Belliehy Feb 04 '23 at 15:16
  • 1
    .. or padding on the whole `ZStack`. I like this solution, especially when there are only two views (the left one or right one is missing). – coco Feb 09 '23 at 02:37
1
    ZStack {
        HStack{
            Text("Left")
            Spacer()
            Text("Right")
        }
        Text("Center")
    }
Alex F
  • 29
  • 3
0

I think you can create more one stack.

My solution like:

/** 
* spacing is an example
* minWidth calculated by fontSize 
*/
HStack(alignment: .center, spacing: 20){ 
    HStack{ Image, Text }.frame(minWidth: 30)
    HStack{ Image, Text }.frame(minWidth: 30)
    HStack{ Image, Text }.frame(minWidth: 30)
}

Hope it useful for you.

Nak
  • 251
  • 1
  • 6