大家平时在用 App 的时候肯定都见过这种效果,在详情页顶部放一张大图,往下一拉还能跟着变大,视觉冲击力直接拉满。说实话,以前我一直以为这种效果很难搞,得写一堆乱七八糟的代码,结果最近发现 SwiftUI 里居然有个骚操作,三行代码就能实现。
网上一搜"SwiftUI 拉伸头部",十有八九都是用 onScrollGeometryChange() 监听滚动偏移,然后把 offset 写到某个 state 里,再用来算图片 frame,能用,但是真的麻烦:要多维护一个 state,代码臃肿,滚动监听和 UI 绑定一大堆,复用性差,换个页面还得重写一遍。
新姿势:visualEffect()
SwiftUI 现在有个新修饰符叫 visualEffect(),用它可以直接拿到视图的 Geometry 信息,然后在闭包里随便玩。核心思路就是:拿到当前图片的高度和滚动偏移量,算出缩放比例,然后直接 scale 就完事了。
直接上代码:
extension View {
func stretchy() -> some View {
visualEffect { effect, geometry in
let currentHeight = geometry.size.height
let scrollOffset = geometry.frame(in: .scrollView).minY
let positiveOffset = max(0, scrollOffset)
let newHeight = currentHeight + positiveOffset
let scaleFactor = newHeight / currentHeight
return effect.scaleEffect(
x: scaleFactor, y: scaleFactor,
anchor: .bottom
)
}
}
}
代码逻辑:
1,拿到当前高度
2,算出滚动偏移(只取正值,防止图片缩小)
3,算缩放比例
4,用 .bottom 锚点 scale,让图片只往上拉伸,下面的内容不受影响
有了上面的 stretchy() 修饰符,实际用起来就一句话:
struct FlowerView: View {
let flower: Flower
var body: some View {
ScrollView {
VStack {
Image(flower.name)
.resizable()
.scaledToFill()
.stretchy() // 就这一行,灵魂!
FlowerInfo(flower: flower)
}
}
.ignoresSafeArea(edges: .top)
}
}
就是这么简单,以前要写一堆 state 和监听,现在一个修饰符全搞定。
拉一下,图片就跟着变大,视觉上很顺滑,完全没有割裂感。用在商品详情、用户头像、文章封面这些场景,效果都很炸裂。
这种写法的优点:
代码极简,维护起来不头疼
没有多余的状态管理,性能也更好
封装成修饰符,想用哪用哪,复用性拉满
逻辑全在一个闭包里,阅读体验也舒服
