ScrollView内嵌LazyVStack中可变高度视图动画异常问题
ScrollView内嵌LazyVStack时高度变化动画抖动的解决办法
问题背景
在Xcode 15.0、目标系统iOS 15的SwiftUI环境中,当ScrollView嵌套的LazyVStack包含高度动态变化的视图时,切换视图高度的动画会出现异常抖动。尤其当滚动到可变高度视图完全脱离可见区域后触发动画,抖动现象会更加明显。
复现代码
@State private var isOn = false var body: some View { VStack { Toggle(isOn.description, isOn: $isOn.animation()) ScrollView { LazyVStack { Rectangle() .frame(height: isOn ? 200 : 50) ForEach(0..<100) { index in AsyncImage(url: URL(string: "https://picsum.photos/200/300")!) } } } } }
现象差异
- 使用
LazyVStack时,高度切换动画伴随明显抖动; - 替换为
VStack后,动画运行流畅无卡顿。
问题原因
LazyVStack的核心是懒加载机制,仅渲染当前可见区域内的视图。当可变高度视图滚动出可见范围后,会被从视图树中移除。此时触发高度变化动画,LazyVStack需要重新计算布局并重新加载该视图,这个过程与动画的帧更新逻辑冲突,导致布局计算紊乱,最终出现抖动。而VStack会一次性渲染所有子视图,即使视图不在可见区域也会保留在视图树中,动画过程中布局计算更稳定。
解决方案
方案1:为可变高度视图添加固定标识
通过给可变高度视图设置id,强制LazyVStack在动画过程中保持该视图的布局上下文,避免因懒加载卸载视图导致的布局异常:
ScrollView { LazyVStack { Rectangle() .frame(height: isOn ? 200 : 50) .id(isOn) // 随状态变化更新id,确保视图布局上下文稳定 ForEach(0..<100) { index in AsyncImage(url: URL(string: "https://picsum.photos/200/300")!) } } }
方案2:用ScrollViewReader控制滚动位置
动画触发时,通过ScrollViewReader将滚动位置临时调整到可变高度视图附近,确保动画过程中视图处于可见状态,规避懒加载卸载逻辑:
@State private var isOn = false @Namespace private var animationNamespace var body: some View { VStack { Toggle(isOn.description, isOn: $isOn.animation()) ScrollViewReader { proxy in ScrollView { LazyVStack { Rectangle() .frame(height: isOn ? 200 : 50) .id("heightAdjustView") ForEach(0..<100) { index in AsyncImage(url: URL(string: "https://picsum.photos/200/300")!) } } } .onChange(of: isOn) { _ in withAnimation { proxy.scrollTo("heightAdjustView", anchor: .top) } } } } }
方案3:替换为VStack(简单直接)
如果列表项数量不大,直接用VStack替代LazyVStack是最省心的方案。虽然会失去懒加载的性能优势,但能直接保证动画流畅性。
内容的提问来源于stack exchange,提问作者Adjie Satryo
相关产品推荐
相关产品推荐

