skip to Main Content

Modern UIButton configuration API allows for new way of setting button appearance. However the code below skips the animation entirely.

@IBAction func buttonTouched(_ sender: UIButton) {
    sender.configuration?.background.backgroundColor = .green
    UIView.animate(withDuration: 0.4, delay: 0.5) {
        sender.configuration?.background.backgroundColor = .systemMint
    }  
}

Similar thing can be done like this:

@IBAction func buttonTouched(_ sender: UIButton) {
    sender.backgroundColor = .red
    UIView.animate(withDuration: 0.4, delay: 0.5) {
        sender.backgroundColor = .systemMint
    }
}

And this works. The question is how to animate UIButton‘s configuration changes.

2

Answers


  1. Chosen as BEST ANSWER

    The way to achieve animated configuration change by using transitions is the only workaround I could find so far. Somewhat like this:

    @IBAction func buttonTouched(_ sender: UIButton) {
        UIView.transition(with: sender, duration: 0.3, options: .transitionCrossDissolve) {
            sender.configuration?.background.backgroundColor = .red
        } completion: { _ in
            UIView.transition(with: sender, duration: 1.0, options: .transitionCrossDissolve) {
                sender.configuration?.background.backgroundColor = .cyan
            }
        }
    }
    

    When I tried to optimize and reuse code, I ended with something like:

    class ViewController: UIViewController {
        
        override func viewDidLoad() {
            super.viewDidLoad()
            // Another option is subclass `UIButton` and override its `configurationUpdateHandler`
            view.subviews.compactMap({ $0 as? UIButton }).forEach {
                $0.configurationUpdateHandler = { button in
                    guard let config = button.configuration else { return }
                    button.setConfiguration(config,duration: 2)
                }
            }
        }
    
        @IBAction func buttonTouched(_ sender: UIButton) {
    //        sender.configuration?.background.backgroundColor = .red
    //        sender.configuration?.baseForegroundColor = .white
    //        The above would work, but we should rather aggregate all changes at once.
            if var config = sender.configuration {
                config.background.backgroundColor = .red
                config.baseForegroundColor = .white
                sender.configuration = config
            }
            
        }
        
    }
    
    extension UIButton {
        func setConfiguration(_ configuration: UIButton.Configuration, duration: Double = 0.25, completion: ((Bool) -> Void)? = nil) {
            UIView.transition(with: self, duration: duration, options: .transitionCrossDissolve) {
                self.configuration? = configuration
            } completion: { completion?($0) }
        }
    }
     I
    

  2. After some quick searching, it appears configuration.background.backgroundColor is not animatable.

    Depending on your needs, you can use a .customView for the button’s background and then animate the color change for that view.

    Quick example (assuming you’ve added the button as an @IBOutlet):

    class TestVC: UIViewController {
    
        @IBOutlet var button: UIButton!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            let v = UIView()
            v.backgroundColor = .red
            button.configuration?.background.customView = v
            
        }
        
        @IBAction func buttonTouched(_ sender: UIButton) {
            if let v = sender.configuration?.background.customView {
                UIView.animate(withDuration: 0.4, delay: 0.5) {
                    v.backgroundColor = v.backgroundColor == .red ? .systemMint : .red
                }
            }
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search