Home > database >  Animating UIButton configuration change
Animating UIButton configuration change

Time:04-13

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.

CodePudding user response:

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
            }
        }
    }
}

CodePudding user response:

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
  • Related