NEW: Subscribe to Hacking with Swift+ and accelerate your learning! >>

Making iPhones vibrate with UINotificationFeedbackGenerator

Paul Hudson    @twostraws   

iOS comes with a number of options for generating haptic feedback, and they are all available for us to use in SwiftUI. In its simplest form, this is as simple as creating an instance of one of the subclasses of UIFeedbackGenerator then calling its play() method, but for more precise control over feedback you should first call its prepare() method to give the Taptic Engine chance to warm up.

Important: Warming up the Taptic Engine helps reduce the latency between us calling play() and the effect actually happening, but it also has a battery impact so the system will only stay ready for a second or two after you call prepare().

There are a few different subclasses of UIFeedbackGenerator we could use, but the one we’ll use here is UINotificationFeedbackGenerator because it provides success and failure haptics that are common across iOS. Now, we could add one central instance of UINotificationFeedbackGenerator to every ContentView, but that causes a problem: ContentView gets notified whenever a card has been removed, but isn’t notified when a drag is in progress, which means we don’t have the opportunity to warm up the Taptic Engine.

So, instead we’re going to give each CardView its own instance of UINotificationFeedbackGenerator so they can prepare and play them as needed. The system will take care of making sure the haptics are all neatly arranged, so there’s no chance of them somehow getting confused.

Add this new property to CardView:

@State private var feedback = UINotificationFeedbackGenerator()

Now find the self.removal?() line in the drag gesture of CardView, and change that whole closure to this:

if self.offset.width > 0 {
    self.feedback.notificationOccurred(.success)
} else {
    self.feedback.notificationOccurred(.error)
}

self.removal?()

That alone is enough to get haptics in our app, but there is always the risk that the haptic will be delayed because the Taptic Engine wasn’t ready. In this case the haptic will still play, but it could be up to maybe half a second late – enough to feel just that little bit disconnected from our user interface.

To improve this we need to call prepare() on our haptic a little before calling play(). It is not enough to call prepare() immediately before play(): doing so does not give the Taptic Engine enough time to warm up, so you won’t see any reduction in latency. Instead, you should call prepare() as soon as you know the haptic might be needed.

Now, there are two helpful implementation details that you should be aware of.

First, it’s OK to call prepare() then never call play() – the system will keep the Taptic Engine ready for a few seconds then just power it down again. If you repeatedly call prepare() and never call play() the system might start ignoring your prepare() calls until at least one play() has happened.

Second, it’s perfectly allowable to call prepare() many times before calling play() once – prepare() doesn’t pause your app while the Taptic Engine warms up, and also doesn’t have any real performance cost when the system is already prepared.

Putting these two together, we’re going to update our drag gesture so that prepare() is called whenever the gesture changes. This means it could be called a hundred times before play() is finally called, because it will get triggered every time the user moves their finger.

So, modify your onChanged() closure to this:

.onChanged { offset in
    self.offset = offset.translation
    self.feedback.prepare()
}

Now go ahead and give the app a try and see what you think – you should be able to feel two very different haptics depending on which direction you swipe.

Before we wrap up with haptics, there’s one thing I want you to consider. Years ago PepsiCo challenged mall shoppers to the “Pepsi Challenge”: drink a sip of one cola drink and a sip of another, and see which you prefer. The results found that more Americans preferred Pepsi than Coca Cola, despite Coke having a much bigger market share. However, there was a problem: people seemed to pick Pepsi in the test because Pepsi had a sweeter taste, and while that worked well in sip-size amounts it worked less well in the sizes of cans and bottles, where people actually preferred Coke.

The reason I’m saying this is because we added two haptic notifications to our app that will get played a lot. And while you’re testing out in small doses these haptics probably feel great – you’re making your phone buzz, and it can be really delightful. However, if you’re a serious user of this app then our haptics might hit two problems:

  1. The user might find them annoying, because they’ll happen once every two or three seconds depending on how fast they are.
  2. Worse, the user might become desensitized to them – they lose all usefulness either as a notification or as a little spark of delight.

So, now you’ve tried it for yourself I want you to think about how they should be used. If this were my app I would probably keep the failure haptic, but I think the success haptic could go – that one is likely to be triggered the most often, and it means when the failure haptic plays it feels a little more special.

Hacking with Swift is sponsored by Instabug

SPONSORED Are you tired of wasting time debugging your Swift app? Instabug’s SDK is here to help you minimize debugging time by providing you with complete device details, network logs, and reproduction steps with every bug report. All data is attached automatically, and it only takes a line of code to setup. Start your free trial now and get 3 months off exclusively for the Hacking with Swift Community.

Start your free trial!

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift (Vapor Edition) Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Server-Side Swift (Kitura Edition) Buy Beyond Code

Was this page useful? Let us know!

Average rating: 5.0/5

Link copied to your pasteboard.