SwiftUI:解决无List时使用searchable的动画跳变问题
问题根源
ScrollView本身没有像List那样内置适配搜索栏显示/隐藏时的布局动画逻辑,导致导航栏和搜索栏执行上下动画时,ScrollView内容直接跳变。全局添加废弃的.animation会污染所有视图的动画,引发列表项加载、筛选时的异常。
解决方案
方案1:iOS16+ 最优解(切换到NavigationStack)
NavigationView在iOS16已被废弃,NavigationStack对.searchable的布局动画做了原生适配,替换后ScrollView会自动跟随动画,无需额外处理:
import SwiftUI struct ContentView: View { @State var searchText: String = "" var body: some View { NavigationStack { ScrollView(.vertical) { CustomListView() } .navigationTitle("Fixed ScrollView") .searchable(text: $searchText, placement: .automatic) } } } // 其余CustomListView、CustomListItemView代码保持不变
方案2:iOS15+ 兼容方案
通过监听搜索状态,仅给ScrollView的布局变化添加动画,避免全局动画污染列表项:
import SwiftUI struct ContentView: View { @State var searchText: String = "" @Environment(\.isSearching) private var isSearching var body: some View { NavigationView { ScrollView(.vertical) { CustomListView() } // 仅针对搜索状态变化绑定动画,不影响列表项的正常加载/筛选 .animation(.easeInOut, value: isSearching) .navigationTitle("Fixed ScrollView") .searchable(text: $searchText, placement: .automatic) } } } // 其余代码保持不变
补充调整(若存在布局偏移)
如果方案2中仍有微小布局偏移,可通过GeometryReader获取导航栏高度,动态调整ScrollView的内容内边距:
struct ContentView: View { @State var searchText: String = "" @Environment(\.isSearching) private var isSearching @State private var navigationBarHeight: CGFloat = 0 var body: some View { NavigationView { ScrollView(.vertical) { CustomListView() } .contentInsets(.init(top: isSearching ? 0 : navigationBarHeight, leading: 0, bottom: 0, trailing: 0)) .animation(.easeInOut, value: isSearching) .navigationTitle("Fixed ScrollView") .searchable(text: $searchText, placement: .automatic) // 监听导航栏高度 .background( GeometryReader { proxy in Color.clear .onAppear { navigationBarHeight = proxy.safeAreaInsets.top } } ) } } }
内容的提问来源于stack exchange,提问作者Matthew Folbigg
相关产品推荐
相关产品推荐

