Hoping someone may know of a solution for this animation issue as I can’t find a way to make it work!
Im using ForEach within LazyVStack within ScrollView. I have a .searchable modifier on the scrollview. When I enter/cancel the search field the navigation bar and search field animate upwards/downwards but my scrollview jumps without animation.
if I add .animation(.easeInOut) after .searchable it animates correctly. However there’s two issues, its deprecated in iOS 15.0, and it animates the list items in crazy ways as they appear and are filtered etc.
When using a List it also works but can’t be customised in the way I need. This issue is present in simulator, in previews and on device.
Does anyone know how I can get this to animate correctly without resorting to using List (Which doesn’t have the customisability I need for the list items)?
Thanks for your help!
A slimmed down version of what I’m doing to recreate the issue:
import SwiftUI
struct ContentView: View {
@State var searchText: String = ""
var body: some View {
NavigationView {
ScrollView(.vertical) {
CustomListView()
}
.navigationTitle("Misbehaving ScrollView")
.searchable(text: $searchText, placement: .automatic)
// This .animation() will fix the issue but create many more...
// .animation(.easeInOut)
}
}
}
struct CustomListView: View {
@State private var listItems = ["Item 0", "Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6", "Item 7", "Item 8", "Item 9", "Item 10"]
var body: some View {
LazyVStack(alignment: .leading, spacing: 10) {
ForEach(listItems, id: .self) { item in
CustomListItemView(item: item)
.padding(.horizontal)
}
}
}
}
struct CustomListItemView: View {
@State var item: String
var body: some View {
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 20, style: .continuous)
.foregroundColor(.green.opacity(0.1))
VStack(alignment: .leading, spacing: 4) {
Text(item)
.font(.headline)
Text(item)
.font(.subheadline)
}
.padding(25)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
An even more basic example that displays the same issue:
import SwiftUI
struct SwiftUIView: View {
@State var text = ""
var body: some View {
NavigationView {
ScrollView {
Text("1")
Text("2")
Text("3")
Text("4")
Text("5")
Text("6")
}
}
.searchable(text: $text)
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView()
}
}
2
Answers
We need to animate
ScrollView
geometry changes synchronously with searchable text field appearance/disappearance, which as seen are animatable.There are two tasks here: 1) detect searchable state changes 2) animate
ScrollView
in correct place (to avoid unexpected content animations as already mentioned in question)A possible solution for task 1) is to read
isSearching
environment variable:and for task 2) is to inject animation right during transition by modifying transaction:
Tested with Xcode 13.4 / iOS 15.5 (debug slow animation for better visibility)
Test code on GitHub
Unfortunately this issue is present in UIKit as well. I’ve managed to find a solution here: Top Safe Area Constraint Animation
The accepted answer can be applied by setting an instance of UIViewControllerRepresentable as background, but unfortunately it does not work on iOS 16.
The other answer, about making the navigation bar opaque, works on iOS 16 as well. It can be applied either:
globally via the appearance proxy
or locally via UIViewControllerRepresentable as background.
The appearance can be adjusted per design requirements.
The only downside of this solution is loosing the beautiful translucent navigation bar where the content can be seen when scrolling behind, but still can be adjusted per screen basis.