UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

How to play custom vibrations using Core Haptics

Swift version: 5.6

Paul Hudson    @twostraws   

Core Haptics lets us define a variety of vibrations and sound effects that trigger with precise timing and behaviors, all powered by the iPhone’s Taptic Engine. These behaviors are somewhat hard to define with words, and are best felt rather than described, but the words Apple uses are things like “intensity” (the relative strength of the vibration) and “sharpness” (whether it’s a dull tap or a more precise one).

To try it out for yourself, first import Core Haptics, then create a property to store an instance of the main Core Haptics engine:

var engine: CHHapticEngine?

Before you try to create an instance of that engine, you should make sure haptics are supported on the current device using code like this:

guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }

But if that passes you’re safe to create and start your haptic engine. This might be inside viewDidLoad(), for example:

guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }

do {
    engine = try CHHapticEngine()
    try engine?.start()
} catch {
    print("There was an error creating the engine: \(error.localizedDescription)")
}

It’s not required, but at the same time you might also want to assign closures to the stoppedHandler and resetHandler properties of your engine, like this:

// The engine stopped; print out why
engine?.stoppedHandler = { reason in
    print("The engine stopped: \(reason)")
}

// If something goes wrong, attempt to restart the engine immediately
engine?.resetHandler = { [weak self] in
    print("The engine reset")

    do {
        try self?.engine?.start()
    } catch {
        print("Failed to restart the engine: \(error)")
    }
}

Finally you’re all set to start making haptic effects. For example, this creates one strong, sharp tap whenever you touch the screen:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }

    let intensity = CHHapticEventParameter(parameterID: .hapticIntensity, value: 1)
    let sharpness = CHHapticEventParameter(parameterID: .hapticSharpness, value: 1)
    let event = CHHapticEvent(eventType: .hapticTransient, parameters: [intensity, sharpness], relativeTime: 0)

    do {
        let pattern = try CHHapticPattern(events: [event], parameters: [])
        let player = try engine?.makePlayer(with: pattern)
        try player?.start(atTime: 0)
    } catch {
        print("Failed to play pattern: \(error.localizedDescription).")
    }
}

For something more exciting you can create a series of events and pass in various values for their relativeTime so they either overlap or play in a sequence.

For example, this creates a series of taps, starting strong and sharp and fading away to weak and dull over a second:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }

    var events = [CHHapticEvent]()

    for i in stride(from: 0, to: 1, by: 0.1) {
        let intensity = CHHapticEventParameter(parameterID: .hapticIntensity, value: Float(1 - i))
        let sharpness = CHHapticEventParameter(parameterID: .hapticSharpness, value: Float(1 - i))
        let event = CHHapticEvent(eventType: .hapticTransient, parameters: [intensity, sharpness], relativeTime: i)
        events.append(event)
    }

    do {
        let pattern = try CHHapticPattern(events: events, parameters: [])
        let player = try engine?.makePlayer(with: pattern)
        try player?.start(atTime: 0)
    } catch {
        print("Failed to play pattern: \(error.localizedDescription).")
    }
}

And this taps out the Morse code for SOS (...---...) on the Taptic engine by mixing transient events (brief taps) with continuous events (long buzzes over a period of time):

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }

    let short1 = CHHapticEvent(eventType: .hapticTransient, parameters: [], relativeTime: 0)
    let short2 = CHHapticEvent(eventType: .hapticTransient, parameters: [], relativeTime: 0.2)
    let short3 = CHHapticEvent(eventType: .hapticTransient, parameters: [], relativeTime: 0.4)
    let long1 = CHHapticEvent(eventType: .hapticContinuous, parameters: [], relativeTime: 0.6, duration: 0.5)
    let long2 = CHHapticEvent(eventType: .hapticContinuous, parameters: [], relativeTime: 1.2, duration: 0.5)
    let long3 = CHHapticEvent(eventType: .hapticContinuous, parameters: [], relativeTime: 1.8, duration: 0.5)
    let short4 = CHHapticEvent(eventType: .hapticTransient, parameters: [], relativeTime: 2.4)
    let short5 = CHHapticEvent(eventType: .hapticTransient, parameters: [], relativeTime: 2.6)
    let short6 = CHHapticEvent(eventType: .hapticTransient, parameters: [], relativeTime: 2.8)

    do {
        let pattern = try CHHapticPattern(events: [short1, short2, short3, long1, long2, long3, short4, short5, short6], parameters: [])
        let player = try engine?.makePlayer(with: pattern)
        try player?.start(atTime: 0)
    } catch {
        print("Failed to play pattern: \(error.localizedDescription).")
    }
}

Notice how I’ve specified all the relativeTime parameters so they are spaced roughly correctly for the sequence I want.

Hacking with Swift is sponsored by Proxyman

SPONSORED Proxyman: A high-performance, native macOS app for developers to easily capture, inspect, and manipulate HTTP/HTTPS traffic. The ultimate tool for debugging network traffic, supporting both iOS and Android simulators and physical devices.

Start for free

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

Available from iOS 13.0

Similar solutions…

About the Swift Knowledge Base

This is part of the Swift Knowledge Base, a free, searchable collection of solutions for common iOS questions.

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI 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 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 Beyond Code

Was this page useful? Let us know!

Average rating: 4.0/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.