SwiftUI Sensory Feedback

In iOS 17, Apple added a range of sensory feedback view modifiers to SwiftUI removing the need to rely on UIKit.

Haptic Feedback

Before iOS 17 if you wanted to give haptic feedback to a user from a SwiftUI view you’d use one of the UIKit (or AppKit) feedback generators. For example, using the selection feedback generator:

struct ListView: View {
  @Binding var store: Store
  let generator = UISelectionFeedbackGenerator()
  
  var body: some View {
    List(store.items, selection: $store.selection) { ... }
    .onChange(of: store.selection) { oldValue, newValue in
      if newValue != nil {
        generator.selectionChanged()
      }
    }
  }
}

In iOS 17, Apple added a range of sensory feedback view modifiers directly to SwiftUI to play haptic and/or audio feedback.

SwiftUI Sensory Feedback (iOS 17)

The sensory feedback modifiers are all trigger based. The trigger needs to be an equatable type. There are three variations on the sensory feedback view modifier:

Trigger On Value Changes

struct ListView: View {
  @Binding var store: Store
    
  var body: some View {
    List(store.items, selection: $store.selection) { ... }
    .sensoryFeedback(.selection, trigger: store.selection)
  }
}

The first parameter to the view modifier is a SensoryFeedback type. Not all feedback types are available on all platforms (see below). The feedback plays when the trigger values changes.

Trigger With Condition Closure

For more control over when you trigger the feedback use the condition closure version of the view modifier. For example, to only play the selection feedback when the selection changes to a non-nil value:

.sensoryFeedback(.selection, trigger: store.selection) {
  oldValue, newValue in
    newValue != nil
}

The condition closure receives the old and new values of the monitored trigger value. In the closure you return a bool indicating if the feedback should play or not.

Trigger With Feedback Closure

For control over what feedback plays use the feedback closure version of the view modifier. For example, to give warning or error feedback based on an error code:

// @State private var errorCode: Int = 0

.sensoryFeedback(trigger: errorCode) { oldValue, newValue in
    switch newValue {
    case 1: .warning
    case 2: .error
    default: nil
    }
}

In this case, you return the required feedback from the closure or nil if you don’t want any feedback.

Platform Support

Not all feedback options are available on all platforms. Here’s a list, as best as I can tell, of what’s available on each platform. Note that the iPad doesn’t support haptic feedback.

watchOS only

  • start: Activity started
  • stop: Activity stopped

watchOS and iOS

  • decrease: Important value decreased below a significant threshold
  • increase: Important value increased above a significant threshold

Note: The documentation claims that decrease/increase are watchOS only but I find they also work on iOS.

  • selection: A UI element’s values are changing.
  • success: A task completed successfully
  • warning: A task produced a warning
  • error: A task produced an error
  • impact: A physical impact when UI elements collide.

Note: The impact feedback has two variations that let you specify either the weight (light, medium, heavy) or the flexibility (rigid, soft, solid) of the elements colliding. In both cases you can also change the intensity (default is 1.0):

// Default impact feedback
.impact()

// Impact with flexibility and increases intensity
.impact(flexibility: .rigid, intensity: 2.0)

// Impact with weight and increases intensity
.impact(weight: .heavy, intensity: 2.0)

macOS only

  • alignment: a dragged item is in alignment with another item.
  • levelChange: indicates movement between discrete levels of pressure. For example, holding a fast-forward button.

HIG Best Practises

The Apple Human Interface Guidelines (see below) have a useful list of best practices for using haptic feedback. In summary:

  • Use the predefined feedback styles for their intended meanings.
  • Don’t overuse them.
  • Allow people to turn haptics off in your app.
  • Be consistent.

Learn More