防止SwiftUI动画覆盖嵌套在Animation块之上的内容

时间:2020-10-24 22:49:30

标签: ios swiftui swiftui-animation

我有一个带有可拖动组件(“ Springy”文本)的屏幕,当以非常出色的春季动画发布时,该屏幕会弹出。

有一些异步输入的文本(在此示例中,使用Timer),我希望当Springy标签平滑向上移动以为其留出空间时,它们能够很好地淡入。

Screenshot of example code

我在VStack中添加了一个动画,当异步加载的文本淡入时,它会按照我希望的那样过渡。但是,这会破坏“ Springy”文本上的spring动画。

这里有一个动画已注释掉,如果将1秒动画切换到该位置,异步加载的文本会淡入,但是在动画开始时,“弹性”文本会跳起来而不是向上滑动。

如何让两个动画都能按我的意愿工作?

import SwiftUI
import Combine

class Store: ObservableObject {
    @Published var showSection: Bool = false
    
    private var cancellables: [AnyCancellable] = []
    
    init() {
        
        Timer
            .publish(every: 2, on: RunLoop.main, in: .common)
            .autoconnect()
            .map { _ in true}
            .assign(to: \.showSection,
                    on: self)
            .store(in: &cancellables)
    }
}

struct ContentView: View {
    
    @ObservedObject var store = Store()
    
    @State var draggedState = CGSize.zero
    
    var body: some View {
        VStack {
            Spacer()
            VStack(alignment: .leading) {
                VStack(spacing: 4) {
                    HStack {
                        Text("Springy")
                            .font(.title)
                        Image(systemName: "hand.point.up.left.fill")
                            .imageScale(.large)
                            
                    }
                    .offset(x: draggedState.width, y: draggedState.height)
                    .foregroundColor(.secondary)
                    .gesture(
                        DragGesture().onChanged { value in
                            draggedState = value.translation
                        }
                        .onEnded { value in
                            // How can this animation be fast without the other animation slowing it down?
                            withAnimation(Animation
                                            .interactiveSpring(response: 0.3,
                                                               dampingFraction: 0.1,
                                                               blendDuration: 0)) {
                                draggedState = .zero
                            }
                        }
                    )
                    
                    VStack {
                        if store.showSection {
                            Text("Something that animates in after loading asynchrously.")
                                .font(.body)
                                .padding()
                                .transition(.opacity)
                        }
                    }
                    // When the animation is here, it doesn't cancel out the spring animation, but the Springy text jumps up at the beginning of the animation instead of animating.
//                    .animation(.easeInOut(duration: 1))
                }
                // Animation here has desired effect for loading, but overrides the Springy release animation.
                .animation(.easeInOut(duration: 1))
            }
            Spacer()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .preferredColorScheme(.dark)
    }
}

根据Asperi的回答扩展问题。

现在,假设我在主要VStack中有多个元素要在它们进入时进行动画处理。

  1. 为要设置动画的每个部分添加单独的.animation修饰符和value是一个好的解决方案吗?

在下面的扩展示例中,这是带有以下内容的部分:

.animation(.easeInOut(duration: 1),
                       value: store.showSection)
.animation(.easeInOut(duration: 1),
                      value: store.somePossibleText)
.animation(.easeInOut(duration: 1),
                      value: store.moreInformation)
  1. 我在这里使用3种方法来确定是否显示部分。为了简单起见,我在初始示例中使用了Bool,但是在我的实际情况下,我认为使用String?方法而不是设置单独的Bool或检查空字符串。在动画上使用它是否有缺点,还是很好?在使用Asperi的解决方案的情况下,此示例似乎运行良好。

这是具有完整修改的完整示例:

import SwiftUI
import Combine

class Store: ObservableObject {
    
    @Published var showSection: Bool = false
    @Published var somePossibleText: String = ""
    @Published var moreInformation: String?
    
    private var cancellables: [AnyCancellable] = []
    
    init() {
        
        Timer
            .publish(every: 2, on: RunLoop.main, in: .common)
            .autoconnect()
            .map { _ in true }
            .assign(to: \.showSection,
                    on: self)
            .store(in: &cancellables)
        
        Timer
            .publish(every: 3, on: RunLoop.main, in: .common)
            .autoconnect()
            .map { _ in "Something else loaded later" }
            .assign(to: \.somePossibleText,
                    on: self)
            .store(in: &cancellables)
        
        Timer
            .publish(every: 4, on: RunLoop.main, in: .common)
            .autoconnect()
            .map { _ in "More stuff loaded later" }
            .assign(to: \.moreInformation,
                    on: self)
            .store(in: &cancellables)
    }
}

struct ContentView: View {
    
    @ObservedObject var store = Store()
    
    @State var draggedState = CGSize.zero
    
    var body: some View {
        VStack {
            Spacer()
            VStack(alignment: .leading) {
                VStack(spacing: 4) {
                    HStack {
                        Text("Springy")
                            .font(.title)
                        Image(systemName: "hand.point.up.left.fill")
                            .imageScale(.large)
                    }
                    .offset(x: draggedState.width,
                            y: draggedState.height)
                    .foregroundColor(.secondary)
                    .gesture(
                        DragGesture().onChanged { value in
                            draggedState = value.translation
                        }
                        .onEnded { value in
                            // TODO: how can this animation be fast without the other one slowing it down?
                            withAnimation(Animation
                                            .interactiveSpring(response: 0.3,
                                                               dampingFraction: 0.1,
                                                               blendDuration: 0)) {
                                draggedState = .zero
                            }
                        }
                    )
                    
                    if store.showSection {
                        Text("Something that animates in after loading asynchrously.")
                            .font(.body)
                            .padding()
                            .transition(.opacity)
                    }
                    
                    if store.somePossibleText != "" {
                        Text(store.somePossibleText)
                            .font(.footnote)
                            .padding()
                            .transition(.opacity)
                    }
                    
                    if let moreInformation = store.moreInformation {
                        Text(moreInformation)
                            .font(.footnote)
                            .padding()
                            .transition(.opacity)
                    }
                }
                .animation(.easeInOut(duration: 1),
                           value: store.showSection)
                .animation(.easeInOut(duration: 1),
                           value: store.somePossibleText)
                .animation(.easeInOut(duration: 1),
                           value: store.moreInformation)
            }
            Spacer()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .preferredColorScheme(.dark)
    }
}

1 个答案:

答案 0 :(得分:5)

如果我正确理解了您的期望,则可以将最后一个动画绑定到showSection值,如下所示

}
// Animation here has desired effect for loading, but overrides the Springy release animation.
.animation(.easeInOut(duration: 1), value: store.showSection)

通过Xcode 12 / iOS 14测试。