如何根据确切的设备动态更改 SwiftUI 中对象的绝对定位

How to dynamically change absolute positioning of objects in SwiftUI depending on the exact device

我正在制作一个 SwiftUI 应用程序并为其提供“新形态”设计,但我的问题是,由于我使用了绝对定位(因为我找不到任何其他可行的解决方案),该应用程序看起来在 iPhone 12 Pro 上很好,在另一个 phones/tablets 上看起来很糟糕。我尝试使用 Spacer() 但我似乎无法理解如何将顶部“横幅”制作成我想要的大小。

我的代码

//
//  ContentView.swift


import SwiftUI


extension Color {
    static let offWhite = Color(red: 225 / 255, green: 225 / 255, blue: 235 / 255)
    
    static let superoffwhite = Color(red: 215 / 255, green: 215 / 255, blue: 225 / 255)
    
    static let hellocolor = Color(red: 183 / 255,green: 114 / 255,blue: 126 / 255)
    
    static let StartPlayingColor = Color(red: 114 / 255,green: 138 / 255 ,blue: 183 / 255)

}

extension LinearGradient {
    init(_ colors: Color...) {
        self.init(gradient: Gradient(colors: colors), startPoint: .topLeading, endPoint: .bottomTrailing)
    }
}

struct ContentView: View {
    var body: some View {
        ZStack {
            Color.offWhite
            RoundedRectangle(cornerRadius: 98)
                .fill(Color.superoffwhite)
                .frame(width: 422, height: 459)
                .position(x:183, y: 125)

                .shadow(color: Color.black.opacity(0.2), radius: 10, x: 10, y: 10)
                .shadow(color: Color.white.opacity(0.7), radius: 10, x: -5, y: -5)
            
            
            Text("Hello\nAmit")
                .foregroundColor(Color.hellocolor)
                .font(Font.custom("Rubik", size: 72))
                .multilineTextAlignment(.center)
                .position(x: 183, y: 180)
            
            Button(action: {
                print("Button tapped")
            }) {
                Text("Start Playing")
                    .foregroundColor(.StartPlayingColor)
                    .position(x: 193, y: 382)
            }
            .buttonStyle(SimpleButtonStyle()).position(x: 193, y: 486)
            
            
        }
    }
}

struct SimpleButtonStyle: ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .background(
                Group {
                    if configuration.isPressed {
                        RoundedRectangle(cornerRadius: 98)      .fill(Color.superoffwhite)
                            .overlay(
                                RoundedRectangle(cornerRadius: 98)
                                    .stroke(Color.gray, lineWidth: 4)
                                    .blur(radius: 98)
                                    .offset(x: 2, y: 2)
                                    .mask(Circle().fill(LinearGradient(Color.black, Color.clear)))
                            )
                            .overlay(
                                RoundedRectangle(cornerRadius: 98)                                 .stroke(Color.white,              lineWidth: 8)
                                    .blur(radius: 98)
                                    .offset(x: -2, y: -2)
                                    .mask(Circle().fill(LinearGradient(Color.clear, Color.black)))

                            )
                            .frame(width: 242, height: 91)

                    }
                    else {
                        RoundedRectangle(cornerRadius: 98)                            .fill(Color.superoffwhite)
                            .shadow(color: Color.black.opacity(0.2), radius: 10, x: 10, y: 10)
                            .shadow(color: Color.white.opacity(0.7), radius: 10, x: -5, y: -5)
                            .frame(width: 242, height: 91)
                    }
                }
            )
        }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


应用在多个设备上的图片

如有任何帮助,我们将不胜感激!

这应该可以帮助您完成大部分工作——您可以进行调整以使其更接近您的理想状态。

struct ContentView: View {
    var body: some View {
        ZStack {
            Color.offWhite.edgesIgnoringSafeArea(.all)
            
            VStack(spacing: 30) {
                VStack {
                    Text("Hello\nAmit")
                        .foregroundColor(Color.hellocolor)
                        .font(Font.custom("Rubik", size: 72))
                        .multilineTextAlignment(.center)
                }
                .frame(maxWidth: .infinity)
                .frame(height: 300)
                .background(
                    RoundedCorner(radius: 98, corners: [.bottomLeft, .bottomRight])
                        .fill(Color.superoffwhite)
                        .shadow(color: Color.black.opacity(0.2), radius: 10, x: 10, y: 10)
                        .shadow(color: Color.white.opacity(0.7), radius: 10, x: -5, y: -5)
                        .edgesIgnoringSafeArea(.top)

                )
                
                Button(action: {
                    print("Button tapped")
                }) {
                    Text("Start Playing")
                        .foregroundColor(.StartPlayingColor)
                }
                .buttonStyle(SimpleButtonStyle())
                .padding(.horizontal, 40)
                
                Spacer()
            }
            
        }
    }
}

struct RoundedCorner: Shape {

    var radius: CGFloat = .infinity
    var corners: UIRectCorner = .allCorners

    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        return Path(path.cgPath)
    }
}

struct SimpleButtonStyle: ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .frame(maxWidth: .infinity)
            .frame(height: 91)
            .background(
                Group {
                    if configuration.isPressed {
                        RoundedRectangle(cornerRadius: 98)
                            .fill(Color.superoffwhite)
                            .overlay(
                                RoundedRectangle(cornerRadius: 98)
                                    .stroke(Color.gray, lineWidth: 4)
                                    .blur(radius: 98)
                                    .offset(x: 2, y: 2)
                                    .mask(Circle().fill(LinearGradient(Color.black, Color.clear)))
                            )
                            .overlay(
                                RoundedRectangle(cornerRadius: 98)                                 .stroke(Color.white, lineWidth: 8)
                                    .blur(radius: 98)
                                    .offset(x: -2, y: -2)
                                    .mask(Circle().fill(LinearGradient(Color.clear, Color.black)))
                            )
                    }
                    else {
                        RoundedRectangle(cornerRadius: 98)
                            .fill(Color.superoffwhite)
                            .shadow(color: Color.black.opacity(0.2), radius: 10, x: 10, y: 10)
                            .shadow(color: Color.white.opacity(0.7), radius: 10, x: -5, y: -5)
                    }
                }
            )
    }
}

我使用的工具:

  1. 尽量避免对顶部安全区域的任何内容进行硬编码(特别是考虑到它在不同设备上的大小可能不同)。相反,当您需要覆盖安全区域时,请利用 .edgesIgnoringSafeArea

  2. 当您需要准确描述某个尺寸(如身高)时使用frame。您还可以使用 frame 占用可用的宽度或高度,如下所示:.frame(maxWidth: .infinity)

  3. 使用 VStack 垂直布局视图 -- 不要依赖绝对定位。

  4. 我使用了 的答案作为您的顶视图,其中只有底角是圆角的。

  5. 使用paddingspacing分隔视图。


正如我所说,这应该可以帮助您完成大部分工作,尽管您可能需要进行更多调整。此外,请记住,在针对多种尺寸的设备进行设计时,有时您可能会被迫做出一些小的妥协,以使一切都能适用于绝大多数尺寸。

您还可以查看 GeometryReader 以了解何时需要了解有关您正在填充的尺码以及尺码 类 到 @Environment 的信息,以备不时之需根据不同的尺寸决定。例如,我为顶视图保留了 300 的高度,但您可以使用这些选项将其作为可用屏幕尺寸的基础。此外,您可以在上方视图中的 Text 上使用 padding 以仅使其达到必要的高度 - 这可能是我个人的选择。