.visualEffect 是一种 SwiftUI 中较新的 API,它允许开发者更灵活地创建自定义的视觉效果。它通常用于创建动态背景模糊、着色等效果,同时可以通过 proxy 获取视图的几何信息,从而基于视图的布局动态调整效果。
.visualEffect 的用法
基本语法
visualEffect(_ effect: @escaping @Sendable (EmptyVisualEffect, GeometryProxy) -> some VisualEffect) -> some View
.visualEffect { content, proxy in … } 是一个闭包,接收以下两个参数:
1、content:原始的子视图内容。
2、proxy:一个提供几何数据的代理,可以用来获取视图在父视图中的位置或尺寸。
代码示例
以下是一个动态模糊背景的示例,其中模糊的程度会根据视图的位置动态调整:
struct ContentView: View {
var body: some View {
ScrollView {
VStack(spacing: 20) {
ForEach(0..<10) { index in
Text("Item \(index)")
.font(.title)
.padding()
.frame(maxWidth: .infinity)
.frame(height: 100)
.background(
Color.orange.opacity(0.5)
.visualEffect { content, proxy in
let minY = proxy.frame(in: .global).minY
let blurRadius = max(0, 10 - (minY / 50)) // 动态调整模糊程度
return content
.blur(radius: blurRadius) // 应用模糊
}
)
.cornerRadius(10)
}
}
.padding()
}
}
}
代码分析
1、proxy.frame(in:):
用于获取视图在特定坐标空间中的位置(如 .global 或 .local)。
这里使用 proxy.frame(in: .global).minY 获取每个 Text 的顶部相对屏幕顶部的位置。
2、动态模糊调整:
通过计算 minY 动态调整模糊程度(blur(radius:))。
随着用户滚动,视图的位置会改变,从而更新模糊效果。
值得注意的是,这里的proxy.frame(in:)与GeometryReader的坐标类似:
GeometryReader { proxy in
let frame = proxy.frame(in: .named("CustomSpace"))
Text("Text Position: \(frame.debugDescription)")
.position(x: 100, y: 100)
}
其他可能的使用场景
1、动态透明度调整
基于视图的位置动态调整透明度:
struct ContentView: View {
var body: some View {
ScrollView {
VStack(spacing: 20) {
ForEach(0..<10) { index in
Text("Item \(index)")
.font(.title)
.padding()
.frame(maxWidth: .infinity)
.frame(height: 100)
.background(
Color.blue.opacity(0.5)
.visualEffect { content, proxy in
let minY = proxy.frame(in: .global).minY
let opacity = max(0, 1 - (minY / 300)) // 根据位置调整透明度
return content.opacity(opacity) // 动态调整透明度
}
)
.cornerRadius(10)
}
}
.padding()
}
}
}
通过visualEffect可以获取空间坐标,并根据最小的Y值动态调整透明度。
2、自定义动画效果
基于 proxy 的值动态调整旋转、缩放等动画效果:
struct ContentView: View {
var body: some View {
ScrollView {
VStack(spacing: 20) {
ForEach(0..<10) { index in
Text("Item \(index)")
.font(.title)
.padding()
.frame(maxWidth: .infinity)
.frame(height: 100)
.background(
Color.green.opacity(0.5)
.visualEffect { content, proxy in
let minY = proxy.frame(in: .global).minY
let scale = max(0.5, 1 - (minY / 1000)) // 根据位置缩放
let rotation = minY / 10 // 动态旋转角度
return content
.scaleEffect(scale)
.rotationEffect(.degrees(rotation))
}
)
.cornerRadius(10)
}
}
.padding()
}
}
}
使用visualEffect的proxy设置缩放和旋转的效果。
.visualEffect 的特点
1、实时计算视图效果:
使用 proxy 提供的几何信息,动态调整视图的视觉属性。
2、灵活性强:
可以组合其他修饰符(如 blur、scaleEffect、rotationEffect)实现复杂的视觉效果。
3、性能优化:
SwiftUI 在底层对模糊和动态视觉效果做了性能优化,适用于动态场景。
总结
.visualEffect 是 SwiftUI 中强大的 API,用于动态创建视觉效果。
使用 proxy 提供的几何信息,可以实现模糊、透明度、缩放、旋转等效果。
非常适合在滚动、动画等场景中增强用户体验。
因为visualEffect返回的是视图,所以如果return中含义变量等内容,需要使用return返回视图。
.visualEffect { content, proxy in
let minY = proxy.frame(in: .global).minY
let scale = max(0.5, 1 - (minY / 1000)) // 根据位置缩放
let rotation = minY / 10 // 动态旋转角度
return content
.scaleEffect(scale)
.rotationEffect(.degrees(rotation))
}
扩展知识
visualEffect是否能平替GeometryReader?
在某些情况下,.visualEffect 可以部分替代 GeometryReader 的功能,尤其是需要动态调整视觉效果时。但两者的设计目标和使用场景并不完全相同:
相似之处
1、动态布局和几何信息:
GeometryReader:提供视图的几何信息(如位置、大小)以供使用。
visualEffect:通过 proxy 参数也可以访问视图的几何信息,类似于 GeometryReader 的功能。
2、基于几何信息调整内容:
都可以基于视图的位置或大小动态调整其内容或修饰符(如缩放、旋转、模糊等)。
区别
1、GeometryReader:提供几何信息供布局调整,可以动态调整子视图的位置和大小,如果大量嵌套可能影响性能。
用法:通常需要嵌套并返回子视图,无法直接实现模糊、透明等效果。
2、visualEffect:用于创建动态视图效果(模糊、透明度等),主要用于修饰视觉效果,无法直接调整布局。性能方面更优,适合动态效果。
用法:与视觉效果直接绑定,内置动态视觉效果支持。
还有一点,如果没有显式约束 GeometryReader 的大小,它会占据整个父视图的可用空间,通常需要使用frame限制大小。而visualEffect不会占用全部父视图的可用空间。
可以替代的场景
简单的动态视觉效果
如果目标是根据视图的位置或尺寸调整模糊、透明度或缩放等视觉属性,visualEffect 是更合适的选择。
示例:使用 visualEffect 替代 GeometryReader 动态调整透明度。
struct ContentView: View {
var body: some View {
ScrollView {
VStack {
ForEach(0..<10) { index in
Text("Item \(index)")
.font(.title)
.padding()
.frame(maxWidth: .infinity)
.frame(height: 100)
.background(
Color.blue.opacity(0.5)
.visualEffect { content, proxy in
let minY = proxy.frame(in: .global).minY
let opacity = max(0, 1 - minY / 300)
content.opacity(opacity) // 动态调整透明度
}
)
.cornerRadius(10)
}
}
}
}
}
在这个例子中,visualEffect 的功能与 GeometryReader 类似,但更直观和简洁。
不适合替代的场景
复杂布局调整
如果需要根据视图的几何信息动态调整布局,例如重新计算视图的位置或尺寸,GeometryReader 更加灵活。
示例:使用 GeometryReader 动态调整布局
struct ContentView: View {
var body: some View {
GeometryReader { proxy in
VStack {
Text("Top")
.frame(width: proxy.size.width * 0.5)
.background(Color.red)
Text("Bottom")
.frame(width: proxy.size.width * 0.8)
.background(Color.blue)
}
}
}
}
当存在纯动态调整布局的场景时,使用GeometryReader。
在这个场景中,visualEffect 无法替代 GeometryReader,因为 visualEffect 专注于视觉效果,而非布局调整。
组合使用
在一些复杂的场景中,GeometryReader 和 .visualEffect 可以组合使用。例如:
使用 GeometryReader 动态调整视图的布局。
使用 .visualEffect 动态调整视图的视觉效果。示例:布局调整 + 视觉效果
struct ContentView: View {
var body: some View {
GeometryReader { proxy in
ScrollView {
ForEach(0..<10) { index in
Text("Item \(index)")
.font(.title)
.padding()
.frame(width: proxy.size.width * 0.8, height: 100)
.background(
Color.green.opacity(0.5)
.visualEffect { content, visualProxy in
let minY = visualProxy.frame(in: .global).minY
let blurRadius = max(0, 10 - minY / 50)
return content.blur(radius: blurRadius) // 动态调整模糊
}
)
.cornerRadius(10)
.padding(.horizontal)
}
}
}
}
}
通过GeometryReader设置Text宽度为父视图的80%,通过visualEffect设置动态模糊效果。
小结
选择 visualEffect:当需求是动态调整视觉效果(模糊、透明度等)时。
选择 GeometryReader:当需求是根据几何信息调整布局时。
两者组合使用:在复杂场景中,布局和视觉效果可以分开处理,各自发挥优势。
如果目标是优化视觉体验,且无需复杂的布局调整,可以优先尝试使用 .visualEffect,因为它更简洁,性能也更好。
相关文章
1、SwiftUI容器视图GeometryReader:https://fangjunyu.com/2024/12/15/swiftui%e5%ae%b9%e5%99%a8%e8%a7%86%e5%9b%begeometryreader/
2、ScrollView effects using visualEffect() and scrollTargetBehavior():https://www.hackingwithswift.com/books/ios-swiftui/scrollview-effects-using-visualeffect-and-scrollTargetBehavior