SwiftUI 学习 (五)

2,613 阅读3分钟

「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战

使用 Size Class 创建不同的布局

SwiftUI 通过使用Size Class 来创建不同的布局。 要使用它们,首先创建一个将存储其值的 @Environment 对象,然后在需要时检查该属性的值,查找 .compact 或 .regular 大小类。

struct ContentView: View {
    @Environment(.horizontalSizeClass) var horizontalSizeClass

    var body: some View {
        if horizontalSizeClass == .compact {
            Text("Compact")
        } else {
            Text("Regular")
        }
    }
}

image.png

Size Class是通过使用 VStack 或 HStack 为您的内容使您的用户界面智能地适应可用空间的好方法。 例如,如果您有很多空间,您可能会水平放置东西,但当空间有限时切换到垂直布局。

使用Size Class来自动的切换Vstack 和 HStack

SwiftUI 可以监控当前的大小类来决定应该如何布局,例如从空间充足时的 HStack 切换到空间受限时的 VStack。

可以编写一个新的 AdaptiveStack 视图,它会自动为我们在水平和垂直布局之间切换。 这使得在 iPad 上创建出色的布局变得更加简单,

struct AdaptiveStack<Content: View>: View {
    @Environment(\.horizontalSizeClass) var sizeClass
    let horizontalAlignment: HorizontalAlignment
    let verticalAlignment: VerticalAlignment
    let spacing: CGFloat?
    let content: () -> Content
    
    init(horizontalAlignment: HorizontalAlignment = .center, verticalAlignment: VerticalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: @escaping () -> Content) {
            self.horizontalAlignment = horizontalAlignment
            self.verticalAlignment = verticalAlignment
            self.spacing = spacing
            self.content = content
        }
    
    var body: some View {
            Group {
                if sizeClass == .compact {
                    VStack(alignment: horizontalAlignment, spacing: spacing, content: content)
                } else {
                    HStack(alignment: verticalAlignment, spacing: spacing, content: content)
                }
            }
        }

}

struct ContentView: View {
    var body: some View {
        AdaptiveStack {
            Text("Horizontal when there's lots of space")
            Text("but")
            Text("Vertical when space is restricted")
        }
    }
}

image.png

  • 首先,在自定义View 监听 HorizontalSizeClass,以便每次大小类更改时都会更新它。

  • 并且为提供了单独存储水平和垂直对齐的参数,因此您可以准确控制布局应如何适应。

  • 有一个可选的 CGFloat 值用于间距,因为这就是 VStack 和 HStack 的工作方式。 如果你想要更多的控制,你可以添加 HorizontalSpacing 和 verticalSpacing 属性。

  • content 属性是一个不接受参数并返回某种内容的函数,最终将依赖该内容来创建他们的布局。

  • 我们的初始化程序将它们全部隐藏起来以备后用。

  • 在 body 属性中,可以读取水平尺寸类,然后在 VStack 或 HStack 中包装对 content() 的调用。

ScrollView 使用

SwiftUI 的 ScrollView 允许开发者相对轻松地创建视图的滚动容器,因为它会自动调整自身大小以适应我们放置在其中的内容,并且还会自动添加额外的插图以避开安全区域。

ScrollView {
    VStack(spacing: 20) {
        ForEach(0..<10) {
            Text("Item ($0)")
                .foregroundColor(.white)
                .font(.largeTitle)
                .frame(width: 200, height: 200)
                .background(Color.red)
        }
    }
}
.frame(height: 350)

image.png

默认情况下,滚动视图是垂直的,但可以通过传入 .horizontal 作为第一个参数来控制轴。 因此,我们可以将之前的示例翻转为水平的,如下所示:

ScrollView(.horizontal) {
    HStack(spacing: 20) {
        ForEach(0..<10) {
            Text("Item ($0)")
                .foregroundColor(.white)
                .font(.largeTitle)
                .frame(width: 200, height: 200)
                .background(Color.red)
        }
    }
}

image.png

可以使用 [.horizontal, .vertical] 同时指定两个轴。

ScrollView(.horizontal, showsIndicators: false) {
    HStack(spacing: 20) {
        ForEach(0..<10) {
            Text("Item ($0)")
                .foregroundColor(.white)
                .font(.largeTitle)
                .frame(width: 200, height: 200)
                .background(Color.red)
        }
    }
}

将ScrollView滑动到指定位置

如果想以编程方式让 SwiftUI 的 ScrollView 移动到特定位置,你应该在其中嵌入一个 ScrollViewReader。 这提供了一个 scrollTo() 方法,该方法可以移动到父滚动视图内的任何视图,只需提供其锚点。

例如,这会在垂直滚动视图中创建 100 个彩色框,当您按下按钮时,它将直接滚动到 ID 为 8 的框:

struct ContentView: View {
    let colors: [Color] = [.red, .green, .blue]

    var body: some View {
        ScrollView {
            ScrollViewReader { value in
                Button("Jump to #8") {
                    value.scrollTo(8)
                }
                .padding()

                ForEach(0..<100) { i in
                    Text("Example (i)")
                        .font(.title)
                        .frame(width: 200, height: 200)
                        .background(colors[i % colors.count])
                        .id(i)
                }
            }
        }
        .frame(height: 350)
    }
}

为了更好地控制滚动,您可以指定第二个参数称为锚点,以控制滚动完成后目标视图的位置。

例如,这将滚动到与以前相同的视图,但这次将该视图放在顶部:

struct ContentView: View {
    let colors: [Color] = [.red, .green, .blue]

    var body: some View {
        ScrollView {
            ScrollViewReader { value in
                Button("Jump to #8") {
                    value.scrollTo(8, anchor: .top)
                }
                .padding()

                ForEach(0..<100) { i in
                    Text("Example (i)")
                        .font(.title)
                        .frame(width: 200, height: 200)
                        .background(colors[i % colors.count])
                        .id(i)
                }
            }
        }
        .frame(height: 350)
    }
}