SwiftUI 多个垂直列表行动画不起作用

SwiftUI multiple vertical lists row animations not working

突然发现没有自动动画:

没有用于插入、删除、重新排序等的动画。☹️ Ids unique and persistent 转换之间

问题 1

如何让它们按预期工作?

问题二

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


解释:

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

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 是一个简单的可识别的:

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 { [=13=].state == state } },
            set: { // TODO: Find a better way for this
                self.calls.removeAll(where: { [=13=].state == state })
                self.calls.append(contentsOf: [=13=])
            }
        )
    }

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

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

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 { [=12=].state == state }) { call in
                        CallView(call: call)
                            .id(call.id)
                            .matchedGeometryEffect(id: call.id, in: items)
                            .onTapGesture {
                                if let idx = visitManager.calls.firstIndex(where: { [=12=].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
        }
    }
}