来自@Published 属性 的 SwiftUI 动画从视图外部改变
SwiftUI Animation from @Published property changing from outside the View
SwiftUI 提供 .animation()
绑定,可以使视图中的更改具有动画效果。但是,如果 @ObserveredObject
中的 @Published
属性 更改为 'autonomously'(例如,来自计时器),而视图将更新以响应更改,没有明显的方法获取视图以动画更改。
在下面的示例中,当 isOn
从 Toggle 更改时,它会设置动画,但当从 Timer
更改时,它不会。有趣的是,如果我在这里使用三元条件而不是 if
/else
,即使切换也不会触发动画。
struct ContentView: View {
@ObservedObject var model: Model
var body: some View {
VStack {
if model.isOn {
MyImage(color: .blue)
} else {
MyImage(color: .clear)
}
Spacer()
Toggle("switch", isOn: $model.isOn.animation(.easeIn(duration: 0.5)))
Spacer()
}
}
}
struct MyImage: View {
var color: Color
var body: some View {
Image(systemName: "pencil.circle.fill")
.resizable()
.frame(width: 100, height: 100)
.foregroundColor(color)
}
}
class Model: ObservableObject {
@Published var isOn: Bool = false
var timer = Timer()
init() {
timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true, block: { [unowned self] _ in
isOn.toggle()
})
}
}
当值更改不是来自绑定时如何触发动画?
最简单的选择是在定时器闭包中添加一个 withAnimation
块:
withAnimation(.easeIn(duration: 0.5)) {
isOn.toggle()
}
如果您无法更改@ObservableObject 闭包,您可以添加一个局部变量来反映更改:
struct ContentView: View {
@ObservedObject var model: Model
@State var localIsOn = false
var body: some View {
VStack {
if localIsOn {
MyImage(color: .blue)
} else {
MyImage(color: .clear)
}
Spacer()
Toggle("switch", isOn: $model.isOn.animation(.easeIn(duration: 0.5)))
Spacer()
}.onChange(of: model.isOn) { (on) in
withAnimation {
localIsOn = on
}
}
}
}
您也可以在 ObservableObject 中使用镜像变量来做类似的技巧:
struct ContentView: View {
@ObservedObject var model: Model
var body: some View {
VStack {
if model.animatedOn {
MyImage(color: .blue)
} else {
MyImage(color: .clear)
}
Spacer()
Toggle("switch", isOn: $model.isOn.animation(.easeIn(duration: 0.5)))
Spacer()
}
}
}
class Model: ObservableObject {
@Published var isOn: Bool = false
@Published var animatedOn : Bool = false
var cancellable : AnyCancellable?
var timer = Timer()
init() {
timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true, block: { [unowned self] _ in
isOn.toggle()
})
cancellable = $isOn.sink(receiveValue: { (on) in
withAnimation {
self.animatedOn = on
}
})
}
}
SwiftUI 提供 .animation()
绑定,可以使视图中的更改具有动画效果。但是,如果 @ObserveredObject
中的 @Published
属性 更改为 'autonomously'(例如,来自计时器),而视图将更新以响应更改,没有明显的方法获取视图以动画更改。
在下面的示例中,当 isOn
从 Toggle 更改时,它会设置动画,但当从 Timer
更改时,它不会。有趣的是,如果我在这里使用三元条件而不是 if
/else
,即使切换也不会触发动画。
struct ContentView: View {
@ObservedObject var model: Model
var body: some View {
VStack {
if model.isOn {
MyImage(color: .blue)
} else {
MyImage(color: .clear)
}
Spacer()
Toggle("switch", isOn: $model.isOn.animation(.easeIn(duration: 0.5)))
Spacer()
}
}
}
struct MyImage: View {
var color: Color
var body: some View {
Image(systemName: "pencil.circle.fill")
.resizable()
.frame(width: 100, height: 100)
.foregroundColor(color)
}
}
class Model: ObservableObject {
@Published var isOn: Bool = false
var timer = Timer()
init() {
timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true, block: { [unowned self] _ in
isOn.toggle()
})
}
}
当值更改不是来自绑定时如何触发动画?
最简单的选择是在定时器闭包中添加一个 withAnimation
块:
withAnimation(.easeIn(duration: 0.5)) {
isOn.toggle()
}
如果您无法更改@ObservableObject 闭包,您可以添加一个局部变量来反映更改:
struct ContentView: View {
@ObservedObject var model: Model
@State var localIsOn = false
var body: some View {
VStack {
if localIsOn {
MyImage(color: .blue)
} else {
MyImage(color: .clear)
}
Spacer()
Toggle("switch", isOn: $model.isOn.animation(.easeIn(duration: 0.5)))
Spacer()
}.onChange(of: model.isOn) { (on) in
withAnimation {
localIsOn = on
}
}
}
}
您也可以在 ObservableObject 中使用镜像变量来做类似的技巧:
struct ContentView: View {
@ObservedObject var model: Model
var body: some View {
VStack {
if model.animatedOn {
MyImage(color: .blue)
} else {
MyImage(color: .clear)
}
Spacer()
Toggle("switch", isOn: $model.isOn.animation(.easeIn(duration: 0.5)))
Spacer()
}
}
}
class Model: ObservableObject {
@Published var isOn: Bool = false
@Published var animatedOn : Bool = false
var cancellable : AnyCancellable?
var timer = Timer()
init() {
timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true, block: { [unowned self] _ in
isOn.toggle()
})
cancellable = $isOn.sink(receiveValue: { (on) in
withAnimation {
self.animatedOn = on
}
})
}
}