SwiftKickMobile / SwiftMessages

A very flexible message bar for UIKit and SwiftUI.
MIT License
7.24k stars 741 forks source link

Delay for hide animation #468

Closed yunustek closed 3 years ago

yunustek commented 3 years ago

Why didn't you give a delay option to the hide animation? I want to run an animation on the popup before I start hiding, and the shutdown process shouldn't start until that animation time is over.


    public func hide(context: AnimationContext, completion: @escaping AnimationCompletion) {
        NotificationCenter.default.removeObserver(self)
        if panHandler.isOffScreen {
            context.messageView.alpha = 0
            panHandler.state?.stop()
        }
        let view = context.messageView
        self.context = context
        CATransaction.begin()
        CATransaction.setCompletionBlock {
            view.alpha = 1
            view.transform = CGAffineTransform.identity
            completion(true)
        }
        UIView.animate(withDuration: hideDuration, delay: 0, options: [.beginFromCurrentState, .curveEaseIn, .allowUserInteraction], animations: {
            view.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
        }, completion: nil)
        UIView.animate(withDuration: hideDuration, delay: 0, options: [.beginFromCurrentState, .curveEaseIn, .allowUserInteraction], animations: {
            view.alpha = 0
        }, completion: nil)
        CATransaction.commit()
    }
wtmoose commented 3 years ago

No reason in particular. I'll add options if there's a need for it. Can you not just call hide() in the completion block of your other animations?

wtmoose commented 3 years ago

No reply. Closing

yunustek commented 3 years ago

I can't pause the hide process when I define auto hide time. I can hide but i can't pause hiding. I think you need add delay option to hide like this:

Using:

        let animator = TopBottomAnimation(style: .top)
        animator.hideDelay = 2
        config.presentationStyle = .custom(animator: animator)

Changes: hideDelay

   func hide(context: AnimationContext, completion: @escaping AnimationCompletion) {
        NotificationCenter.default.removeObserver(self)
        let view = context.messageView
        self.context = context
        UIView.animate(withDuration: hideDuration, delay: hideDelay, options: [.beginFromCurrentState, .curveEaseIn], animations: {
            switch self.style {
            case .top:
                view.transform = CGAffineTransform(translationX: 0, y: -view.frame.height)
            case .bottom:
                view.transform = CGAffineTransform(translationX: 0, y: view.frame.maxY + view.frame.height)
            }
        }, completion: { completed in
            completion(completed || UIApplication.shared.applicationState != .active)
        })
    }
wtmoose commented 3 years ago

It isn't quite as straightforward as adding a hideDelay property. The animator is required to provide an accurate value for hideDuration, which is a read/write property on TopBottomAnimation but it also needs to include hideDelay. I'd need to make an API change to accomplish this.

Regardless, my recommendation is to make your own animator and use composition with TopBottomAnimation. Something like this, nice and self-contained:

class FancyAnimation: Animator {

    var delegate: AnimationDelegate? {
        get {
            return animation.delegate
        }
        set {
            animation.delegate = newValue
        }
    }

    func show(context: AnimationContext, completion: @escaping AnimationCompletion) {
        animation.show(context: context, completion: completion)
    }

    func hide(context: AnimationContext, completion: @escaping AnimationCompletion) {
        UIView.animate(withDuration: localHideDuration, delay: 0, options: [.beginFromCurrentState, .curveEaseIn]) {
            let view = context.messageView
            view.alpha = 0.5
        } completion: { _ in
            self.animation.hide(context: context, completion: completion)
        }

    }

    var showDuration: TimeInterval { return animation.showDuration }

    var hideDuration: TimeInterval { return animation.hideDuration + localHideDuration }

    init(animation: Animator) {
        self.animation = animation
    }

    private let localHideDuration: TimeInterval = 1
    private let animation: Animator
}

Then you can do something like this:

let topBottomAnimation = TopBottomAnimation(style: .top)
let animation = FancyAnimation(animation: topBottomAnimation)
var config = SwiftMessages.defaultConfig
config.presentationStyle = .custom(animator: animation)
yunustek commented 3 years ago

Okay I try it but it's not hiding when I swiping to top?

wtmoose commented 3 years ago

Oh yeah, I forgot to test the interactive hide.

Here's an update, which is more complicated than I'd like, but you can use it as a workaround while I try to think of something better.

class FancyAnimation: Animator {

    var delegate: AnimationDelegate?

    func show(context: AnimationContext, completion: @escaping AnimationCompletion) {
        animation.show(context: context, completion: completion)
    }

    func hide(context: AnimationContext, completion: @escaping AnimationCompletion) {
        if doLocalHide {
            UIView.animate(withDuration: localHideDuration, delay: 0, options: [.beginFromCurrentState, .curveEaseIn]) {
                let view = context.messageView
                view.alpha = 0.5
            } completion: { _ in
                self.animation.hide(context: context, completion: completion)
            }
        } else {
            self.animation.hide(context: context, completion: completion)
        }
    }

    var showDuration: TimeInterval { return animation.showDuration }

    var hideDuration: TimeInterval {
        return animation.hideDuration + (doLocalHide ? localHideDuration : 0)
    }

    init(animation: Animator) {
        self.animation = animation
        animation.delegate = self
    }

    private var doLocalHide = true
    private let localHideDuration: TimeInterval = 1
    private let animation: Animator
}

extension FancyAnimation: AnimationDelegate {
    func hide(animator: Animator) {
        doLocalHide = false
        delegate?.hide(animator: self)
    }

    func panStarted(animator: Animator) {
        delegate?.panStarted(animator: self)
    }

    func panEnded(animator: Animator) {
        delegate?.panEnded(animator: self)
    }
}