【问题标题】:Why doesn't custom SwiftUI view animate on ViewModel state change?为什么自定义 SwiftUI 视图在 ViewModel 状态更改时没有动画?
【发布时间】:2020-08-12 00:07:58
【问题描述】:

当我的 viewModel 更改为错误状态时,我正在展示一个自定义视图。我想为这个演示文稿制作动画。我使用枚举表示 viewModel 状态

enum ViewModelState<T> {
    case idle
    case error(Error)
    case loading
    case data(T)

    var isError: Bool {
        switch self {
        case .error:
            return true
        default:
           return false
        }
    }
}

在我的 viewModel 中,我有这个属性...

@Published var state: ViewModelState = .idle

当它变为错误状态时,我想在我的错误视图中制作动画。当我在我的视图中使用 if 语句执行以下操作时,它会在...

private var content: some View {
    return ZStack {
        mainView // VStack
        if case let .error(error) = viewModel.state {
            ErrorView(
                errorDescription: error.localizedDescription,
                state: $viewModel.state
            )
        }
    }.animation(.default)
}

var body: some View {
    content
    .navigationBarTitle(viewModel.title)
    .navigationBarBackButtonHidden(true)
}

但是我想打开错误并执行此操作

private var content: some View {
    switch viewModel.state {
    case .idle:
        return mainView.eraseToAnyView()
    case let .error(error):
        return ZStack {
            mainView
            ErrorView(
               errorDescription: error.localizedDescription,
               state: $viewModel.state
            )
        }.animation(.default).eraseToAnyView()
    default:
        return EmptyView().eraseToAnyView()
    }
} 

我不明白为什么这种方法不设置动画 - 有没有一种方法可以在不使用视图模型中的多个属性来表示状态枚举的状态/计算属性的情况下为视图设置动画,或者这是最好的方法吗?

【问题讨论】:

标签: ios swiftui


【解决方案1】:

AnyView 对编译器隐藏所有实现细节。这意味着 SiwftUI 不能动画,因为它试图在编译期间推断所有动画。

这就是为什么您应该尽可能避免使用AnyView

如果您想将 body 的某些逻辑分离到计算属性(或函数)中,请使用 @ViewBuilder 注释。

@ViewBuilder
private var content: some View {
    switch viewModel.state {
    case .idle:
       mainView
    case let .error(error):
       ZStack {
            mainView
            ErrorView(
               errorDescription: error.localizedDescription,
               state: $viewModel.state
            )
        }.animation(.default)
    default:
       EmptyView()
    }
} 

请注意,我删除了属性中的 return@ViewBuilder 的行为与默认的 body 属性非常相似,将所有内容包装在 Group 中。


第二个问题是动画之间的状态。 通过使用switch,您正在创建一个SwiftUI._ConditionalContent&lt;MainView,SwiftUI.ZStack&lt;SwiftUI.TupleView&lt;(MainView, ErrorView)&gt;&gt;

这有点难以解析,但您创建了一个ConditionalContent,它在MainView 和一个由MainViewErrorView 组成的ZStack 之间切换,这不是我们想要的。

我们应该尝试创建一个不带MainViewZStack 和一个可选的ErrorView

我喜欢通过具有计算 error: Error? 属性的视图模型进行扩展。

extension ViewModel {
    var error: Error? {
        guard case let .error(error) = state else { 
              return nil
        }
        return error
   }
}

现在,您可以使用Optional 类型的map 函数创建可选视图

ZStack {
    mainView
    viewModel.error.map { unwrapped in
         ErrorView(
             errorDescription: unwrapped.localizedDescription,
             state: $viewModel.state
         )
    }
}

这应该可以按预期工作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-26
    • 1970-01-01
    • 1970-01-01
    • 2021-08-20
    • 1970-01-01
    相关资源
    最近更新 更多