SwiftUI:对List中的更改进行动画处理,而无需对内容更改进行动画处理

时间:2020-01-29 17:23:59

标签: swift swiftui watchos swiftui-list

我在SwiftUI中有一个简单的应用程序,显示了一个List,每个项目都是一个带有两个VStack元素的Text

var body: some View {
    List(elements) { item in
        NavigationLink(destination: DetailView(item: item)) {
            VStack(alignment: .leading) {
                Text(item.name) 
                Text(self.distanceString(for: item.distance))
            }
        }
    }
    .animation(.default)
}

.animate()在其中是因为我想在elements数组更改时为列表中的更改添加动画。不幸的是,SwiftUI还会对内容进行任何动画处理,从而导致怪异的行为。例如,每个项目中的第二个Text都非常频繁地更新,并且在更新为新内容之前,更新将立即显示被截断的标签(末尾带有...)。

那么,当我更新列表的内容时如何防止这种奇怪的行为,而当列表中的元素发生更改时如何保留动画呢?

如果相关,我将创建一个watchOS应用。

4 个答案:

答案 0 :(得分:11)

以下应禁用行内部的动画

VStack(alignment: .leading) {
    Text(item.name) 
    Text(self.distanceString(for: item.distance))
}
.animation(nil)

答案 1 :(得分:7)

@Asperi的回答解决了我也遇到的问题(一如既往地支持他的回答)。

我遇到一个问题,我在使用以下内容对整个屏幕进行动画处理:AnyTransition.asymmetric(insertion: .move(edge: .bottom), removal: .move(edge: .top))

所有Text()和Button()子视图也以怪异而不是奇妙的方式进行动画处理。看到Asperi的回答后,我使用animation(nil)解决了这个问题。但是问题是我的按钮在选择时不再带有动画,而我也想要其他动画。

因此,我添加了一个新的State变量来打开和关闭VStack的动画。默认情况下,它们是关闭的,并且在屏幕上对视图进行了动画处理后,我稍加延迟便启用了它们:

struct QuestionView : View {
    @State private var allowAnimations : Bool = false

    var body : some View {
        VStack(alignment: .leading, spacing: 6.0) {
            Text("Some Text")

            Button(action: {}, label:Text("A Button")
        }
        .animation(self.allowAnimations ? .default : nil)
        .onAppear() {
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
                self.allowAnimations = true
            }
        }
    }
}

只要为与我有类似问题并且需要以Asperi的出色回答为基础的人添加此内容即可。

答案 2 :(得分:1)

感谢@Brett提供延迟解决方案。我的代码在几个地方需要它,因此我将其包装在ViewModifier中。

只需在您的视图中添加 .delayedAnimation()

您可以传递一秒以外的默认参数和默认动画。

import SwiftUI

struct DelayedAnimation: ViewModifier {
  var delay: Double
  var animation: Animation

  @State private var animating = false

  func delayAnimation() {
    DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
      self.animating = true
    }
  }

  func body(content: Content) -> some View {
    content
      .animation(animating ? animation : nil)
      .onAppear(perform: delayAnimation)
  }
}

extension View {
  func delayedAnimation(delay: Double = 1.0, animation: Animation = .default) -> some View {
    self.modifier(DelayedAnimation(delay: delay, animation: animation))
  }
}

答案 3 :(得分:0)

在我的情况下,上述任何一种都会导致奇怪的行为。解决方案是动画触发元素数组的变化而不是列表。例如:


@State private var sortOrderAscending = true

// Your list of elements with some sorting/filtering that depends on a state
// In this case depends on sortOrderAscending
var elements: [ElementType] {
    let sortedElements = Model.elements
    if (sortOrderAscending) {
        return sortedElements.sorted { $0.name < $1.name }
    } else {
        return sortedElements.sorted { $0.name > $1.name }
    }
}

var body: some View {

    // Your button or whatever that triggers the sorting/filtering
    // Here is where we use withAnimation
    Button("Sort by name") {
        withAnimation {
            sortOrderAscending.toggle()
        }
    }

    List(elements) { item in
        NavigationLink(destination: DetailView(item: item)) {
            VStack(alignment: .leading) {
                Text(item.name) 
            }
        }
    }

}