微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

SwiftUI 在动画期间改变列表会导致显示状态不正确

如何解决SwiftUI 在动画期间改变列表会导致显示状态不正确

我正在尝试在使用某些控件和撤消 + 重做按钮时向列表视图添加动画。我已经为按钮添加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))
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。