简体   繁体   中英

SwiftUI - Fix animation jump when using searchable without a list

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()
    }
}

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:

.background(
    // read current searching state is available only in
    // child view level environment
    SearchingReaderView(searching: $isSearching)
)

// ...

struct SearchingReaderView: View {
    @Binding var searching: Bool
    @Environment(\.isSearching) private var isSearching

    var body: some View {
        Text(" ")
            .onChange(of: isSearching) {
                searching = $0          // << report to perent
            }
    }
}

and for task 2) is to inject animation right during transition by modifying transaction:

ScrollView(.vertical) {
    CustomListView()
}
.transaction {
    if isSearching || toggledSearch {
        // increased speed to avoid views overlaping
        $0.animation = .default.speed(1.2)

        // needed to animate end of searching
        toggledSearch.toggle()
    }
}

Tested with Xcode 13.4 / iOS 15.5 (debug slow animation for better visibility)

演示

Test code on GitHub

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM