49

To style a button in SwiftUI, according to my understanding, you extend ButtonStyle and implement func makeBody(configuration: Self.Configuration) -> some View within which you start applying modifications to the configuration.label which is a reference to the Button view.

Now, besides the label field, ButtonStyle.Configuration has a boolean field for isPressed, but that's seems to be all.

How do I check if the button is enabled or disabled?

For example, I want to draw a border around the button, and I want the border to be blue if the button is enabled and gray if disabled.

My first guess is something like this:

    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .overlay(
                RoundedRectangle(cornerRadius: 4).stroke(configuration.isEnabled ? Color.blue : Color.gray, lineWidth: 1).padding(8)
            )
    }

But there's no isEnabled field.

The Apple-supplied PlainButtonStyle obviously has access to this information, as the doc comment in the header file that declares it says 'The style may apply a visual effect to indicate the pressed, focused, or enabled state of the button.'

/// A `Button` style that does not style or decorate its content while idle.
///
/// The style may apply a visual effect to indicate the pressed, focused,
/// or enabled state of the button.
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public struct PlainButtonStyle : PrimitiveButtonStyle {
.....

Is there a way to access this information?

EDIT:

Before you suggest closing this question, please read this:

There's a similar question asked a while ago. That question does not specify any context.

Here I have a specific context: creating a generic reusable button style.

The answer provided there does not suffice. I can't expect people to pass the disabled state to the style constructor. That's too much duplication of effort.

Can you imagine if people have to write code this way:

Button() { .... }.disabled(someExprssion).buttonStyle(MyCustomStyle(disabled: someExpression))

Clearly that's not desireable.

Also clearly the Apple supplied style has access to the information without requiring people to pass the disable state again to the style.

If you close the question, you will forever prevent anyone from providing a useful answer to this question on StackOverflow.

Please reconsider before you propose closing.

EDIT2:

I have a guess that if you can get the EnvironmentValues.isEnabled of the Button view then it might be the right value.

So another way to ask the question is: is it possible in SwiftUI to get the environment of a view that you didn't define yourself?

hasen
  • 161,647
  • 65
  • 194
  • 231
  • Does this answer your question? [How can I know if a SwiftUI Button is enabled/disabled?](https://stackoverflow.com/questions/56849090/how-can-i-know-if-a-swiftui-button-is-enabled-disabled) – Hitesh Surani Dec 04 '19 at 05:44
  • that suggests there's no way, but clearly there is a way since the builtin PlainButtonStyle knows? – hasen Dec 04 '19 at 05:50
  • 2
    As expected, the close police started raiding .. – hasen Dec 04 '19 at 05:51
  • 1
    When creating a new struct NewButtonStyle: ButtonStyle, it seems that it's not possible to grab the button's @Environment(\.isEnabled) var isEnabled. Only got to access the correct value of isEnabled when creating a custom Button... – Victor Sanchez Dec 05 '19 at 15:06

7 Answers7

64

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.

hasen
  • 161,647
  • 65
  • 194
  • 231
30

You can access the enabled value through environment directly in the ButtonStyle, eg:

struct MyButtonStyle: ButtonStyle {
    @Environment(\.isEnabled) var isEnabled
    
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .background(!isEnabled ? Color.black : (configuration.isPressed ? Color.red : Color.blue))
            
    }
    
}
Jonathan.
  • 53,997
  • 54
  • 186
  • 290
10

Tweaked up a bit on @hasen answer so as to make things more configurable

struct MyButtonStyle: ButtonStyle {
    var foreground = Color.white
    var background = Color.blue
    
    func makeBody(configuration: ButtonStyle.Configuration) -> some View {
        MyButton(foreground: foreground, background: background, configuration: configuration)
    }

    struct MyButton: View {
        var foreground:Color
        var background:Color
        let configuration: ButtonStyle.Configuration
        @Environment(\.isEnabled) private var isEnabled: Bool
        var body: some View {
            configuration.label
                .padding(EdgeInsets(top: 7.0, leading: 7.0, bottom: 7.0, trailing: 7.0))
                .frame(maxWidth: .infinity)
                .foregroundColor(isEnabled ? foreground : foreground.opacity(0.5))
                .background(isEnabled ? background : background.opacity(0.5))
                .opacity(configuration.isPressed ? 0.8 : 1.0)
        }
    }
}

Usage

       Button(action: {}) {
            Text("Hello")
        }.buttonStyle(MyButtonStyle(foreground: .white, background: .green))
anoop4real
  • 7,598
  • 4
  • 53
  • 56
7

Answering your question: yes, it is possible to get the Environment value you didn't pass yourself. SwiftUI uses the Environment heavily and a lot of Views depend on it without you even knowing it.

A ButtonStyle can access Environment too so you can achieve what you want as easy as:

struct SomeButtonStyle: ButtonStyle {
    @Environment(\.isEnabled) var isEnabled: Bool

    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .disabled(!self.isEnabled)
            .opacity(self.isEnabled ? 1 : 0.5)
    }
}
ramzesenok
  • 5,469
  • 4
  • 30
  • 41
5

Another option would be to pass a disabled Boolean value to your ButtonStyle, and then use the allowsHitTesting modifier on the label to effectively disable the button:

struct MyButtonStyle: ButtonStyle {
    let disabled: Bool

    func makeBody(configuration: Self.Configuration) -> some View {
        configuration
            .label
            .allowsHitTesting(!disabled)
    }
}
damirstuhec
  • 6,069
  • 1
  • 22
  • 39
1

You can merge the disabled status into the buttonStyle, so that you will not used it twice in the application.

  struct CustomizedButtonStyle : PrimitiveButtonStyle {

var disabled: Bool = false

func makeBody(configuration: Self.Configuration) -> some View {
configuration.label.disabled(disabled)
    .overlay(
        RoundedRectangle(cornerRadius: 4).stroke(disabled ? Color.blue :  Color.gray, lineWidth: 1).padding(8)
    )
}
}


 struct ButtonUpdate: View {
 var body: some View {
    VStack{
    Button(action: {
    }) { Text("button")
    }.buttonStyle(CustomizedButtonStyle(disabled: true))
    Button(action: {
    }) { Text("button")
    }.buttonStyle(CustomizedButtonStyle())
    }}

  }
E.Coms
  • 11,065
  • 2
  • 23
  • 35
  • 1
    sounds like a reasonable work around but then the style is not really just a style anymore and can't be easily replaced with other styles. at this point might as well use a custom button instead of the builtin `Button` – hasen Dec 04 '19 at 23:26
  • 1
    bro what is your formatting – Div Apr 17 '23 at 17:47
0

we have to use @Environment(.isEnabled) it should check button is enabled or disabled and you can also change the background colour if disabled

import Foundation
import SwiftUI

struct ButtonStyling:ButtonStyle {

    @Environment(\.isEnabled) private var isEnabled: Bool
    
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .foregroundColor(.white)
            .bold()
            .padding(.all,5)
            .padding(.horizontal)
            .frame(height: 50)
            .frame(maxWidth:.infinity)
            .background(isEnabled ? Color("AppButtonBackground"): Color("AppButtonBackground").opacity(0.5))
            .cornerRadius(10)
    }
}

And use this like

private var isFormValid: Bool {
          !packageName.isEmpty && duration > 0
      }

     Button {
                                    
                                } label: {
                                    Text("Save")
                                        .buttonStyle(ButtonStyling())
                                        .disabled(!isFormValid)
                                }
    
    ```