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)
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.
The default hover effect depends on the ButtonStyle
used for the button.
The button style in your example is .automatic
and the default effect is .highlight
.
None of the other built-in button styles seem to have a default hover effect, but you can turn an effect on by applying a .hoverEffect
modifier.
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.
If you choose .plain
or .borderless
as button style then you will probably want to add padding too.
If you want the hover effect to be shown as soon as the mouse cursor enters the frame, as opposed to when it reaches the text of the label, apply .contentShape(Rectangle())
to the padded button.
Using this approach with your example:
Button("B") {}
.buttonStyle(.borderless)
.padding()
.contentShape(Rectangle())
.hoverEffect(.highlight)
.border(.red)
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:
.onHover
callback..transition
can be used to add a bit of animation.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
}
}
}