Published on August 20, 2020
Byte of the Day: Haptics in SwiftUI
Haptic feedback adds a layer of polish to your app. It can also serve as an accessibility feature, providing a physical confirmation when a task fails or completes.
In UIKit, you'd typically use one of the UIFeedbackGenerator
subclasses. But in SwiftUI there is no native support for these.
One solution is to create an object that wraps this functionality and then expose it to views in the SwiftUI environment. Let's create this type:
struct Haptics {
/// Performs a success haptic feedback.
func success() {
let feedbackGenerator = UINotificationFeedbackGenerator()
feedbackGenerator.prepare()
feedbackGenerator.notificationOccurred(.success)
}
}
Now, let's expose it to views. In SwiftUI, the environment is a collection of values that are propagated down the view hierarchy. It is a good place to provide objects that pertain to the device, your app or your application stack.
This collection is keyed by types that conform to the EnvironmentKey
protocol, that requires you to provide a default value. In this case, we'll instantiate a Haptics
instance. We use an enum
since it's a namespace-like structure and we don't need an init
for it.
enum HapticsEnvironmentKey: EnvironmentKey {
static let defaultValue = Haptics()
}
The last step is to make this key available in the environment values collection, so that it can be used with the @Environment
property wrapper in views. This is done by creating a computed property on EnvironmentValues
.
extension EnvironmentValues {
var haptics: Haptics {
get { self[HapticsEnvironmentKey.self] }
set { self[HapticsEnvironmentKey.self} = newValue }
}
}
You can now use the haptics
property on the environment values in your views by using it as a key-path with @Environment
:
struct ContentView: View {
@Environment(\.haptics) private var haptics
var body: some View {
Button(action: buttonTapped) {
Text("Tap Me!")
}
}
private func buttonTapped() {
haptics.success()
}
}
Next Steps
- You can modify the
Haptics
type to include more feedback types, such as impact or selection. - You can create a publisher in your view model that emits
Void
when a task completes, and use theonReceive(_:perform:)
method to perform haptics.
The source code of the example is available on GitHub. Don't hesitate to reach out with questions!