SwiftUI -修复使用可搜索但没有列表时的动画跳转

vzgqcmou  于 2023-03-07  发布在  Swift
关注(0)|答案(2)|浏览(123)

希望有人可能知道这个动画问题的解决方案,因为我找不到一种方法来使它工作!
我在滚动视图中使用LazyVStack中的ForEach。我在滚动视图上有一个. searchable修饰符。当我进入/取消搜索字段时,导航栏和搜索字段向上/向下动画,但我的滚动视图跳转时没有动画。
如果我在. searchable之后添加. animation(. easeInOut),它可以正确地生成动画。但是有两个问题,它在iOS 15.0中已被弃用,并且它会以疯狂的方式生成列表项的动画,因为它们会出现并被过滤等等。
当使用一个列表,它也工作,但不能自定义的方式,我需要的。这个问题是目前在模拟器,预览和设备上。
有没有人知道我如何才能让这个正确的动画,而不诉诸使用列表(这不具有定制我需要的列表项目)?
谢谢你的帮忙!
我为重现这个问题所做的精简版:

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

一个更基本的例子显示了同样的问题:

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

6kkfgxo01#

我们需要使ScrollView几何图形的变化与可搜索文本字段的出现/消失同步,如图所示,这是可以动画化的。
这里有两个任务:1)检测可搜索的状态改变2)在正确的位置动画化ScrollView(以避免如所讨论的已经提到的意外内容动画)
任务1)的一个可能的解决方案是读取isSearching环境变量:

.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
            }
    }
}

对于任务2)是通过修改事务:

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

使用Xcode 13.4 / iOS 15.5进行测试(调试慢速动画以获得更好的可见性)

Test code on GitHub

ffvjumwh

ffvjumwh2#

不幸的是这个问题也存在于UIKit中。我已经设法在这里找到了解决方案:Top Safe Area Constraint Animation
accepted answer可以通过将UIViewControllerRepresentable的示例设置为后台来应用,但遗憾的是,它在iOS 16上不起作用。
other answer,关于使导航栏不透明,也适用于iOS 16。它可以应用于:

  • 通过外观代理全局
let appearance = UINavigationBarAppearance()
 appearance.configureWithOpaqueBackground()
 UINavigationBar.appearance().isTranslucent = false
 UINavigationBar.appearance().standardAppearance = appearance
 UINavigationBar.appearance().scrollEdgeAppearance = appearance
  • 或者本地地经由可表示为背景的UIViewControllerTM。
class NavigationBarAppearanceViewController: UIViewController {
   override func viewDidLoad() {
     super.viewDidLoad()

     let appearance = UINavigationBarAppearance()
     appearance.configureWithOpaqueBackground()
     navigationController?.navigationBar.isTranslucent = false
     navigationController?.navigationBar.standardAppearance == appearance
     navigationController?.navigationBar.scrollEdgeAppearance = appearance
   }
 }

 struct NavigationBarAppearanceViewControllerRepresentable: UIViewControllerRepresentable {
   func makeUIViewController(context: Context) -> UIViewController { NavigationBarAppearanceViewController() }
   func updateUIViewController(_ viewController: UIViewController, context: Context) {}
 }

 extension View {
   func opaqueNavigationBar() -> some View {
     background(NavigationBarAppearanceViewControllerRepresentable())
   }
 }

外观可以根据设计要求进行调整。这种解决方案唯一的缺点是失去了漂亮的半透明导航栏,在后面滚动时可以看到内容,但仍然可以根据屏幕进行调整。

相关问题