SwiftUI 多个垂直列表行动画不起作用
SwiftUI multiple vertical lists row animations not working
突然发现没有自动动画:
没有用于插入、删除、重新排序等的动画。☹️ Id
s 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
}
}
}
突然发现没有自动动画:
没有用于插入、删除、重新排序等的动画。☹️ Id
s 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
}
}
}