根据是否已禁用在SwiftUI中更改按钮的颜色

时间:2019-06-19 21:11:33

标签: swift swiftui

我有一个带有发送按钮的文本字段,该按钮是systemImage箭头。我希望图像的前景色根据textField是否为空而改变。 (即,该按钮为灰色,如果文本字段为空,则禁用该按钮。如果文本字段文本的计数为> 1,则为蓝色)。

我有一个不完善的解决方法:


if chatMessageIsValid {
                Spacer()
                HStack {
                    TextField($chatMessage, placeholder: Text("Reply"))
                        .padding(.leading, 10)
                        .textFieldStyle(.roundedBorder)
                    Button(action: sendMessage) {
                        Image(systemName: "arrow.up.circle")
                            .foregroundColor(Color.blue)
                            .padding(.trailing, 10)
                        }.disabled(!chatMessageIsValid)
                }
            } else {
                Spacer()
                HStack {
                    TextField($chatMessage, placeholder: Text("Reply"))
                        .padding(.leading, 10)
                        .textFieldStyle(.roundedBorder)
                    Button(action: sendMessage) {
                        Image(systemName: "arrow.up.circle")
                            .foregroundColor(Color.gray)
                            .padding(.trailing, 10)
                        }.disabled(!chatMessageIsValid)
                }
            }

这几乎可以起作用,并且如果文本的长度> 1,它的确会更改图像的颜色。但是,由于状态的改变,您在键入一个字符后将无法再编辑文本字段,因此您需要再次选择文本字段才能继续输入。是否可以使用.disabled修饰符更好的方法?

5 个答案:

答案 0 :(得分:4)

我想你想要这个:

demo

您可以为按钮颜色添加一个计算属性,并将该属性传递给按钮的foregroundColor修饰符。您还可以在padding周围使用单个HStack修饰符,而不是在其子视图上使用单独的padding

struct ContentView : View {
    @State var chatMessage: String = ""

    var body: some View {
        HStack {
            TextField($chatMessage, placeholder: Text("Reply"))
                .textFieldStyle(.roundedBorder)
            Button(action: sendMessage) {
                Image(systemName: "arrow.up.circle")
                    .foregroundColor(buttonColor)
            }
                .disabled(!chatMessageIsValid)
        }
            .padding([.leading, .trailing], 10)
    }

    var chatMessageIsValid: Bool {
        return !chatMessage.isEmpty
    }

    var buttonColor: Color {
        return chatMessageIsValid ? .accentColor : .gray
    }

    func sendMessage() {
        chatMessage = ""
    }
}

但是,您完全不应该在这里使用foregroundColor修饰符。您应该使用accentColor修饰符。使用accentColor有两个好处:

  • 在启用Image的情况下,accentColor将自动使用环境的Button,而在禁用Button的情况下将自动使用灰色。您根本不需要计算颜色。

  • 您可以在accentColor层次结构中较高的环境中设置View,它会滴落到所有后代。这样可以轻松为整个界面设置统一的强调色。

在下面的示例中,我将accentColor修饰符放在HStack上。在真实的应用程序中,您可能会在整个应用程序的根视图上进行设置:

struct ContentView : View {
    @State var chatMessage: String = ""

    var body: some View {
        HStack {
            TextField($chatMessage, placeholder: Text("Reply"))
                .textFieldStyle(.roundedBorder)
            Button(action: sendMessage) {
                Image(systemName: "arrow.up.circle")
            }
                .disabled(!chatMessageIsValid)
        }
            .padding([.leading, .trailing], 10)
            .accentColor(.orange)
    }

    var chatMessageIsValid: Bool {
        return !chatMessage.isEmpty
    }

    func sendMessage() {
        chatMessage = ""
    }
}

此外,马特(Matt)将发送按钮提取为自己的类型的想法可能很聪明。它使用户可以轻松完成一些精美的事情,例如在用户单击时对其进行动画处理:

button animation demo

代码如下:

struct ContentView : View {
    @State var chatMessage: String = ""

    var body: some View {
        HStack {
            TextField($chatMessage, placeholder: Text("Reply"))
                .textFieldStyle(.roundedBorder)
            SendButton(action: sendMessage, isDisabled: chatMessage.isEmpty)
        }
            .padding([.leading, .trailing], 10)
            .accentColor(.orange)
    }

    func sendMessage() {
        chatMessage = ""
    }
}

struct SendButton: View {
    let action: () -> ()
    let isDisabled: Bool

    var body: some View {
            Button(action: {
                withAnimation {
                    self.action()
                    self.clickCount += 1
                }
            }) {
                Image(systemName: "arrow.up.circle")
                    .rotationEffect(.radians(2 * Double.pi * clickCount))
                    .animation(.basic(curve: .easeOut))
            }
                .disabled(isDisabled)
    }

    @State private var clickCount: Double = 0
}

答案 1 :(得分:4)

如果要在禁用(或任何其他)状态下进一步自定义按钮,可以将变量添加到自定义ButtonStyle结构中。

Swift 5.1

struct CustomButtonStyle: ButtonStyle {
    var disabled = false
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
        .background(disabled ? .red : .blue)
    }
}

// SwiftUI
@State var isDisabled = true
var body: some View {
    Button().buttonStyle(CustomButtonStyle(disabled: self.isDisabled))
}

答案 2 :(得分:3)

尝试一下

Spacer()
HStack {
    TextField($chatMessage, placeholder: Text("Reply"))
        .padding(.leading, 10)
        .textFieldStyle(.roundedBorder)
    Button(action: sendMessage) {
        Image(systemName: "arrow.up.circle")
            .foregroundColor(chatMessageIsValid ? Color.blue : Color.gray)
            .padding(.trailing, 10)
        }.disabled(!chatMessageIsValid)
}

我想,TextField失去焦点的原因是,在您的代码中,整个视图层次结构根据chatMessageIsValid的值而改变。 SwiftUI无法理解then块中的视图层次结构与else块中的视图层次结构几乎相同,因此它会完全重建它。

在此经过编辑的代码中,应该看到唯一改变的是图像的foregroundColor和按钮的disabled状态,而TextField保持不变。我认为,Apple在WWDC视频之一中也有类似的例子。

答案 3 :(得分:2)

所有修饰符参数可以是有条件的:

.foregroundColor(condition ? .red : .yellow)

条件可以是任何true / false

var condition: Bool { chatMessage.isEmpty } // can be used inline

您可以使用所需的任何其他条件

答案 4 :(得分:0)

使用这些各种解决方案,您可以使用\.isEnabled环境属性来代替创建自定义按钮样式或自己传递禁用的布尔值。

@Environment(\.isEnabled) private var isEnabled