SwiftUI Comment centrer verticalement une vue dans un VStack

Oct 09 2020

Je voudrais avoir une vue au centre vertical de l'écran, une vue en haut de l'écran et une vue centrée verticalement entre ces deux vues comme ceci:

Cela m'a pris 5 minutes à faire sur un storyboard mais je ne semble pas trouver un moyen de le faire dans SwiftUI 🙃.

J'ai déjà essayé avec plusieurs zstacks, vstacks, plusieurs alignements personnalisés mais c'est le plus proche que j'ai obtenu:

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)
}

Je garde le GeometryReader en dernier recours car cela semble être une mesure trop drastique pour cette mise en page apparemment simple.

Je suppose que j'aborde cela d'une manière erronée (encore trop d'UIKit / Contraintes dans ma tête) ..

Réponses

1 Asperi Oct 09 2020 at 00:51

Voici une solution possible. Testé avec Xcode 12 / iOS 14

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)
        }
    }
}