How to Know If a Swiftui Button Is Enabled/Disabled

How can I know if a SwiftUI Button is enabled/disabled?

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.

SwiftUI ButtonStyle - how to check if button is disabled or enabled?

I found the answer thanks to this blog: https://swiftui-lab.com/custom-styling/

You can get the enabled state from the environment by creating a wrapper view and using it inside the style struct:

struct MyButtonStyle: ButtonStyle {
func makeBody(configuration: ButtonStyle.Configuration) -> some View {
MyButton(configuration: configuration)
}

struct MyButton: View {
let configuration: ButtonStyle.Configuration
@Environment(\.isEnabled) private var isEnabled: Bool
var body: some View {
configuration.label.foregroundColor(isEnabled ? Color.green : Color.red)
}
}
}

This example demonstrates how to get the state and use it to change the appearance of the button. It changes the button text color to red if the button is disabled or green if it's enabled.

Button won't show disabled styling

The @Environment is not injected in style, it is only for views, so here a demo of possible solution - based on internal helping wrapper view.

struct CustomButton: ButtonStyle {

enum ButtonStyle {
case button, destructive, light
}
enum ButtonSize {
case normal, large, small
}

private var maxWidth : CGFloat
private var padding : CGFloat
private var foreground : Color
private var foregroundDisabled : Color
private var strokeColor : Color
private var strokeDisabled : Color
private var strokeWidth : CGFloat
private var background : Color
private var backgroundDisabled : Color
private var fontSize : Font

init(style: ButtonStyle, size: ButtonSize) {
switch size {
case .large:
self.maxWidth = .infinity
self.padding = 15.0
self.fontSize = Font.system(.title3, design: .rounded).weight(.bold)
case .small:
self.maxWidth = 200
self.padding = 8.0
self.fontSize = Font.system(.callout, design: .rounded)
default:
self.maxWidth = .infinity
self.padding = 12.0
self.fontSize = Font.system(.body, design: .rounded)
}

switch style {
case .light:
strokeColor = .main
strokeDisabled = .gray
strokeWidth = size == .small ? strokeSmall: strokeLarge
foreground = .main
foregroundDisabled = .gray
background = .clear
backgroundDisabled = .clear
case .destructive:
strokeColor = .destructive
strokeDisabled = .gray
strokeWidth = size == .small ? strokeSmall : strokeLarge
foreground = .destructive
foregroundDisabled = .destructive
background = .clear
backgroundDisabled = .clear
default:
strokeColor = .clear
strokeDisabled = .clear
strokeWidth = 0.0
foreground = .white
foregroundDisabled = .white
backgroundDisabled = .gray
background = .main
}
}

private struct EnvReaderView<Content: View>: View { // << this one !!
let content: (Bool) -> Content
@Environment(\.isEnabled) var isEnabled // read environment
var body: some View {
content(isEnabled) // transfer into builder
}
}

func makeBody(configuration: Self.Configuration) -> some View {
EnvReaderView { isEnabled in // now we can use it !!
configuration.label
.frame(maxWidth: maxWidth)
.font(fontSize)
.foregroundColor(isEnabled ? foreground : Color.red)
.padding(padding)
.background(RoundedRectangle(cornerRadius: roundedCorner)
.strokeBorder(isEnabled ? strokeColor : strokeDisabled, lineWidth: strokeWidth)
.background(isEnabled ? background : backgroundDisabled)
)
.clipShape(RoundedRectangle(cornerRadius: roundedCorner))
.opacity(configuration.isPressed ? 0.8 : 1.0)
.scaleEffect(configuration.isPressed ? 0.98 : 1.0)
}
}
}

How to disable / modify a button using SwiftUI?

Here is possible approach (see also comments in code). Tested & works with Xcode 11.2 / iOS 13.2.

struct ContentView: View {

@State private var buttonColor : Color = .blue

var body: some View {
VStack {

Button(action: {
self.buttonColor = .red

DispatchQueue.global(qos: .background).async { // do in background
test()
DispatchQueue.main.async {
self.buttonColor = .blue // work with UI only in main
}
}

}) {
Text("Start")
.font(.title)
.padding(.horizontal, 40)
.padding(.vertical, 5)
.background(self.buttonColor)
.foregroundColor(.white)
}
.disabled(self.buttonColor == .red) // disabled while calculating
}
}
}

SwiftUI: Disable/Re-Enable button based on WebView state

You're ending up doubling up the publisher property by defining them as @Published and CurrentValueSubject.

The easiest fix would be to just make them Published, which handles most of the work for you:

class ViewModel: ObservableObject {
@Published var canGoBack = false
@Published var canGoForward = false
}

//...
//In delegate:
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
viewModel.canGoBack = webView.canGoBack
viewModel.canGoForward = webView.canGoForward
}

//...
//In Navigation view:
Button(action: goBack) {
Image(systemName: "chevron.left").resizable().aspectRatio(contentMode: .fit)
}.disabled(!viewModel.canGoBack) //<-- here
.frame(width: 24, height: 24, alignment: .center).padding(.leading, 32)

Button(action: goForward) {
Image(systemName: "chevron.right").resizable().aspectRatio(contentMode: .fit)
}.disabled(!viewModel.canGoForward) //<-- here
.frame(width: 24, height: 24, alignment: .center)

You could still define them as CurrentValueSubject if you want (and ditch the @Published property wrapper), but there's probably no need to in this case.

Good SO question on the difference between @Published and CurrentValueSubject: Difference between CurrentValueSubject and @Published

Smartest way to set color for enabled/disabled buttons with SwiftUI

Actually, adding .buttonStyle(.plain) to the button just did what I was looking for. Now, the button automatically gets a gray color if it's disabled.



Related Topics



Leave a reply



Submit