SwiftUI按钮样式 - 如何检查按钮是否已禁用或启用?

52

根据我的理解,在SwiftUI中样式化一个按钮,你需要扩展ButtonStyle并实现func makeBody(configuration: Self.Configuration) -> some View方法,在其中开始对configuration.label进行修改,这是一个指向Button视图的引用。

此外,除了label字段之外,ButtonStyle.Configuration还有一个布尔字段isPressed,但这似乎就是全部。

那么我该如何检查按钮的启用或禁用状态呢?

例如,我想在按钮周围绘制边框,并且我希望如果按钮启用则边框为蓝色,如果禁用则为灰色。

我的第一个猜测是这样的:

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

但是没有isEnabled字段。

苹果提供的PlainButtonStyle显然可以访问此信息,因为在声明它的头文件中的文档注释中说:“该样式可以应用视觉效果以指示按钮的按下、聚焦或启用状态。”

/// 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 {
.....

有没有办法访问这些信息?

编辑:

在您建议关闭此问题之前,请阅读以下内容:

一段时间以前也有一个类似的问题,但那个问题没有指定任何上下文。

这次我有一个具体的上下文:创建一个通用可重用的按钮样式。

那里提供的答案不足够。我不能期望人们将禁用状态传递给样式构造函数。这会导致太多的重复劳动。

你能想象如果人们必须这样编写代码:

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

很明显这是不可取的。

同时很明显,苹果提供的样式可以在不需要人们再次传递禁用状态的情况下访问该信息。

如果您关闭了该问题,则将永远防止任何人在 StackOverflow 上为此问题提供有用的答案。

请重新考虑在提出关闭建议之前。

编辑2:

我猜如果您可以获取Button视图的EnvironmentValues.isEnabled,那么它可能是正确的值。

因此,另一个提问的方式是:在 SwiftUI 中是否有可能获取您没有自己定义的视图的环境?


这个回答解决了你的问题吗?如何知道SwiftUI按钮是否启用/禁用? - Hitesh Surani
这段程序相关的内容意味着似乎没有办法实现,但显然内置的PlainButtonStyle却知道实现方法? - hasen
2
正如预期的那样,警方开始进行突袭行动。 - hasen
1
在创建一个新的结构体NewButtonStyle: ButtonStyle时,似乎无法获取按钮的@Environment(.isEnabled)var isEnabled。只有在创建自定义按钮时才能访问isEnabled的正确值... - Victor Sanchez
7个回答

68

我通过这篇博客找到了答案:https://swiftui-lab.com/custom-styling/

你可以通过创建一个包装视图并在样式结构体中使用它来从环境中获取启用状态:

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)
        }
    }
}

此示例演示如何获取状态并使用它来更改按钮的外观。如果按钮已被禁用,则将其按钮文本颜色更改为红色;如果启用,则将其更改为绿色。


1
我不得不使用@SwiftUI.Environment,因为我遇到了错误“无法将枚举'环境(Environment)'用作属性”。 - 93sauu
随着时间的推移和Swift语言的变化,我预计这里的一些关键字也将不得不改变。 - hasen
13
对于最近加入的人,不需要再使用这样的装置了,你只需将@Environment行放入ButtonStyle结构中即可生效。 - Michel Donais
@MichelDonais,你能提供一段代码片段吗? - Zonker.in.Geneva
@Zonker.in.Geneva,请查看Jonathan在https://dev59.com/JlIH5IYBdhLWcg3wjfm4#69134893的答案。 - Michel Donais
显示剩余2条评论

34

您可以直接通过环境访问ButtonStyle中的启用值,例如:

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))
            
    }
    
}

2
如果你要支持iOS 13.0,那就不行。 - user3344239

10

稍微调整了一下@hasen的答案,使事情更具可配置性。

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)
        }
    }
}

使用方法

       Button(action: {}) {
            Text("Hello")
        }.buttonStyle(MyButtonStyle(foreground: .white, background: .green))

8
回答您的问题:是的,可能会得到您没有传递自己的Environment值。 SwiftUI在很大程度上使用Environment,许多View依赖于它,而您甚至不知道它。 ButtonStyle也可以访问Environment,所以您可以轻松实现您想要的内容:
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)
    }
}

这在iOS15上可以工作,但在iOS14.3上无法工作。看起来是操作系统中的一个错误。你有这个问题吗? - Ricardo
1
这感觉就像是最好的答案。 - Peter Suwara

5

另一个选择是将disabled布尔值传递给您的ButtonStyle,然后在标签上使用allowsHitTesting修饰符来有效地禁用按钮:

struct MyButtonStyle: ButtonStyle {
    let disabled: Bool

    func makeBody(configuration: Self.Configuration) -> some View {
        configuration
            .label
            .allowsHitTesting(!disabled)
    }
}

迄今为止最佳解决方案 - WalterF

1
您可以将禁用状态合并到buttonStyle中,这样您就不会在应用程序中重复使用它。
  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())
    }}

  }

1
听起来像是一个合理的解决方法,但这样风格就不再只是风格了,也不能轻易地用其他风格替换。此时最好使用自定义按钮而不是内置的 Button - hasen
1
兄弟,你的格式是什么? - Div

0

我们必须使用 @Environment(.isEnabled) 来检查按钮是否启用或禁用,如果禁用,您还可以更改背景颜色。

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)
    }
}

并像这样使用

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接