我的观点由存储在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()
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果我删除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
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
And*_*kyi 23
您可以.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
}
}
Run Code Online (Sandbox Code Playgroud)
结果将是下一个:
你可以看到我们在两个动作上都有动画。这可能是首选行为,但在某些情况下,您可能希望单独控制每个操作。
假设我们希望在第一次 API 调用 ( fetchArticle)后为视图设置动画,但在第二次 ( fetchContent) 上 - 只需重绘没有动画的视图。换句话说 - 在title接收时动画视图但在接收时不动画视图content。
为了实现这一点,我们需要:
@State var title = ""在视图中创建一个单独的属性。viewModel.title..onReceive(viewModel.$title) { newTitle in ... }。当发布者 ( viewModel.$title) 发送新值时,将执行此闭包。在这一步中,我们可以控制视图中的属性。在我们的例子中,我们将更新titleView的属性。withAnimation {...}在闭包内部使用以动画化更改。所以当title更新时我们会有动画。在接收contentViewModel的新值时,我们的 View 只是在没有动画的情况下更新。
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
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
结果将是下一个:
看起来好像withAnimation在异步闭包内部使用会导致颜色不产生动画,而是立即改变。
删除包装asyncAfter,或删除调用并在您的中withAnimation添加修饰符(如下所示)应该可以解决该问题:animationbodyContentView
Color(viewModel.color).onAppear {
self.viewModel.change()
}.animation(.easeInOut(duration: 1))
Run Code Online (Sandbox Code Playgroud)
在本地测试(iOS 13.3,Xcode 11.3),这似乎也按照您的意愿从蓝色溶解/淡化为红色。
| 归档时间: |
|
| 查看次数: |
2010 次 |
| 最近记录: |