Xcode 15 strokeColor functionality incorrect with UIButton configuration

Hi,

I realize that updating strokeColor property on the UIButton configuration after dark/light mode changes is not working correctly. Here is a sample code to explain problem.


class ViewController: UIViewController {

    var configuration = UIButton.Configuration.plain()

    private lazy var button: UIButton = {
        let button = UIButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle("Test Button", for: .normal)
        configuration.background.strokeColor = borderColor
        configuration.background.backgroundColor = borderColor
        configuration.background.strokeWidth = 10
        button.configuration = configuration
        return button
    }()

    var borderColor: UIColor {
        UIColor.dynamic(light: UIColor.systemPink, dark: UIColor.green)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(button)
        NSLayoutConstraint.activate([
            button.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 30),
            button.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -30),
            button.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 100),
            button.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -100)
        ])
    }

}
extension UIColor {
    static func dynamic(light: UIColor, dark: UIColor) -> UIColor {
        return UIColor { traitCollection -> UIColor in
            traitCollection.userInterfaceStyle == .light ? light : dark
        }
    }
}

The code above is quite simple I have a button and it has UIButtonConfiguration with backgroundColor and strokeColor functionalities. Both backgroundColor and strokeColor use the same borderColor. I also have a simple extension to generate dynamic color for active trait collection. So in theory whenever I change the active appearance UIButton Configuration should fetch the dynamic color based on the active trait (pink for light, green for dark). But here are the results.

First app install on dark mode

After changing trait to light mode

After changing trait to dark mode again

As you can see I am using the same color for both the background and stroke they are messed up after the first trait change. Somehow configuration.background.strokeColor gets incorrect trait color. I debug it and check it borderColor returns the correct color. Also, this code is working on Xcode 14.3.1 perfectly. Somehow the strokeColor functionality seems to be broken on Xcode 15.

Does anyone have any idea how to work around it?

Thanks for bringing this to our attention; this appears to be a bug in iOS 17.

As a temporary workaround until a fix is available in a future update, try adding the following code after you set the configuration of the button:

    private lazy var button: UIButton = {
        let button = UIButton()
        // ...
        button.configuration = configuration

        // Workaround for stroke color not updating properly in response to userInterfaceStyle changes
        button.configurationUpdateHandler = { b in
            guard var config = b.configuration else { return }
            let strokeColor = config.background.strokeColor
            config.background.strokeColor = .clear
            b.configuration = config
            config.background.strokeColor = strokeColor
            b.configuration = config
        }
        
        return button
    }()

What this workaround does is manually reset the button's background stroke color to .clear and then back to the original color anytime the button's state changes, which includes changes to the button's traits (such as its userInterfaceStyle).

Xcode 15 strokeColor functionality incorrect with UIButton configuration
 
 
Q