swiftui

How to change the size of onHover background of a button?


When using a stylus or a mouse on the iPad, hovering over a text button causes it to get highlighted. Unfortunately, the highlighted area does not match the size of the button. How can I make hover-highlighted area to match the size of the button?

Here is an image to demonstrate it (notice the pale gray hover and red border of a real button) Hovered button

And here is the min.code to see for yourself (run in the simulator and use "Capture Pointer" (looks like a "sun with rays" button)):

struct SOQuestionView: View {
    var body: some View {
        NavigationStack {
            Text("Hello, World!")
                .toolbar {
                    ToolbarItemGroup(placement: .principal) {
                        Button("B") {}
                            .border(Color.red)
                    }
                }
        }
    }
}

P.S. Why do I need it? - I want to give background to that button. But when I hover over it, you guessed it - the hover background doesn’t align with the actual button's background.


Solution

  • The default hover effect depends on the ButtonStyle used for the button.

    So one workaround for the mis-fitting highlight shape is to use a button style with a more defined size and apply .hoverEffect(.highlight) to it.

    Using this approach with your example:

    Button("B") {}
        .buttonStyle(.borderless)
        .padding()
        .contentShape(Rectangle())
        .hoverEffect(.highlight)
        .border(.red)
    

    Screenshot

    If you want to use the same modifiers for other buttons too, it would be simpler to define a custom ButtonStyle that encapsulates this styling and behavior:

    struct MyHoverButtonStyle: ButtonStyle {
        func makeBody(configuration: Configuration) -> some View {
            configuration.label
                .padding()
                .contentShape(Rectangle())
                .foregroundStyle(.tint)
                .hoverEffect(.highlight)
        }
    }
    
    Button("B") {}
        .buttonStyle(MyHoverButtonStyle())
        .border(.red)
    

    With a custom button style, you could also apply your own styling when hover is in effect:

    struct MyHoverButtonStyle: ButtonStyle {
        @State private var withHover = false
    
        func makeBody(configuration: Configuration) -> some View {
            configuration.label
                .padding()
                .foregroundStyle(.tint)
                .background {
                    if withHover {
                        RoundedRectangle(cornerRadius: 10)
                            .fill(.yellow.opacity(0.5))
                            .scaleEffect(1.1)
                            .transition(.scale.animation(.easeInOut(duration: 0.1)))
                    }
                }
                .onHover { isHovering in
                    withHover = isHovering
                }
        }
    }
    

    Screenshot