Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing the color of a button in SwiftUI based on disabled or not

Tags:

swift

swiftui

I have a textfield with a send button that's a systemImage arrow. I want the foreground color of the image to change depending on whether the textField is empty or not. (I.e. the button is gray, and it is disabled if the textfield is empty. It's blue if the count of the textfield text is > 1).

I have a workaround that's not perfect:

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

This almost works, and it does change the color of the image if the text is > 1 in length. However, due to the change in state you're kicked out of editing the textfield after one character is typed, and you'll need to select the textfield again to continue typing. Is there a better way to do this with the .disabled modifier?

like image 648
mg_berk Avatar asked Jun 19 '19 21:06

mg_berk


People also ask

How do I change the color of a button in SwiftUI?

To change a button's text color in SwiftUI we need to use . foregroundColor on our text view. In the above code it is almost the same as the simple button, but we used a modifier to change the foregroundColor to green. This needs to be on the Text and not on the Button view.

How do I disable SwiftUI?

SwiftUI lets us disable any part of its forms or even the whole form, all by using the disabled() modifier. This takes a single Boolean that defines whether the element should be disabled or not. The form element's style automatically gets updated to reflect its status – buttons and toggles get grayed out, for example.

How do I add a button in SwiftUI?

SwiftUI's button is similar to UIButton , except it's more flexible in terms of what content it shows and it uses a closure for its action rather than the old target/action system. To create a button with a string title you would start with code like this: Button("Button title") { print("Button tapped!") }


1 Answers

I guess you want this:

demo

You can add a computed property for the button color, and pass the property to the button's foregroundColor modifier. You can also use a single padding modifier around the HStack instead of separate paddings on its subviews.

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 = ""     } } 

However, you shouldn't use the foregroundColor modifier at all here. You should use the accentColor modifier. Using accentColor has two benefits:

  • The Image will automatically use the environment's accentColor when the Button is enabled, and gray when the Button is disabled. You don't have to compute the color at all.

  • You can set the accentColor in the environment high up in your View hierarchy, and it will trickle down to all descendants. This makes it easy to set a uniform accent color for your whole interface.

In the following example, I put the accentColor modifier on the HStack. In a real app, you would probably set it on the root view of your entire app:

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 = ""     } } 

Also, Matt's idea of extracting the send button into its own type is probably smart. It makes it easy to do nifty things like animating it when the user clicks it:

button animation demo

Here's the code:

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 } 
like image 83
rob mayoff Avatar answered Sep 22 '22 08:09

rob mayoff