Swift unable to animate navigation bar button item

Viewed 804

I need some help to translate the rightBarButtonItem in the UINavigationBar. I have followed this and also found similar questions which lead me to add the button as a customView of a UIBarButtonItem, however the code is still not working.

I have created a UIBarButtonItem and set a UIButton as its custom view.

let button: UIButton = {
    let b = UIButton(type: .system)
    b.setTitle("Test", for: .normal)
    return b
}()

lazy var barButton = UIBarButtonItem(customView: button)

This method is called from viewDidLoad

func configNavBar() {
    navigationItem.rightBarButtonItem = barButton
}

To animate the bar button when some action occurs:

func animateRightBarButton() {
    UIView.animate(withDuration: 0.25,
                   delay: 0,
                   usingSpringWithDamping: 0.8,
                   initialSpringVelocity: 1.5,
                   options: .curveEaseInOut,
                   animations: {
                    guard let customView = self.barButton.customView else {return}
                    var x: CGFloat = customView.transform.tx == 0 ? 100 : 0
                    customView.transform = CGAffineTransform(scaleX: x, y: 0)
    }, completion: nil)
} 

The bar button appears as expect when the view has loaded. I want the button to slide off screen but i have noticed a couple of problems.

The button disappears from the view with no animation when this function is called. If I print(x) (the translate x value) it remains at 100.0 and does not change back to 0 using the ternary operator. Any help appreciated to get this working, thanks.

2 Answers

use this code:

let bell = UIImage(named: "gift")!
    let button = UIButton(type: .custom)
    button.translatesAutoresizingMaskIntoConstraints = false
    button.setBackgroundImage(bell, for: .normal)
    button.addTarget(self, action: #selector(self.showAdd), for: .touchUpInside)
    let bellButton = UIBarButtonItem(customView: button)
    self.navigationItem.leftBarButtonItem = bellButton
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        if let bell = self.navigationItem.leftBarButtonItem?.customView {
            let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
            animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
            animation.duration = 0.6
            animation.values = [-8.0, 8.0, -8.0, 8.0, -4.0, 4.0, -4.0, 4.0, 0.0 ]
            bell.layer.add(animation, forKey: "shake")
        }
    }

Thanx to @KMI

I improved usability by carrying out the functionality of @KMI 's method in separate helper:

Swift 5

UIBarButtonItem+Helper.swift

extension UIBarButtonItem {
    typealias AnimationClosure = (_ customView: UIView)->()
    
    private var uniqueCustomViewAnimationKey: String {
        "customViewAnimation"
    }
    
    func animate(with animation: CAAnimation, delay: TimeInterval = 0.5) {
        DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
            guard let `self` = self else { return }
            self.customView?.layer.add(animation, forKey: self.uniqueCustomViewAnimationKey)
        }
    }
}

And usage in some class:

func bellItem() -> UIBarButtonItem {
    let button = UIButton(type: .custom)
    button.translatesAutoresizingMaskIntoConstraints = false
    button.setBackgroundImage(UIImage(named: "bell"), for: .normal)
    button.addTarget(self, action: #selector(bellItemPressed), for: .touchUpInside)
        
    let item = UIBarButtonItem(customView: button)
        
    // create custom animation
    let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
    animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
    animation.duration = 0.6
    animation.values = [-8.0, 7.0, -6.0, 5.0, -4.0, 3.0, -2.0, 1.0, 0.0 ]

    // applying the animation
    item.animate(with: animation)
        
    return item
}

@objc private func bellItemPressed() {
    // do some stuff when item pressed
}
Related