How to dynamically adjust the appearance of a view based on its size and location
How to dynamically adjust the appearance of a view based on its size and location 관련
Updated for Xcode 15
New in iOS 17
SwiftUI's visualEffect()
modifier lets us read the geometry proxy for a view without using a GeometryReader
, which means we can look at the size and location of a view without affecting its layout behavior.
Important
This modifier is specifically for applying visual effects such as adjusting colors or adding blur, and cannot adjust how the frame of your content is calculated for layout purposes. It can adjust frame-like things such as the offset and scale of your view, because they don't affect layout.
As an example, the following code blurs each view in a scroll view by some blur amount that's calculated by how far away the view is away from the center of its scroll view. That means views near the vertical center have little or no blur, whereas views on the outside are heavily blurred:
struct ContentView: View {
var body: some View {
ScrollView {
ForEach(0..<100) { i in
Text("Row \(i)")
.font(.largeTitle)
.frame(maxWidth: .infinity)
.visualEffect { content, proxy in
content.blur(radius: blurAmount(for: proxy))
}
}
}
}
func blurAmount(for proxy: GeometryProxy) -> Double {
let scrollViewHeight = proxy.bounds(of: .scrollView)?.height ?? 100
let ourCenter = proxy.frame(in: .scrollView).midY
let distanceFromCenter = abs(scrollViewHeight / 2 - ourCenter)
return Double(distanceFromCenter) / 100
}
}
Tips
Calling proxy.frame(in: .scrollView)
finds the size of this view in the innermost scroll view that contains it.
These visual effects work with any kind of position, including that generated through animation. For example, this makes a series of circles in a grid spin around, with each one dynamically recoloring based on a hue rotation:
struct ContentView: View {
@State private var rotationAmount = 0.0
var body: some View {
Grid {
ForEach(0..<3) { _ in
GridRow {
ForEach(0..<3) { _ in
Circle()
.fill(.green)
.frame(width: 100, height: 100)
.visualEffect { content, proxy in
content.hueRotation(.degrees(proxy.frame(in: .global).midY / 2))
}
}
}
}
}
.rotationEffect(.degrees(rotationAmount))
.onAppear {
withAnimation(.linear(duration: 5).repeatForever(autoreverses: false)) {
rotationAmount = 360
}
}
}
}
The modifier's name, visualEffect()
, should make it clear that any adjustments you make are limited how the finished view looks – if you find yourself wanting to use it to adjust view content, you're looking in the wrong place.