An easy-to-use domain-specific language to build haptic patterns from Core Haptics.
The Core Haptics framework allows you to "Compose and play haptic patterns to customize your iOS app’s haptic feedback.". These patterns consist of sequences of haptic events. The HapticsDSL framework allows the easy of these patterns. For example:
let pattern = HapticPattern(delayBetweenEvents: 0.1) {
TransientEvent()
ContinuousEvent(duration: 1)
TransientEvent()
}
// ...
let coreHapticsPattern = try CHHapticPattern(fromPatternSequence: [pattern])
// play the pattern...This pattern is made of a very quick vibration, followed by one that lasts 1 second, followed by another very quick vibration.
There’s also a specified delay of 0.1 s between each vibration.
To learn how to play a CHHapticPattern visit the Core Haptics documentation.
The Haptic Events are defined by the HapticEvent class. They consist of an event type, which can be nil in case of a delay event; a duration and a specificTime to be played, which can be nil in case of an event that should be played after the previous one.
There are three main event types:
- Transient Event: This is a very short vibration, which takes 0.1 s and uses the
.hapticTransienttype. - Continuous Event: This is a variable duration vibration, which uses the
.hapticContinuoustype. - Delay Event: This is a way to specify that the next event should wait before it starts playing.
Every event can be played at an specific time. To express this intention, the startingExactlyAt parameter should be used.
Important: the events that follow the declaration of an event that starts at a specific time do not play out after them. They are played after the last sequential event finishes.
HapticPattern(delayBetweenEvents: 0.1) {
ContinuousEvent(duration: 1)
TransientEvent(startingExactlyAt: 5)
ContinuousEvent(duration: 1)
}This pattern will play out as follows: a continuous event that will play immediately and last for 1 second, a delay of 0.1 s, a continuous event that will play from 1.1 s for 1 second, a delay until the 5 s mark and then a transient event.
If you want the last continuous event to play after the transient event, you should declare a new pattern and set it to start at the 5 second mark instead. This way you would make use of the default sequential behavior as usual.
The three event types discussed previously are subclasses of the HapticEvent class. You can make your own subclass if you wish.
This framework comes with a helper module called HapticsDSLUI. It consists of a SwiftUI View that is capable of showing a timeline of the pattern.
import SwiftUI
// ...
let (hapticEvents, lastEventTime) = pattern.generateHapticEventsTimeline(startingAt: 0)
HapticsTimelineView(hapticEvents: hapticEvents,
timelineEnd: lastEventTime,
currentTime: $currentTime)The pattern DSL supports control flow statements, which enable you to write really expressive patterns. Below are some examples:
HapticPattern(delayBetweenEvents: 0.1) {
TransientEvent()
if condition {
ContinuousEvent(duration: 1.5)
} else {
HapticDelay(duration: 1.5)
}
for i in 0...3 {
TransientEvent()
HapticDelay(duration: Double(i) * 0.1)
}
if anotherCondition {
ContinuousEvent(duration: 2, startingExactlyAt: 5)
}
}- Write more individual class, method and property documentations
- Write more tests for the result builder
- Make the structure more general in order to be able to build patterns with nested patterns or pattern sequences
This framework is fully developed using the Swift programming language using Xcode and is compatible with iOS 15 or later.
It makes use of the Result Builders feature, which enables us to define new, declarative syntaxes inside the language.
The patterns generated are intended to be used with the Core Haptics framework.
The testing was made using the XCTest framework on Xcode.
The module responsible for the UI (that helps visualize the patterns) was developed using the SwiftUI framework, a declarative UI framework built on top of those same Result Builders in Swift.
The framework was built for distribution using a Swift Package.