Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI: How to force a specific view in an HStack to be in the centre

Given an HStack like the following:

        HStack{
            Text("View1")

            Text("Centre")

            Text("View2")

            Text("View3")
        }

How can I force the 'Centre' view to be in the centre?

like image 787
Confused Vorlon Avatar asked Jun 15 '20 14:06

Confused Vorlon


People also ask

How do I center a view in VStack?

A VStack defaults to horizontally aligning its contents by their centers. You can change this default by specifying the alignment (. leading, . center or .

What is alignment guide SwiftUI?

SwiftUI provides us with the alignmentGuide() modifier for just this purpose. This takes two parameters: the guide we want to change, and a closure that returns a new alignment. The closure is given a ViewDimensions object that contains the width and height of its view, along with the ability to read its various edges.

How many views can HStack SwiftUI have?

HStack can contain up to 10 static views, if you need more static views, you can nest HStack inside another HStack or Group to nest views inside.

How do I align text in SwiftUI?

To align a text view along the horizontal axis, you need to use . frame() modifier with maxWidth set to . infinity and alignment to the alignment you want.


2 Answers

Here is possible simple approach. Tested with Xcode 11.4 / iOS 13.4

demo

struct DemoHStackOneInCenter: View {
    var body: some View {
        HStack{
            Spacer().overlay(Text("View1"))

            Text("Centre")

            Spacer().overlay(
                HStack {
                    Text("View2")
                    Text("View3")
                }
            )
        }
    }
}

The solution with additional alignments for left/right side views was provided in Position view relative to a another centered view

backup

like image 139
Asperi Avatar answered Oct 27 '22 00:10

Asperi


the answer takes a handful of steps

  1. wrap the HStack in a VStack. The VStack gets to control the horizontal alignment of it's children
  2. Apply a custom alignment guide to the VStack
  3. Create a subview of the VStack which takes the full width. Pin the custom alignment guide to the centre of this view. (This pins the alignment guide to the centre of the VStack)
  4. align the centre of the 'Centre' view to the alignment guide

For the view which has to fill the VStack, I use a Geometry Reader. This automatically expands to take the size of the parent without otherwise disturbing the layout.

import SwiftUI


//Custom Alignment Guide
extension HorizontalAlignment {
    enum SubCenter: AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            d[HorizontalAlignment.center]
        }
    }
    
    static let subCentre = HorizontalAlignment(SubCenter.self)
}

struct CentreSubviewOfHStack: View {
    var body: some View {
        //VStack Alignment set to the custom alignment
        VStack(alignment: .subCentre) {
            HStack{
                Text("View1")
                
                //Centre view aligned
                Text("Centre")
                .alignmentGuide(.subCentre) { d in d.width/2 }
                
                Text("View2")
                
                Text("View3")
            }
            
            //Geometry reader automatically fills the parent
            //this is aligned with the custom guide
            GeometryReader { geometry in
                EmptyView()
            }
            .alignmentGuide(.subCentre) { d in d.width/2 }
        }
    }
}

struct CentreSubviewOfHStack_Previews: PreviewProvider {
    static var previews: some View {
        CentreSubviewOfHStack()
            .previewLayout(CGSize.init(x: 250, y: 100))
    }
}

Edit: Note - this answer assumes that you can set a fixed height and width of the containing VStack. That stops the GeometryReader from 'pushing' too far out

In a different situation, I replaced the GeometryReader with a rectangle:

            //rectangle fills the width, then provides a centre for things to align to
            Rectangle()
                .frame(height:0)
                .frame(idealWidth:.infinity)
                .alignmentGuide(.colonCentre) { d in d.width/2 }

Note - this will still expand to maximum width unless constrained!

Centre is in the centre

like image 28
Confused Vorlon Avatar answered Oct 27 '22 01:10

Confused Vorlon