我的视图由存储在ViewModel
中的状态决定。有时,视图可能会在其ViewModel
上调用函数,从而导致异步状态更改。
如何在View
中为状态变化的效果设置动画?
这是一个人为的示例,其中对viewModel.change()
的调用将导致视图改变颜色。
class ViewModel: ObservableObject {
@Published var color: UIColor = .blue
func change() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.color = .red
}
}
}
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
var body: some View {
Color(viewModel.color).onAppear {
withAnimation(.easeInOut(duration: 1.0)) {
self.viewModel.change()
}
}
}
}
如果我删除ViewModel
并将状态存储在视图本身中,那么一切都会按预期进行。但是,这不是一个很好的解决方案,因为我想将状态封装在ViewModel
中。
struct ContentView: View {
@State var color: UIColor = .blue
var body: some View {
Color(color).onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
withAnimation(.easeInOut(duration: 1.0)) {
self.color = .red
}
}
}
}
}
答案 0 :(得分:11)
您可以在正文内容或任何子视图上使用.animation(...)
,但它将为视图的所有更改添加动画效果。
让我们考虑一个示例,当我们通过ViewModel进行两个API调用并在正文内容上使用.animation(.default)
时:
import SwiftUI
import Foundation
class ArticleViewModel: ObservableObject {
@Published var title = ""
@Published var content = ""
func fetchArticle() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
self?.title = "Article Title"
}
}
func fetchContent() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
self?.content = "Content"
}
}
}
struct ArticleView: View {
@ObservedObject var viewModel = ArticleViewModel()
var body: some View {
VStack(alignment: .leading) {
if viewModel.title.isEmpty {
Button("Load Article") {
self.viewModel.fetchArticle()
}
} else {
Text(viewModel.title).font(.title)
if viewModel.content.isEmpty {
Button("Load content...") {
self.viewModel.fetchContent()
}
.padding(.vertical, 5)
.frame(maxWidth: .infinity, alignment: .center)
} else {
Rectangle()
.foregroundColor(Color.blue.opacity(0.2))
.frame(height: 80)
.overlay(Text(viewModel.content))
}
}
}
.padding()
.frame(width: 300)
.background(Color.gray.opacity(0.2))
.animation(.default) // animate all changes of the view
}
}
结果将是下一个:
您可以看到我们在两个动作上都有动画。这可能是首选行为,但在某些情况下,您可能希望分别控制每个动作。
假设我们要在第一个API调用(fetchArticle
)之后对视图进行动画处理,但是在第二个API(fetchContent
)上进行动画处理-仅重绘不带动画的视图。换句话说-在收到title
时为视图添加动画,但在收到content
时不为视图添加动画。
要实现这一点,我们需要:
@State var title = ""
。viewModel.title
。.onReceive(viewModel.$title) { newTitle in ... }
。当发布者(viewModel.$title
)发送新值时,将执行此关闭。在此步骤中,我们可以控制“视图”中的属性。在我们的情况下,我们将更新视图的title
属性。withAnimation {...}
为更改设置动画。因此,title
更新时,我们将制作动画。在收到新的content
的ViewModel值时,我们的视图只会更新而不会产生动画。
struct ArticleView: View {
@ObservedObject var viewModel = ArticleViewModel()
// 1
@State var title = ""
var body: some View {
VStack(alignment: .leading) {
// 2
if title.isEmpty {
Button("Load Article") {
self.viewModel.fetchArticle()
}
} else {
// 2
Text(title).font(.title)
if viewModel.content.isEmpty {
Button("Load content...") {
self.viewModel.fetchContent()
}
.padding(.vertical, 5)
.frame(maxWidth: .infinity, alignment: .center)
} else {
Rectangle()
.foregroundColor(Color.blue.opacity(0.2))
.frame(height: 80)
.overlay(Text(viewModel.content))
}
}
}
.padding()
.frame(width: 300)
.background(Color.gray.opacity(0.2))
// 3
.onReceive(viewModel.$title) { newTitle in
// 4
withAnimation {
self.title = newTitle
}
}
}
}
结果将是下一个:
答案 1 :(得分:1)
在异步闭包中使用withAnimation
似乎使颜色没有动画,而是立即改变。
要么删除包装的asyncAfter
,要么删除withAnimation
调用并在animation
的{{1}}中添加body
修饰符(如下所示),解决问题:
ContentView
在本地进行了测试(iOS 13.3,Xcode 11.3),它也可以根据需要从蓝色变为红色。