结合使用 EnvironmentObject 与 withAnimation

了解如何使用 @State 和 EnvironmentObject 相结合的方式,在复杂的视图结构中轻松管理动画。

结合使用 EnvironmentObject 与 withAnimation

使用 EnvironmentObject(环境变量),可以更加简洁、高效的方式来管理全局状态,使得在复杂的视图层次结构中共享和更新状态变得更加容易。我们在之前的文章中介绍过环境变量的使用方法:

使用 EnvironmentObject 和 AppState 管理全局状态
在应用程序开发中,组件之间的状态传递是一个常见的需求。管理状态的方式会影响代码的可维护性、可扩展性以及可读性。本文将介绍两种常见的状态传递方式:变量传递和全局状态管理。 使用变量传递状态 变量传递是最直观的状态传递方式。在这种方式中,我们在声明组件时创建变量,并在需要的地方通过显式方式传递这些变量。 举个例子,假设我们有一个父组件 ParentView 和一个子组件 ChildView,我们可以通过变量传递在这两个组件之间共享数据。 struct ParentView: View { @State private var username: String = “John Doe” var body: some View { VStack { Text(“Parent View”) ChildView(username: $username) Text(“Username: \(username)”) } } } struct ChildView:

使用 .transition 和 .contentTransition 可以为应用添加平滑的过渡动画,提升用户体验。但这两者都需要与 withAnimation 函数配合使用才能正确触发动画效果。

由于我们可能在多个组件中更改环境变量,在每个地方都手动使用 withAnimation 进行包装会显得繁琐且容易出错。那么,如何让 withAnimation 与环境变量更方便地结合使用呢?

接下来,我将介绍两种我常用的解决方案,以简化这种操作。

使用 @State 变量进行过渡

在视图组件中,我们可以创建一个私有的 @State 变量,并在它每次更新时,将其同步到环境变量中,并使用 withAnimation 包装这个过程,从而实现动画过渡。

假设我们有如下的环境变量:

import SwiftUI

class AppState: ObservableObject {
    @Published var selectedDate: Date

    init() {
        // 初始化时设置为当天的日期
        self.selectedDate = Date()
    }
}

在需要使用 AppState.selectedDate 的视图组件中,我们创建一个私有的 @State 变量,并遵循以下原则:

  • 更新值:需要更新 AppState.selectedDate 的地方,使用 selectedDate 状态变量来代替。
  • 获取值:需要获取 AppState.selectedDate 的地方,继续使用环境对象中的 AppState.selectedDate
  • 监听变化:使用 onChange 来监听 selectedDate 的变化,并在每次变化时,使用 withAnimation 将新值更新到 appState.selectedDate。最后使用 onChange 来监听 selecteDate 的变化,并在每次变化时,使用 withAnimation 更新到 appState.selectedDate 中。

使用方法就像这样:

struct HomeView: View {
  @EnvironmentObject var appState: AppState  // 使用环境对象
  @State private var selectedDate = Date  
  
  {
    <省略部分代码>
    DatePicker(
        "选择日期",
        selection: $selectedDate,
        displayedComponents: [.date]
    )
  }
 .onChange(of: selectedDate) {
     withAnimaton {
        appState.selectedDate = selectedDate
      }
  }
}

在上面的代码中,我们使用 @State 变量 selectedDate 来管理本地的日期选择。onChange 监听到状态变化后,通过 withAnimation 将新的日期同步到环境对象 appState.selectedDate,从而触发动画效果。

在我的实践中,它是有效的,并能够正常触发过渡动画。

在 ObservableObject 中使用 setter 更新变量

如果你希望在 AppState 中集中管理动画,并避免在视图中创建额外的变量,可以将 selectedDate 修改为一个计算属性,并通过自定义的 setter 添加 withAnimation

你可以像下面这样修改 AppState

class AppState: ObservableObject {
    @Published private var _selectedDate: Date

    var selectedDate: Date {
        get { _selectedDate }
        set {
            withAnimation {
                _selectedDate = newValue
            }
        }
    }

    init() {
        self._selectedDate = Date()
    }
}

在这个例子中,selectedDate 是一个计算属性,setter 中使用了 withAnimation。这样,无论你在何处更新 AppState.selectedDate,都会自动触发动画。