【问题标题】:SwiftUI multiple vertical lists row animations not workingSwiftUI 多个垂直列表行动画不起作用
【发布时间】:2021-09-24 11:19:10
【问题描述】:

突然间我发现他们没有自动动画:

没有用于插入、删除、重新排序等的动画。☹️ Ids 在转换之间是唯一和持久

问题1

我怎样才能让它们按预期工作?

问题 2

有没有一种简单的方法来制作动画来查看列表之间的过渡?就像在单个列表的各个部分之间移动一行一样。


解释:

假设我有三个列表,它们将单个数组的元素的不同状态分组:

extension Call {
    enum State: Equatable {
        case inProgress
        case accepted
        case rejected
    }
}

可观察的:

class CallManager: ObservableObject {
    @Published var calls: [Call]
    init(visits: [Call] = []) { self.calls = visits }
}

而且 Call 是一个简单的 Identifiable:

struct Call: Identifiable & Equatable & Hashable {
    let id = UUID()
    var state: State
}

通过进行这些绑定,我已将所有列表绑定到核心 calls 数组:

extension CallManager {
    func bindingCalls(for state: Call.State) -> Binding<[Call]> {
        Binding<[Call]>(
            get: { self.calls.filter { $0.state == state } },
            set: { // TODO: Find a better way for this
                self.calls.removeAll(where: { $0.state == state })
                self.calls.append(contentsOf: $0)
            }
        )
    }

    var inProgress: Binding<[Call]> { bindingCalls(for: .inProgress) }
    var accepted: Binding<[Call]> { bindingCalls(for: .accepted) }
    var rejected: Binding<[Call]> { bindingCalls(for: .rejected) }
}

这是查看代码:

struct ContentView: View {

    @StateObject var visitManager =  CallManager(visits: [
        Call(state: .inProgress),
        Call(state: .accepted),
        Call(state: .accepted),
        Call(state: .inProgress),
        Call(state: .inProgress),
        Call(state: .rejected)
    ])

    var body: some View {
        HStack {
            List(visitManager.inProgress) { $call in
                CallView(call: $call)
            }

            List(visitManager.accepted) { $call in
                CallView(call: $call)
            }

            List(visitManager.rejected) { $call in
                CallView(call: $call)
            }
        }
    }
}

struct CallView: View & Identifiable {
    @Binding var call: Call
    var id: UUID { call.id }

    var body: some View {
        Text(call.id.uuidString.prefix(15))
            .foregroundColor(call.state.color)
            .onTapGesture(count: 2) { call.state = .rejected }
            .onTapGesture { call.state = .accepted }
    }
}

extension Call.State {
    var color: Color {
        switch self {
        case .inProgress: return .blue
        case .rejected: return .red
        case .accepted: return .green
        }
    }
}

【问题讨论】:

    标签: ios swiftui binding ios15


    【解决方案1】:

    您可以在列表视图中启用动画:

    List(visitManager.inProgress) { $call in
        CallView(call: $call)
    }
    .animation(.default)
    

    或者将更改包装在 withAnimation 块中:

    .onTapGesture { withAnimation { call.state = .accepted } }
    

    至于列之间的动画,您可以使用.matchedGeometryEffect 获得类似的效果。 afaik 它在 List 之间总是看起来有点易碎,为了让它看起来不错,你需要使用 VStack (但随后会失去 List 视图的所有舒适性)。例如:

    import SwiftUI
    
    extension Call {
        enum State: Equatable, CaseIterable {
            case inProgress
            case accepted
            case rejected
        }
    }
    
    class CallManager: ObservableObject {
        @Published var calls: [Call]
        init(visits: [Call] = []) { calls = visits }
    }
    
    struct Call: Identifiable & Equatable & Hashable {
        let id = UUID()
        var state: State
    }
    
    struct ContentView: View {
        @Namespace var items
    
        @StateObject var visitManager = CallManager(visits: [
            Call(state: .inProgress),
            Call(state: .accepted),
            Call(state: .accepted),
            Call(state: .inProgress),
            Call(state: .inProgress),
            Call(state: .rejected),
        ])
    
        var body: some View {
            HStack(alignment: .top) {
                ForEach(Call.State.allCases, id: \.self) { state in
                    VStack {
                        List(visitManager.calls.filter { $0.state == state }) { call in
                            CallView(call: call)
                                .id(call.id)
                                .matchedGeometryEffect(id: call.id, in: items)
                                .onTapGesture {
                                    if let idx = visitManager.calls.firstIndex(where: { $0.id == call.id }) {
                                        withAnimation {
                                            visitManager.calls[idx].state = .rejected
                                        }
                                    }
                                }
                        }
                    }
                }
            }
        }
    }
    
    struct CallView: View & Identifiable {
        var call: Call
        var id: UUID { call.id }
    
        var body: some View {
            Text(call.id.uuidString.prefix(15))
                .foregroundColor(call.state.color)
        }
    }
    
    extension Call.State {
        var color: Color {
            switch self {
            case .inProgress: return .blue
            case .rejected: return .red
            case .accepted: return .green
            }
        }
    }
    

    【讨论】:

    • 感谢 Ralf +1 回答 问题 1。我正在寻找一个方便的动画来在列表之间移动行。我怎样才能做到这一点?
    • 你可以使用matchedGeometryEffect得到类似的东西,但它不会在列表之间完美。请参阅答案以获取示例。
    • 谢谢,拉尔夫,如果可以的话,我会再次支持你。我不知道@Namespace 和matchedGeometryEffect。今晚我学到了很多东西。
    猜你喜欢
    • 1970-01-01
    • 2020-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-21
    • 2017-02-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多