'How to set custom highlighted state of SwiftUI Button

I have a Button. I want to set custom background color for highlighted state. How can I do it in SwiftUI?

enter image description here

Button(action: signIn) {
    Text("Sign In")
}
.padding(.all)
.background(Color.red)
.cornerRadius(16)
.foregroundColor(.white)
.font(Font.body.bold())


Solution 1:[1]

As far as I can tell, theres no officially supported way to do this as of yet. Here is a little workaround that you can use. This produces the same behavior as in UIKit where tapping a button and dragging your finger off of it will keep the button highlighted.

struct HoverButton<Label>: View where Label: View {

    private let action: () -> ()

    private let label: () -> Label

    init(action: @escaping () -> (), label: @escaping () -> Label) {
        self.action = action
        self.label = label
    }

    @State private var pressed: Bool = false

    var body: some View {
        Button(action: action) {
            label()
                .foregroundColor(pressed ? .red : .blue)
                .gesture(DragGesture(minimumDistance: 0.0)
                    .onChanged { _ in self.pressed = true }
                    .onEnded { _ in self.pressed = false })
        }    
    }
}

Solution 2:[2]

I was looking for a similar functionality and I did it in the following way.

I created a special View struct returning a Button in the style I need, in this struct I added a State property selected. I have a variable named 'table' which is an Int since my buttons a round buttons with numbers on it

struct TableButton: View {
    @State private var selected = false

    var table: Int

    var body: some View {
        Button("\(table)") {
            self.selected.toggle()
        }
        .frame(width: 50, height: 50)
        .background(selected ? Color.blue : Color.red)
        .foregroundColor(.white)
        .clipShape(Circle())
    }
}

Then I use in my content View the code

HStack(spacing: 10) {
  ForEach((1...6), id: \.self) { table in
    TableButton(table: table)
  }
}

This creates an horizontal stack with 6 buttons which color blue when selected and red when deselected.

I am not a experienced developer but just tried all possible ways until I found that this is working for me, hopefully it is useful for others as well.

Solution 3:[3]

This is for the people who are not satisfied with the above solutions, as they raise other problems such as overlapping gestures(for example, it's quite hard to use this solution in scrollview now). Another crutch is to create a custom button style like this

struct CustomButtonStyle<Content>: ButtonStyle where Content: View {
    
    var change: (Bool) -> Content
    
    func makeBody(configuration: Self.Configuration) -> some View {
        return change(configuration.isPressed)
    }
}

So, we should just transfer the closure which will return the state of the button and create the button based on this parameter. It will be used like this:

struct CustomButton<Content>: View where Content: View {
    var content:  Content
    
    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }
    var body: some View {
        Button(action: { }, label: {
            EmptyView()
        })
            .buttonStyle(CustomButtonStyle(change: { bool in
                Text("\(bool ? "yo" : "yo2")")
            }))
   }
} 

Solution 4:[4]

Okey let me clear everything again. Here is the exact solution

  1. Create the below button modifier.
    struct StateableButton<Content>: ButtonStyle where Content: View {
        var change: (Bool) -> Content
        
        func makeBody(configuration: Configuration) -> some View {
            return change(configuration.isPressed)
        }
    }
  1. Then use it like below one
    Button(action: {
        print("Do something")
    }, label: {

        // Don't create your button view in here
        EmptyView()
    })
    .buttonStyle(StateableButton(change: { state in

        // Create your button view in here
        return HStack {
            Image(systemName: "clock.arrow.circlepath")
            Text(item)
            Spacer()
            Image(systemName: "arrow.up.backward")
        }
        .padding(.horizontal)
        .frame(height: 50)
        .background(state ? Color.black : Color.clear)
        
    }))

Solution 5:[5]

You need to define a custom style that can be used to provide the two backgrounds for normal and highlighted states:

Button(action: {
     print("action")
}, label: {
     Text("My Button").padding()
})
.buttonStyle(HighlightableButtonStyle(normal: { Color.red },
                                      highlighted: { Color.green }))

// Custom button style
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
struct HighlightableButtonStyle<N, H>: ButtonStyle where N: View, H: View {
    
    private let alignment: Alignment
    private let normal: () -> N
    private let highlighted: () -> H
    
    init(alignment: Alignment = .center, @ViewBuilder normal: @escaping () -> N, @ViewBuilder highlighted: @escaping () -> H) {
        self.alignment = alignment
        self.normal = normal
        self.highlighted = highlighted
    }
    
    func makeBody(configuration: Configuration) -> some View {
        return ZStack {
            if configuration.isPressed {
                configuration.label.background(alignment: alignment, content: normal)
            }
            else {
                configuration.label.background(alignment: alignment, content: highlighted)
            }
        }
    }
}

Solution 6:[6]

Thanks @Chris:

use lambda-parameters initialized:

dct[key]=lambda x,y,xx=params[key][0],yy=params[key][1]: myfun(xx*x,yy*y)

this resolved my question!

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Marcela
Solution 2 Nico van der Linden
Solution 3
Solution 4 Ozan Honamlioglu
Solution 5 Alexander Volkov
Solution 6 Friedrich Ka