Getting Started With Core Haptics

In this Core Haptics tutorial, you’ll learn how to create and play haptic patterns, synchronize audio with haptic events and create dynamic haptic patterns that respond to external stimuli. By Andrew Tetlaw.

Leave a rating/review
Download materials
Save for later
Share

Apple introduced Core Haptics in iOS 13. It’s a new API that can generate custom, high-resolution tactile and audio feedback, making vibrations and rumbles feel like an old-fashioned gimmick. Get ready to immerse yourself in a world of haptic experiences.

In this tutorial, you’ll enhance an app with new haptic experiences. You’ll learn how to:

  1. Create haptic patterns and play them.
  2. Synchronize audio with haptic events.
  3. Create dynamic haptic patterns that change in response to external stimuli.

This tutorial is fully hands-on!

Note: This intermediate-level tutorial assumes that you’re comfortable building an iOS app using Xcode and writing Swift. You’ll also need a device running iOS 13 that supports haptic feedback. This tutorial uses the game from the tutorial How To Make a Game Like Cut the Rope With SpriteKit, Snip The Vine, as the starting project. You should read that tutorial first if you’d like an introduction to SpriteKit.

Getting Started

Download the starter project using the Download Materials button at the top or bottom of this tutorial. Open the starter project. Build and run, and… you have a game.

Snip the vine game

You’ve got the cute graphics, your physics simulation and animations are slick and your sound effects put the player right in the jungle. But you have an intense feeling that something’s missing. You’re itching to feel the *snip*-iness as you slice the vine. And what about the *ker-splosh*-iness of that pineapple landing in the water and, of course, the *nom-nom-crunch-munch*-iness when the croc eats it?

It’s time to get tactile with Core Haptics!

Adding Your First Haptic Experience

The best way to start is to quickly make a simple haptic experience and test it. Your first haptic experience will add a little *snip* that players feel when they cut the vine.

The standard process of creating a haptic experience is to:

  1. Check device compatibility.
  2. Create a haptic engine object.
  3. Create a pattern of haptic events.
  4. Make a pattern player.
  5. Start the haptic engine.
  6. Play the pattern.
  7. Stop the haptic engine.

To avoid adding even more code to GameScene.swift, expand SnipTheVine ▸ Classes then find and open Haptics.swift. It’s an empty file for you to use. Add the following:

import CoreHaptics

class HapticManager {
  // 1
  let hapticEngine: CHHapticEngine

  // 2
  init?() {
    // 3
    let hapticCapability = CHHapticEngine.capabilitiesForHardware()
    guard hapticCapability.supportsHaptics else {
      return nil
    }

    // 4
    do {
      hapticEngine = try CHHapticEngine()
    } catch let error {
      print("Haptic engine Creation Error: \(error)")
      return nil
    }
  }
}

Here’s what this code does:

  1. hapticEngine holds a reference to CHHapticEngine.
  2. The initializer is failable, so you can check if it’s nil in the game scene and use that to indicate that haptics are unavailable.
  3. The first thing you do in the initializer is check if haptics are available. Call CHHapticEngine.capabilitiesForHardware() to obtain an object that you can use to test support with a simple check of supportsHaptics.
  4. Finally, you create an engine object. The CHHapticEngine initializer can throw, so you need to wrap it in a do/catch block and return nil if it throws an error.
Note: There are many reasons why haptics could be unavailable on a device, so you need to use do-catch blocks for a lot of the API. This also means you need to make sure you have a fallback for any haptic experience.

Now, push on to get to the point where you can test your first haptic as soon as possible.

Still in Haptics.swift, add this extension to the class:

extension HapticManager {
  private func slicePattern() throws -> CHHapticPattern {
    let slice = CHHapticEvent(
      eventType: .hapticContinuous,
      parameters: [
        CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.35),      
        CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.25)
      ],
      relativeTime: 0,
      duration: 0.25)

    let snip = CHHapticEvent(
      eventType: .hapticTransient,
      parameters: [
        CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0),
        CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0)
      ],
      relativeTime: 0.08)

    return try CHHapticPattern(events: [slice, snip], parameters: [])
  }
}

slicePattern() returns your first Core Haptics pattern. It creates two haptic events and a haptic pattern that uses them. You’ll explore the details of events and patterns shortly, but for now, forge on!

Finally, add a method to HapticManager to play the pattern:

func playSlice() {
  do {
    // 1
    let pattern = try slicePattern()
    // 2
    try hapticEngine.start()
    // 3
    let player = try hapticEngine.makePlayer(with: pattern)
    // 4
    try player.start(atTime: CHHapticTimeImmediate)
    // 5
    hapticEngine.notifyWhenPlayersFinished { _ in
      return .stopEngine
    }
  } catch {
    print("Failed to play slice: \(error)")
  }
}

This code does the following:

  1. You call slicePattern() to, as the name implies, grab your haptic slice pattern.
  2. Then you call start() on the haptic engine.
  3. Make a pattern player with your slice pattern.
  4. Next, play the pattern, calling start(atTime:) with CHHapticTimeImmediate to play it immediately.
  5. Call notifyWhenPlayersFinished(finishedHandler:) and return .stopEngine to stop the engine once it finishes playing.

OK, you’re nearly there, but you need to make some updates to GameScene.swift. Start by opening the file and adding the following property at the top of the class:

private var hapticManager: HapticManager?

Then add the following to the top of didMove(to:):

hapticManager = HapticManager()

In checkIfVineCut(withBody:), add this line above the line where you run the slice sound action:

hapticManager?.playSlice()

Build and run and slice that vine! Can you feel the gameplay improving already?

Exploring the Events That Make up the Pattern

OK, that was a lot all at once. Now, take a moment for a deeper look at what you did there.

Focusing on the pattern, you can see that it’s made up of events:

let slice = CHHapticEvent(
  eventType: .hapticContinuous,
  parameters: [
    CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.35),      
    CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.25)
  ],
  relativeTime: 0,
  duration: 0.5)

let snip = CHHapticEvent(
  eventType: .hapticTransient,
  parameters: [
    CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0),
    CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0)
  ],
  relativeTime: 0.08)

A haptic event, represented by CHHapticEvent, can be one of two types: .hapticTransient or .hapticContinuous. A transient event is instantaneous, like a single drum beat, and a continuous event is like a rumble. You can also create audio events, but more on that later.

Each event has two properties that control the haptic sensation that CHHapticEventParameter represents. Each has a parameter ID and a value between 0 and 1.

A parameter with the ID .hapticIntensity represents the strength of the sensation; the higher the value, the stronger it is. A parameter with the ID .hapticSharpness represents a physical quality that, at the high end of the scale, creates a precise mechanical feel. At the low end, it has a more rounded, organic feel.

The event’s relativeTime represents the number of seconds from the start of the pattern where the event occurs. For continuous events, there’s also a duration property to define how long the event plays.

Your first haptic effect is a low, soft rumble for 0.25 seconds starting immediately, with a sharp, intense beat that occurs 0.08 seconds from the start. You can represent the pattern in a diagram:

Haptic event timing graph

Next, you’ll learn how you can use haptic events more efficiently.