SwiftUI How to vertically center a view inside a VStack

14k views Asked by At

I would like to have one view in the vertical center of the screen, one view at the top of the screen and one view vertically centered between these two views like so:

enter image description here

This took me 5min to do on a storyboard but I don't seem to find a way to do it in SwiftUI .

I already tried with multiple zstacks, vstacks, multiple custom alignments but this is the closest I got:

struct SelectionView: View {
    var body: some View {
        ZStack(alignment: .myAlignment) {
            Color.green
                .edgesIgnoringSafeArea(.all)
            
            
            VStack {
                Image(systemName: "clock")
                    .resizable()
                    .foregroundColor(.white)
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 156, height: 80)
                
//                Spacer()
                
                Text("My\nmultiline label")
                    .multilineTextAlignment(.center)
                    .font(.title)
                    .foregroundColor(.white)

//                Spacer()
                
                VStack(spacing: 16) {
                    RoundedRectangle(cornerRadius: 5).fill(Color.white).frame(height: 79)
                    RoundedRectangle(cornerRadius: 5).fill(Color.white).frame(height: 79)
                }
                .alignmentGuide(VerticalAlignment.myAlignment) { dimension in
                    dimension[VerticalAlignment.center]
                }
                .layoutPriority(1)
            }
            .padding([.leading, .trailing], 24)
        }
    }
    
}

struct SelectionView_Previews: PreviewProvider {
    static var previews: some View {
        LanguageSelectionView()
    }
}

// MARK

extension HorizontalAlignment {
  
    enum MyHorizontal: AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat
                 { d[HorizontalAlignment.center] }
    }

    static let myAlignment =
                 HorizontalAlignment(MyHorizontal.self)
}

extension VerticalAlignment {
    enum MyVertical: AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat
                 { d[VerticalAlignment.center] }
    }
  
    static let myAlignment = VerticalAlignment(MyVertical.self)
}

extension Alignment {
    static let myAlignment = Alignment(horizontal: .myAlignment,
                               vertical: .myAlignment)
}

I'm keeping the GeometryReader as a last resort as it feels like a too drastic measure for this seemingly simple layout..

I guess I'm approaching this in some wrong way (still too much UIKit/Constraints in my head)..

1

There are 1 answers

3
Asperi On BEST ANSWER

Here is possible solution. Tested with Xcode 12 / iOS 14

demo

struct SelectionView: View {
    var body: some View {
        ZStack {
            Color.green
                .edgesIgnoringSafeArea(.all)
            
            VStack(spacing: 16) {
                Color.clear
                    .overlay(
                        VStack {
                            Image(systemName: "clock")
                                .resizable()
                                .foregroundColor(.white)
                                .aspectRatio(contentMode: .fit)
                                .frame(width: 156, height: 80)
                            Color.clear
                                .overlay(
                                    Text("My\nmultiline label")
                                        .multilineTextAlignment(.center)
                                        .font(.title)
                                        .foregroundColor(.white)
                            )
                        }
                    )
                RoundedRectangle(cornerRadius: 5).fill(Color.white).frame(height: 79)
                RoundedRectangle(cornerRadius: 5).fill(Color.white).frame(height: 79)
                Color.clear
            }
            .padding([.leading, .trailing], 24)
        }
    }
}