Search code examples
iosswiftuikitios17ios-animations

Animate UIBarButtonItem with iOS 17 pulse symbol effect not working (without UIImageView)


I'm trying to get the new symbol animation from iOS 17 working for a UIBarButtonItem. The WWDC 2023 video linked to below, "Animate symbols in your app," promises the following:

First of all, the new UIKit methods on UIImageView are also available on UIBarButtonItem. This makes it easy to bring your toolbars to life using symbol animations.

Unfortunately, all of the example code accompanying the video only illustrates using UIImageView. I've tried using a UIImageView as a custom view passed to my UIBarButtonItem at initialization as a work-around, but that doesn't work right either.

To be clear, I want to get the effect working on UIBarButtonItem using the class's function.

I made a small demo project using a Storyboard. The code looks like this:

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let config = UIImage.SymbolConfiguration(scale: UIImage.SymbolScale.large)
        let image = UIImage(systemName: "playpause.fill", withConfiguration: config)
        
        let resumeButton =
            UIBarButtonItem(image: image, style: .plain, target: self, action: #selector(sayHello))
        
        resumeButton.addSymbolEffect(.pulse, options: .repeating, animated: true)
        resumeButton.isSymbolAnimationEnabled = true
        
        toolbarItems = [resumeButton]
    }
    
    @objc func sayHello(_ sender: UIBarButtonItem) {
        print("Hello: \(sender)")
    }
}

As far as I can tell, that should be all I need to get it to work. The button appears and is sized and lined up correctly, but it does not pulse. The symbol effect seems to be dead completely, contrary to what Apple has promised.

Anybody have ideas as to where I've gone wrong? (I'm hoping it's me and not Apple!)

Just for some more context, here is a screenshot of the Storyboard:

Storyboard screenshot

Here is a screenshot from the simulator:

Simulator screenshot

I have tried running this both on the simulator and an actual device, with no luck. The button does not pulse.

As a side note, using an image view as a custom view will result in a pulsing button. However, with respect to the other buttons in the toolbar, the button using the custom view neither lines up correctly nor is sized appropriately, when running on an iOS device. (Strangely, it will look fine in the Simulator!) This seems to be a known and longstanding issue that goes all the way back to iOS 11 and apparently has no easy answers. I'd rather abandon the whole effect than trying some kind of kludge to resize and realign an image view.

Thank you.


Solution

  • SOLVED

    I don't think this is at all obvious, but if you save a handle to the bar button item as an instance variable and then start the animation only after the view appears, the effect is visible.

    Here is the working code:

    import UIKit
    
    class ViewController: UIViewController {
        var button: UIBarButtonItem?
    
        override func viewDidLoad() {
            super.viewDidLoad()
            
            let config = UIImage.SymbolConfiguration(scale: UIImage.SymbolScale.large)
            let image = UIImage(systemName: "playpause.fill", withConfiguration: config)
            
            let resumeButton =
                UIBarButtonItem(image: image, style: .plain, target: self, action: #selector(sayHello))
            
            // The animation cannot be started yet…
            button = resumeButton
            
            toolbarItems = [resumeButton]
        }
        
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            
            // SOLVED: Start the animation after the view appears.
            button?.addSymbolEffect(.pulse, options: .repeating, animated: true)
        }
        
        @objc func sayHello(_ sender: UIBarButtonItem) {
            print("Hello: \(sender)")
        }
    }