How can I know if a SwiftUI Button is enabled/disabled?
Inside a view, if you wish to react to the state set by .disabled(true)
, you can use:
@Environment(\.isEnabled) var isEnabled
Since the environment can be used from within a View or a ViewModifier, this can be used to change layout properties of a view based on the state set from outside.
Unfortunately, ButtonStyle
cannot directly use @Environment
, but you can use a ViewModifier
to inject environment values into a ButtonStyle
in order to use the value from within a ButtonStyle
:
// First create a button style that gets the isEnabled value injected
struct MyButtonStyle: ButtonStyle {
private let isEnabled: Bool
init(isEnabled: Bool = true) {
self.isEnabled = isEnabled
}
func makeBody(configuration: Configuration) -> some View {
return configuration
.label
.background(isEnabled ? .green : .gray)
.foregroundColor(isEnabled ? .black : .white)
}
}
// Then make a ViewModifier to inject the state
struct MyButtonModifier: ViewModifier {
@Environment(\.isEnabled) var isEnabled
func body(content: Content) -> some View {
return content.buttonStyle(MyButtonStyle(isEnabled: isEnabled))
}
}
// Then create a convenience function to apply the modifier
extension Button {
func styled() -> some View {
ModifiedContent(content: self, modifier: MyButtonModifier())
}
}
// Finally, try out the button and watch it respond to it's state
struct ContentView: View {
var body: some View {
Button("Test", {}).styled().disabled(true)
}
}
You can use this method to inject other things into a ButtonStyle, like size category and theme.
I use it with a custom style enum that contains all the flavours of button styles found in our design system.
The whole idea of SwiftUI, is to avoid duplication of the source of truth. You need to think differently, and consider where the source of truth is. This is where you need to go to find out the button's state. Not from the button itself.
In "Data Flow Through SwiftUI", at minute 30:50, they explain that every piece of data has a single source of truth. If your button gets its state from some @Binding, @State, @EnvironmentObject, etc, your if statement should get that information from the same place too, not from the button.
From outside a view you should know if you used .disabled(true)
modifier.
From inside a view you can use @Environment(\.isEnabled)
to get that information:
struct MyButton: View {
let action: () -> Void
@Environment(\.isEnabled) private var isEnabled
var body: some View {
Button(action: action) {
Text("Click")
}
.foregroundColor(isEnabled ? .green : .gray)
}
}
struct MyButton_Previews: PreviewProvider {
static var previews: some View {
VStack {
MyButton(action: {})
MyButton(action: {}).disabled(true)
}
}
}