禁用 SwiftUI 框架动画出现

问题描述 投票:0回答:3

目标

假设我有一个

List
LazyVGrid
,它显示嵌套在
ScrollView
内的多个项目。我使用
ForEach
视图来生成单个项目视图:

ForEach(items) { item in
    ItemView(item)
}

items
数组可能是视图本身的
@State
属性,或者是符合
@Published
的视图模型上的
@ObservableObject
属性(在本例中我将使用第一个)。

现在,当我通过插入或删除元素来更改

items
数组时,我希望以特定的方式对更改进行动画处理,因此我添加了
transition
animation
修饰符,如下所示:

ScrollView {
    LazyVGrid(columns: 2) {
        ForEach(items) { item in
            ItemView(item)
                .transition(.scale)
        }
    }
}
.animation(.default, value: items)

这效果非常好。

问题

唯一的问题是,当视图首次出现时,此代码还会导致整个

ScrollView
从零缩放到其完整大小。 (这是有道理的,因为在从商店中获取项目之前,项目数组最初是空的,因此数组确实发生了变化。)

解决方案尝试

为了解决这个问题,我显然需要使动画依赖于一个在视图出现和加载项目数组之前“不”改变的属性。因此,我创建了一个普通布尔值的属性,并在 items 数组发生变化时切换它,但仅在调用

didAppear
后:
@State var changedState: Bool = false
@State var didAppear: Bool = false

@State var items: [Item] = [] {
    didSet {
        if didAppear {
            changedState.toggle()
        }
    }
}

然后我将动画修改器的 
value

更改为这个新属性:

.animation(.default, value: changedState)

✅ 这样就解决了问题。不过,感觉很“丑”,而且开销很大。

问题

还有其他(更优雅/简洁)的方法来禁用初始缩放动画吗?

👩u200d💻编辑:最小代码示例

struct ContentView: View { @State var items: [Int] = [] var body: some View { NavigationView { ScrollView { LazyVGrid(columns: [GridItem(), GridItem()]) { ForEach(items, id: \.self) { item in Rectangle() .frame(height: 50) .foregroundColor(.red) .transition(.scale) } } } .animation(.default, value: items) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button { let newItem = items.last.map { $0 + 1 } ?? 0 items.append(newItem) } label: { Text("Add Item") } } } } .onAppear { items = [Int](0...10) } } }

这就是初始动画的样子:

animation swiftui swiftui-animation onappear swiftui-transition
3个回答
2
投票
didSet

不会按您期望的方式工作,这就是我们有

.onChange()
的原因,但正如您所怀疑的,确实有一种更简单的方法。您只想制作将项目附加到列表(显示在屏幕上)的动画。最简单的方法是添加一个
@State
布尔值,并将其用作
.animation()
值。然后,当您添加到数组时,只需在按钮中切换它,如下所示:
struct ContentView: View {
    @State var items: [Int] = []
    @State var animate = false // Variable for animation
    
    var body: some View {
        ScrollView {
            LazyVGrid(columns: [GridItem(), GridItem()]) {
                ForEach(items, id: \.self) { item in
                    Rectangle()
                        .frame(height: 50)
                        .foregroundColor(.red)
                        .transition(.scale)
                }
            }
        }
        // Use animate as a flag to allow items to be the value
        // for .animation
        .animation(.default, value: (animate ? items : []))
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                Button {
                    let newItem = items.last.map { $0 + 1 } ?? 0
                    items.append(newItem)
                    animate.toggle() // <- Switch it here
                } label: {
                    Text("Add Item")
                }
            }
        }
        .onAppear {
            items = [Int](0...10)
            // The DispatchQueue is necessary to delay changing
            // the flag until the initial view is loaded.
            DispatchQueue.main.asyncAfter(deadline: .now()) {
                animate = true
            }
        }
    }
}

编辑:

上面的代码已更改以反映评论。这应该适合您的需求。


0
投票
.animation

修饰符应用于

LazyVGrid
而不是
ScrollView
可以按照您期望的那样工作。
struct ContentView: View {
    
    @State var items: [Int] = []
    
    var body: some View {
        NavigationView {
            ScrollView {
                LazyVGrid(columns: [GridItem(), GridItem()]) {
                    ForEach(items, id: \.self) { item in
                        Rectangle()
                            .frame(height: 50)
                            .foregroundColor(.red)
                            .transition(.scale)
                    }
                }
                .animation(.default, value: items) // <- New Place
            }
            // <- Old Place
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button {
                        let newItem = items.last.map { $0 + 1 } ?? 0
                        items.append(newItem)
                    } label: {
                        Text("Add Item")
                    }
                }
            }
        }
        .onAppear {
            items = [Int](0...10)
        }
    }
}



0
投票

您的代码应如下所示:

var transaction = Transaction() transaction.disablesAnimations = true withTransaction(transaction) { // Make the changes that you don't want animated }

这应该适用于任何标准动画,包括显示/消除工作表和全屏封面。

© www.soinside.com 2019 - 2024. All rights reserved.