How it works
HoverPhase describes where the pointer is in relation to a view at a single moment during a continuous-hover interaction. Rather than reporting a simple boolean enter/exit, it captures the pointer's location while it remains over the view and signals the moment it leaves, so you can drive position-aware effects such as spotlights, tooltips, or magnification. Reach for HoverPhase whenever you need the pointer's coordinates throughout a hover, not just the fact that a hover began or ended.
Receive phases from onContinuousHover(perform:)
HoverPhase is delivered by the onContinuousHover modifier, which calls your closure each time the pointer moves over the view or finally exits it. In the example, .onContinuousHover { phase in self.phase = phase } stores every incoming HoverPhase into @State so the view can react to it.
Read the pointer location from the active case
The active(_:) case carries the pointer's current position as a CGPoint in the view's local coordinate space, letting you follow the cursor as it travels. The example pattern-matches case .active(let point) and reads point.x and point.y to build its label.
Detect the exit with the ended case
When the pointer leaves the view, HoverPhase reports the ended case, which carries no associated value and is your cue to tear down or reset any hover-driven state. Here case .ended returns the "ended" label, and the isActive helper returns false for it so the rectangle falls back to .gray.
Store phase as @State to drive the view
Because HoverPhase is an ordinary value you can hold and compare, keeping it in state lets SwiftUI recompute the body as the phase changes. The example seeds @State private var phase: HoverPhase = .ended and derives both isActive and label from it, switching the fill between .blue and .gray.
Handle future cases with @unknown default
HoverPhase is a non-frozen enum, so SwiftUI may add cases in later releases; switching over it should include an @unknown default to stay forward-compatible. The label computed property does exactly this, returning "unknown" for any phase it doesn't explicitly handle.
Example & preview
Press Run live & edit to compile it in your browser — then edit the Swift on the left and the preview re-renders live.
struct HoverPhaseDemo: View {
@State private var phase: HoverPhase = .ended
var body: some View {
VStack(spacing: 12) {
Text("Hover over the box")
.font(.headline)
Text(label)
.font(.title3)
.foregroundStyle(.secondary)
RoundedRectangle(cornerRadius: 12)
.fill(isActive ? .blue : .gray)
.frame(width: 160, height: 100)
.onContinuousHover { phase in
self.phase = phase
}
}
.padding()
}
private var isActive: Bool {
if case .active = phase { return true }
return false
}
private var label: String {
switch phase {
case .active(let point):
return "active at (\(Int(point.x)), \(Int(point.y)))"
case .ended:
return "ended"
@unknown default:
return "unknown"
}
}
}