简体   繁体   English

在 SwiftUI 中为视图设置动画以向上滑动和隐藏

[英]Animate a view to slide up and hide on tap in SwiftUI

I created a banner modifier that displays a banner from the top.我创建了一个从顶部显示横幅的横幅修改器。 This animates well.这动画效果很好。 However, when I tap to dismiss it, it does not animate at all, just hides even though the tap gesture action has withAnimation wrapping it.但是,当我点击以关闭它时,它根本没有动画,即使点击手势动作有withAnimation包裹它,它也会隐藏起来。

struct BannerModifier: ViewModifier {
    @Binding var model: BannerData?
    
    func body(content: Content) -> some View {
        content.overlay(
            Group {
                if model != nil {
                    VStack {
                        HStack(alignment: .firstTextBaseline) {
                            Image(systemName: "exclamationmark.triangle.fill")
                            VStack(alignment: .leading) {
                                Text(model?.title ?? "")
                                    .font(.headline)
                                if let message = model?.message {
                                    Text(message)
                                        .font(.footnote)
                                }
                            }
                        }
                        .padding()
                        .frame(minWidth: 0, maxWidth: .infinity)
                        .foregroundColor(.white)
                        .background(.red)
                        .cornerRadius(10)
                        .shadow(radius: 10)
                        Spacer()
                    }
                    .padding()
                    .animation(.easeInOut)
                    .transition(AnyTransition.move(edge: .top).combined(with: .opacity))
                    .onTapGesture {
                        withAnimation {
                            model = nil
                        }
                    }
                    .gesture(
                        DragGesture()
                            .onChanged { _ in
                                withAnimation {
                                    model = nil
                                }
                            }
                    )
                }
            }
        )
    }
}

struct BannerData: Identifiable {
    let id = UUID()
    let title: String
    let message: String?
}

在此处输入图像描述

In the tap gesture, I wipe out the model but it does not animate.在点击手势中,我清除了 model 但它没有动画。 It only hides immediately.它只会立即隐藏。 How can I animate it so it slide up which is opposite of how it slide down to display?如何对其进行动画处理,使其向上滑动,这与向下滑动显示的方式相反? It would be really nice if I can also make the drag gesture interactive so I can slide it out like the native notifications.如果我还可以使拖动手势交互,那将是非常好的,这样我就可以像原生通知一样将其滑出。

Removing view from hierarchy is always animated by container, so to fix your modifier it is needed to apply .animation on some helper container (note: Group is not actually a real container).从层次结构中删除视图始终由容器设置动画,因此要修复您的修改器,需要在一些辅助容器上应用.animation (注意: Group实际上不是真正的容器)。

演示

Here is corrected variant这是更正的变体

struct BannerModifier: ViewModifier {
    @Binding var model: BannerData?
    
    func body(content: Content) -> some View {
        content.overlay(
            VStack {               // << holder container !!
                if model != nil {
                    VStack {
                        HStack(alignment: .firstTextBaseline) {
                            Image(systemName: "exclamationmark.triangle.fill")
                            VStack(alignment: .leading) {
                                Text(model?.title ?? "")
                                    .font(.headline)
                                if let message = model?.message {
                                    Text(message)
                                        .font(.footnote)
                                }
                            }
                        }
                        .padding()
                        .frame(minWidth: 0, maxWidth: .infinity)
                        .foregroundColor(.white)
                        .background(Color.red)
                        .cornerRadius(10)
                        .shadow(radius: 10)
                        Spacer()
                    }
                    .padding()
                    .transition(AnyTransition.move(edge: .top).combined(with: .opacity))
                    .onTapGesture {
                        withAnimation {
                            model = nil
                        }
                    }
                    .gesture(
                        DragGesture()
                            .onChanged { _ in
                                withAnimation {
                                    model = nil
                                }
                            }
                    )
                }
            }
            .animation(.easeInOut)         // << here !!
        )
    }
}

Tested with Xcode 12.1 / iOS 14.1 and test view:使用 Xcode 12.1 / iOS 14.1 和测试视图进行测试:

struct TestBannerModifier: View {
    @State var model: BannerData?
    var body: some View {
        VStack {
            Button("Test") { model = BannerData(title: "Error", message: "Fix It!")}
            Button("Reset") { model = nil }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .modifier(BannerModifier(model: $model))
    }
}

This is a slightly improoved version of the banner posted by Asperi Hope it helps someone.这是Asperi发布的横幅的略微改进版本,希望对某人有所帮助。

import SwiftUI

public class BannerData {
    public enum BannerType {
        case warning, error, success

        var textColor: Color {
            switch self {
            case .warning:
                return .black
            case .error:
                return .white
            case .success:
                return .white
            }
        }

        var backgroundColor: Color {
            switch self {
            case .warning:
                return .yellow
            case .error:
                return .red
            case .success:
                return .green
            }
        }

        var icon: String {
            switch self {
            case .warning:
                return "exclamationmark.triangle.fill"
            case .error:
                return "exclamationmark.circle.fill"
            case .success:
                return "checkmark.circle.fill"
            }
        }
    }

    var type: BannerType = .success
    let title: String
    let message: String?

    public init(title: String, message: String? = nil, type: BannerType) {
        self.title = title
        self.message = message
        self.type = type
    }
}

public struct BannerModifier: ViewModifier {
    @Binding var model: BannerData?

    public init(model: Binding<BannerData?>) {
        _model = model
    }

    public func body(content: Content) -> some View {
        content.overlay(
            VStack {
                if model != nil {
                    VStack {
                        HStack(alignment: .firstTextBaseline) {
                            Image(systemName: model?.type.icon ?? BannerData.BannerType.success.icon)
                            VStack(alignment: .leading) {
                                Text(model?.title ?? "")
                                    .font(.headline)
                                if let message = model?.message {
                                    Text(message)
                                        .font(.footnote)
                                }
                            }
                            Spacer()
                        }
                        .padding()
                        .frame(minWidth: 0, maxWidth: .infinity)
                        .foregroundColor(.white)
                        .background(model?.type.backgroundColor ?? .clear)
                        .cornerRadius(10)
                        .shadow(radius: 10)
                        Spacer()
                    }
                    .padding()
                    .transition(AnyTransition.move(edge: .top).combined(with: .opacity))
                    .onTapGesture {
                        withAnimation {
                            model = nil
                        }
                    }
                    .gesture(
                        DragGesture()
                            .onChanged { _ in
                                withAnimation {
                                    model = nil
                                }
                            }
                    )
                }
            }
            .animation(.spring())
        )
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM