当要变异的值是引用类型时,如何对视图进行动画更改?
在下面的示例中,更改提醒时间会更新顺序,但没有动画。
我认为数组是值类型,但也许 event.reminders 是一个引用,因此不会触发更新?如果是这样,我猜 .animation(_:value:) 不是正确的方法?
有哪些替代方案?
import SwiftUI
struct MissingAnimationWhenChangingArrayOrder: View {
let event: Event
var body: some View {
ForEach(event.reminders.sorted { $0.dateTime < $1.dateTime }) { reminder in
ReminderView(reminder: reminder)
}
.animation(.spring, value: event.reminders)
}
}
#Preview {
var event = Event(
reminders: [
Reminder(title: "Meeting 1", dateTime: .now),
Reminder(title: "Meeting 2", dateTime: .now)
]
)
return MissingAnimationWhenChangingArrayOrder(event: event)
}
struct ReminderView: View {
@Bindable var reminder: Reminder
var body: some View {
DatePicker("", selection: $reminder.dateTime)
.frame(width: 200)
}
}
@Observable
class Event {
var reminders: [Reminder]
init(reminders: [Reminder]) {
self.reminders = reminders
}
}
@Observable
class Reminder: Identifiable, Equatable {
let id = UUID()
var title: String
var dateTime: Date
init(title: String, dateTime: Date) {
self.title = title
self.dateTime = dateTime
}
static func == (lhs: Reminder, rhs: Reminder) -> Bool {
lhs.id == rhs.id
}
}
这确实是因为引用类型语义。
.animation
修改器的工作方式是在视图更新后检查其value:
参数是否已更改(由==
确定)。 SwiftUI 在视图更新之前存储 value:
的“旧版本”,然后通过调用 body
运行视图更新,然后将 value:
调用期间收到的新 body
与它的“旧值”进行比较有。如果它们不相等,则启动动画。
这对于值类型来说效果很好,但对于引用类型来说,
value:
的“旧版本”只是同一对象的reference的另一个副本!当您更改属性时,SwiftUI 存储的旧版本也会“更改”为相同的值,因为它是同一个实例。所以 SwiftUI 总是发现它们是相等的,除非你以非常错误的方式实现了 ==
:)
请注意,即使
Reminder
是值类型,您的 ==
实现也会阻止动画,因为它只比较 ID。更改提醒的日期不会导致提醒不相等。
为了解决这个问题,我将
map
将提醒数组设置为日期数组:
.animation(.spring, value: event.reminders.map(\.dateTime))