Center Item Inside Horizontal Stack
Asked Answered
F

4

3

If I have 3 items inside a Horizontal Stack, I thought I could do something like this:

HStack{

      Text("test")

      Spacer()

      item2()

      Spacer()

      Text("test")
}

to center item2() in between the two Text views. However, the problem with this is that item2() isn't necessarily always centered, because, lets say Text("test") changes to Text("a") or something. This causes problems, and the second item isn't always centered on the screen.

How can I make it so item2() is always centered?

Thanks

Flofloat answered 29/12, 2019 at 6:30 Comment(0)
S
7

I would propose the following start point (simplest case... read below why)

Center horizontally one element

As it's seen it really gives centred w/o frame shift with correctly aligned side elements, but ... there is drawback - it will work in such simplest variant only if it is known in advance that those three text elements should never overlap in user run-time. If it is the case (really there are such) then this approach just goes. However if left/right text might grow in run-time, then more calculations will be needed to limit their width by .frame(maxWidth:) depending on the width of centred element... that variant is more complicated, but it is feasible.

var body: some View {
        ZStack {
            HStack {
                Text("Longer side")
                Spacer()
                Text("One")
            }
            item2()
        }
    }

private func item2() -> some View {
    Text("CENTER")
        .background(Color.yellow)
        .border(Color.red)
}

Update: here is possible approach to limit one of the side to not overlap centred one (contains async updates, so should be tested in Live Preview or Simulator)

So... if left text is dynamic and the requirement to cut trailing symbols, here is how it could go ...

enter image description here

and it automatically fit well on device orientation change

enter image description here

struct TestHorizontalPinCenter: View {

    @State var centerFrame: CGRect = .zero
    
    private let kSpacing: CGFloat = 4.0
    var body: some View {
            ZStack {
                HStack {
                    Text("Longer side very long text to fit")
                        .lineLimit(1)
                        .frame(maxWidth: (centerFrame == .zero ? .infinity : centerFrame.minX - kSpacing), alignment: .leading)

                    Spacer()
                    
                    Text("One")
                }
                item2()
                    .background(rectReader($centerFrame))
            }
        }
    
    private func item2() -> some View {
        Text("CENTER")
            .background(Color.yellow)
            .border(Color.red)
    }

    func rectReader(_ binding: Binding<CGRect>) -> some View {
        return GeometryReader { (geometry) -> AnyView in
            let rect = geometry.frame(in: .global)
            DispatchQueue.main.async {
                binding.wrappedValue = rect
            }
            return AnyView(Rectangle().fill(Color.clear))
        }
    }
}

And if it is needed to wrap left side, then .lineLimit(nil) and additional layout will be needed, and solution growth, but the idea is the same. Hope this will be helpful for someone.

Snowblind answered 29/12, 2019 at 9:23 Comment(0)
P
5

I had the same problem and the solution from @Asperi works, but i had problems with multiline texts and some performance issues if i use it in a list.

The following solution solved all the problems.

HStack(alignment: .center) {
  Text("test")
      .frame(maxWidth: .infinity)
  item2()
  Text("test")            
      .frame(maxWidth: .infinity)
}
Polyphony answered 5/6, 2020 at 7:51 Comment(0)
L
0

You may need to add some customized Alignment components.

extension HorizontalAlignment {
    private enum MyHAlignment: AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            return d[HorizontalAlignment.center]
        }
    }
    
    static let myhAlignment = HorizontalAlignment(MyHAlignment.self)
}


HStack {
    Spacer()
    Text("jjjjjjjjjj")
    Spacer()
    
    Image("image").alignmentGuide(.myhAlignment) { (ViewDimensions) -> CGFloat in
        return ViewDimensions[HorizontalAlignment.center]
    }
    
    Spacer()     
    Text("test")       
}
.frame(alignment: Alignment(horizontal: .myhAlignment, vertical: .center))



Lucia answered 29/12, 2019 at 7:29 Comment(0)
S
0

To center views you can use a ZStack:

ZStack {
    item2()

    HStack {
        Text("test")
        Spacer()
        Text("test")
    }
}
Supercharge answered 29/12, 2019 at 9:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.