跳到主要内容

在SwiftUI中使用CoordinateSpace实现视差标题效果

· 阅读需 6 分钟
GoSwiftUI
goswiftui.com

在移动和 Web 应用程序中,视差标题是一种流行的设计模式,可以创建视觉上令人惊叹的界面。通过在用户滚动时以不同的速度移动内容,视差标题可以创建深度和动态的错觉。

在本博客文章中,我们将讨论如何使用 SwiftUI 实现视差标题。我们将使用.named(coordinateSpace)修饰符创建一个坐标空间,用于计算标题在用户滚动时的位置。

创建 ParallaxHeader 视图

我们的视差标题的实现将位于 ParallaxHeader 视图中。ParallaxHeader 视图接受三个参数:coordinateSpace、defaultHeight 和 content。

struct ParallaxHeader<Content: View, Space: Hashable>: View {
let content: () -> Content
let coordinateSpace: Space
let defaultHeight: CGFloat

init(
coordinateSpace: Space,
defaultHeight: CGFloat,
@ViewBuilder _ content: @escaping () -> Content
) {
self.content = content
self.coordinateSpace = coordinateSpace
self.defaultHeight = defaultHeight
}

var body: some View {
// ...
}

private func offset(for proxy: GeometryProxy) -> CGFloat {
// ...
}

private func heightModifier(for proxy: GeometryProxy) -> CGFloat {
// ...
}
}

content 参数是一个视图构建器闭包,返回将显示在标题中的视图。coordinateSpace 参数是将用于计算标题位置的坐标空间的名称。这应该是我们稍后将实现的包含 ScrollView 的坐标空间。defaultHeight 参数是偏移量为 0 时标题的默认高度。

Body

在 ParallaxHeader 视图的正文中,我们使用 GeometryReader 来确定标题的位置。我们使用 offset(for:)和 heightModifier(for:)辅助函数来计算偏移量和高度修饰器,稍后我们将详细讨论这两个函数。

var body: some View {
GeometryReader { proxy in
let offset = offset(for: proxy)
let heightModifier = heightModifier(for: proxy)
let blurRadius = min(
heightModifier / 20,
max(10, heightModifier / 20)
)
content()
.edgesIgnoringSafeArea(.horizontal)
.frame(
width: proxy.size.width,
height: proxy.size.height + heightModifier
)
.offset(y: offset)
.blur(radius: blurRadius)
}.frame(height: defaultHeight)
}

我们的 ParallaxHeader 将会是神奇的!当向上滚动时,它将给我们一种深度错觉。当我们尝试向下拉动时,它还会使内容拉伸和模糊!这将丰富用户体验并提供愉快的滚动感!

模糊效果

我们正在计算标题图像的模糊半径。它不应该太大以遮挡图片,但足够大以创建出令人愉悦的效果。我选择了最大模糊半径为 10。为了使模糊效果逐渐增加,我通过将 heightModifier 除以 20 来减小增加率。您可以尝试不同的值,找到最适合您特定情况的值。

计算偏移量

private func offset(for proxy: GeometryProxy) -> CGFloat {
let frame = proxy.frame(in: .named(coordinateSpace))
if frame.minY < 0 {
return -frame.minY * 0.8
}
return -frame.minY
}

offset(for:) 方法根据 GeometryProxy 相对于指定的 coordinateSpace 的位置来计算标题视图的当前偏移量。如果标题在滚动视图的顶部之上,该方法返回一个修改过的偏移量,使标题的移动速度比内容慢。否则,该方法返回与用户滚动位置相匹配的偏移量。第二种选项确保我们的内容保持粘在屏幕顶部。这个计算是基于指定的 coordinateSpace 中标题视图的框架。

计算高度

private func heightModifier(for proxy: GeometryProxy) -> CGFloat {
let frame = proxy.frame(in: .named(coordinateSpace))
return max(0, frame.minY)
}

heightModifier(for:) 方法根据 GeometryProxy 相对于指定的 coordinateSpace 的位置来计算标题视图高度的修饰器。如果标题完全可见,heightModifier 被设置为零。如果标题完全不可见,heightModifier 被设置为零的最小值 - 我们不希望在向上滚动时使标题变小!否则,heightModifier 被设置为标题在指定的 coordinateSpace 中的当前位置。

使用

最后,我们可以使用我们的视差标题并享受效果!

struct ContentView: View {
private enum CoordinateSpaces {
case scrollView
}
var body: some View {
ScrollView {
ParalaxHeader(
coordinateSpace: CoordinateSpaces.scrollView,
defaultHeight: 400
) {
Image("flower")
.resizable()
.scaledToFill()
}
Rectangle()
.fill(.blue)
.frame(height: 1000)
.shadow(color: .black.opacity(0.8), radius: 10, y: -10)
.offset(y: -10)
}
.coordinateSpace(name: CoordinateSpaces.scrollView)
.edgesIgnoringSafeArea(.top)

}
}

我已经创建了一个简单的结构,使用图像作为标题内容。为此,我使用了一张免费的花朵图片。为了模拟大量可滚动的内容,我使用了一个非常高的矩形。这足以演示视差效果。我们代码的关键部分是 .coordinateSpace(name: CoordinateSpaces.scrollView) 修饰符。这个修饰符允许我们精确计算视差效果的偏移量。另外,我创建了一个 CoordinateSpaces 枚举,为我们的坐标空间提供了类型安全性和命名空间。

最终效果

以下是我们视差标题的最终演示效果:

Working effect

希望你能喜欢。