How do you change the image a SwiftUI button shows while the user is pressing the button?
Button State
A UIKit control like a UIButton has a default normal state and additional states that indicate if it’s highlighted, disabled, selected or focused. You can provide different configurations for each state. You can configure the button to show a different image, title, or background when the user touches (highlights) it. That provides a useful visual feedback to the user.
A SwiftUI button lacks a lot of the UIKit customizations. I’ve written about SwiftUI button styles and shapes before. The ButtonStyleConfiguration gives you access to the button label, isPressed state, and button role (cancel or destructive).
I wanted to create the equivalent of a UIKit info button that uses the “info.circle” SF Symbol:
When the user presses the button I want the image to switch the filled variant of the symbol:
Pressed Button Style
Since this is something I’m going to want to reuse for other buttons I built a pressed button style that accepts a title, and two images for the default and pressed states:
struct PressedButtonStyle: ButtonStyle {
let title: String
let systemImage: String
let pressedImage: String
func makeBody(configuration: Configuration) -> some View {
let imageName = configuration.isPressed ? pressedImage : systemImage
return Label(title, systemImage: imageName)
}
}
In the makeBody method I check the button configuration and return a label with the correct image based on the isPressed state. I don’t like how the style ignores the button’s configured label but I don’t see another way?
Then to build my InfoButton:
struct InfoButton: View {
let action: () -> Void
init(_ action: @escaping () -> Void) {
self.action = action
}
var body: some View {
Button("") {
action()
}
.buttonStyle(
PressedButtonStyle(
title: "Info",
systemImage: "info.circle",
pressedImage: "info.circle.fill")
)
.padding()
}
}
Using the button:
InfoButton {
// Do something
}
.labelStyle(.iconOnly)
.font(.largeTitle)
Adding Animation
One extra refinement, adding an SF Symbol effect to the PressedButtonStyle to scale the image up when pressed:
struct PressedButtonStyle: ButtonStyle {
let title: String
let systemImage: String
let pressedImage: String
func makeBody(configuration: Configuration) -> some View {
let imageName = configuration.isPressed ? pressedImage : systemImage
return Label(title, systemImage: imageName)
.symbolEffect(.scale.up, isActive: configuration.isPressed)
}
}
Maybe Apple will improve the button configuration to make this easier but until then that’s the best I can manage. If you have a better approach let me know.