SwiftUI自定义视觉效果visualEffect
SwiftUI自定义视觉效果visualEffect

SwiftUI自定义视觉效果visualEffect

.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

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注