问题描述
我正在尝试在使用某些控件和撤消 + 重做按钮时向列表视图添加动画。我已经为按钮添加了 withAnimation
闭包,但是当我双击一个按钮时,列表视图与后备状态不同步并且不会刷新。我发现我经常一次撤消多个步骤,因此这是我希望用户经常体验的模式。
我对为什么会发生这种情况的理解是,在动画状态更改期间不会触发新的渲染。
我已经创建了该问题的 MVP:
import SwiftUI
struct MyListView: View {
@State var data: [UUID] = []
var body: some View {
vstack{
HStack{
Button("Add",action: {
withAnimation(Animation.linear(duration: 2)){ // Offending animation
data.append(UUID())
}
})
Spacer()
Button("Clear",action: {
withAnimation{
data.removeAll()
}
})
Spacer()
Text("Count: \(data.count)")
}
List{
ForEach(data,id: \.self) { v in
Text("\(v)")
}
.onMove(perform: { indices,newOffset in
data.move(fromOffsets: indices,toOffset: newOffset)
})
.onDelete(perform: { indexSet in
data.remove(atOffsets: indexSet)
})
}
}
.padding()
}
}
struct MyListView_Previews: PreviewProvider {
static var previews: some View {
MyListView()
.environment(\.editMode,.constant(.active))
}
}
如果您在模拟器中运行此代码,您可以通过快速按下“添加”按钮来重现该问题。
我一直无法在 Google 上找到任何可以真正为解决此问题的正确方法提供见解的信息。我已经探索过 Transactions 是否可以解决这个问题,但这似乎不是解决方案,而且我还没有找到任何关于如何在不重新渲染视图的情况下中断动画以开始一个新动画的信息。
我开始认为答案是不要动画撤消/重做和追加,或者依赖其他形式的用户反馈。
任何帮助将不胜感激,谢谢。
解决方法
我也找不到确切的潜在问题,更改 @State 应该会更新您的视图。但我确实也认为 withAnimation 破坏了某些东西。
要解决这个问题,您可以在单独的视图中创建自己的动画,如下所示:
struct MyListItem: View {
let id: UUID
@State var animate = false
var body: some View {
Text("\(id)").scaleEffect(y: animate ? 1 : 0).animation(.default).onAppear {
animate = true
}
}
}
struct MyListView: View {
@State var data: [UUID] = []
var body: some View {
...
Button("Add",action: {
data.append(UUID())
})
...
List{
ForEach(data,id: \.self) { v in
MyListItem(id: v)
}
...
}
}
,
这很烦人哈哈。我不知道为什么第二个动画没有触发,但这里有一个可能有帮助的解决方法。
我将数据放入一个类中,然后添加了一个名为“dataPublisher”的自定义发布者。 dataPublisher 是 $binding 到数据数组,所以它会在每次数据数组更新时发布(强制渲染)。然而,神奇之处在于 .debounce,这将使它在数据数组更新后等待 0.5 秒(您可以根据需要更改时间),然后再实际发布新更新。这 0.5 秒的延迟似乎有助于使列表与数据保持同步。
这样,操作来自发布者而不是按钮,因此添加按钮中的 withAnimation 将不起作用。所以,我只将动画直接移动到列表。您当然可以将其移回,但它只会在双击时为第一个按钮单击设置动画。
import SwiftUI
import Combine
class DataViewModel: ObservableObject {
@Published var data: [UUID] = []
@Published var dataPublisher: Bool = false
var cancellables = Set<AnyCancellable>()
init() {
setUpPublisher()
}
func setUpPublisher() {
$data
.debounce(for: 0.5,scheduler: RunLoop.main) // magic
.removeDuplicates()
.map { input in
return !self.dataPublisher
}
.eraseToAnyPublisher()
.subscribe(on: DispatchQueue.global(qos: .userInteractive))
.receive(on: DispatchQueue.main)
.assign(to: \.dataPublisher,on: self)
.store(in: &cancellables)
}
}
struct MyListView: View {
@ObservedObject var viewModel = DataViewModel()
var body: some View {
VStack{
HStack{
Button("Add",action: {
viewModel.data.append(UUID())
})
Spacer()
Button("Clear",action: {
withAnimation{
viewModel.data.removeAll()
}
})
Spacer()
Text("Count: \(viewModel.data.count)")
}
List {
ForEach(viewModel.data,id: \.self) { v in
Text("\(v)")
}
.onMove(perform: { indices,newOffset in
viewModel.data.move(fromOffsets: indices,toOffset: newOffset)
})
.onDelete(perform: { indexSet in
viewModel.data.remove(atOffsets: indexSet)
})
}
.animation(Animation.linear(duration: 2))
}
.padding()
}
}
struct MyListView_Previews: PreviewProvider {
static var previews: some View {
MyListView()
.environment(\.editMode,.constant(.active))
}
}