Skip to content

iOS SDK

The Pulsar iOS SDK provides haptic feedback through three building blocks: Presets, PatternComposer, and RealtimeComposer. All are accessed through the main Pulsar class.

  • iOS 13.0+
  • Swift 5.9+

Latest available version: 1.4.0

Add Pulsar as a Swift Package dependency in Xcode:

  1. Go to File > Add Package Dependencies…
  2. Enter the repository URL
  3. Select the Pulsar library product

Or add it to your Package.swift:

dependencies: [
.package(url: "https://github.com/software-mansion-labs/pulsar-ios", from: "1.4.0")
]

Create a Pulsar instance to access all SDK features:

import Pulsar
let pulsar = Pulsar()

A collection of ready-to-use haptic patterns accessed through pulsar.getPresets().

let presets = pulsar.getPresets()
MethodDescription
afterglow()A three-beat phrase that dissolves gently, ideal for soft endings or gradually quieting feedback.
aftershock()A firm opening that settles calmly, ideal for transitions needing a strong start and a gentle finish.
alarm()Relentless and urgent, best for critical errors or emergencies that require immediate attention.
anvil()The full weight of a massive collision, conveys sheer physical force and momentum.
applause()A growing wave of appreciation, ideal for celebratory moments or social approval.

Example

let presets = pulsar.getPresets()
presets.dogBark()

Plays the platform’s platform haptic feedback presets.

MethodDescription
systemImpactHeavy()UIImpactFeedbackGenerator.heavy
systemImpactLight()UIImpactFeedbackGenerator.light
systemImpactMedium()UIImpactFeedbackGenerator.medium
systemImpactRigid()UIImpactFeedbackGenerator.rigid
systemImpactSoft()UIImpactFeedbackGenerator.soft

Example

let presets = pulsar.getPresets()
presets.systemImpactHeavy()

You can also play a preset by its string name using getByName(_:). This returns a Preset? that you call play() on.

Available names:

  • Afterglow
  • Aftershock
  • Alarm
  • Anvil
  • Applause
  • Ascent
  • BalloonPop
  • Barrage

Example

let presets = pulsar.getPresets()
// Direct call
presets.dogBark()
// By name
presets.getByName("Success")?.play()

Composes and plays custom haptic patterns. Get a new instance from pulsar.getPatternComposer(). Each call returns a fresh composer, so you can manage multiple patterns independently.

let composer = pulsar.getPatternComposer()

Parses a PatternData object and prepares it for playback.

func parsePattern(hapticsData: PatternData)

parsePatternWithSound(hapticsData:uri:volume:offset:start:duration:)

Section titled “parsePatternWithSound(hapticsData:uri:volume:offset:start:duration:)”

Parses a pattern together with a short sound played in sync with the haptics. The sound is registered as a Core Haptics audio event on the same timeline as the pattern, so audio and haptics stay sample-accurate. volume (0–1) and offset (milliseconds, shifting the audio relative to the haptics) are optional.

func parsePatternWithSound(
hapticsData: PatternData,
uri: String,
volume: Float = 1,
offset: Double = 0,
start: Double = 0,
duration: Double = 0
)

uri must resolve to a local file — an absolute path, a file:// URL, or the name of a resource bundled in the app. When no extension is given it defaults to .wav, so a bare name like "beep" resolves beep.wav from the app bundle (add the file to your target’s Copy Bundle Resources). Requires a real device — Core Haptics is unavailable on the simulator — and a missing or unregisterable file degrades gracefully to haptics-only.

start and duration (both in milliseconds) trim the clip: start is where playback begins in the source file, duration is how much of it to play (0 plays to the end). Core Haptics registers an audio resource by URL only, so when either is set the file is sliced to a temporary .caf first — the temp file is cleaned up on the next parse and on dispose.

Parses and immediately plays a pattern. Equivalent to calling parsePattern followed by play.

func playPattern(hapticsData: PatternData)

Plays the previously parsed pattern.

func play()

Stops all playback.

func stop()
let composer = pulsar.getPatternComposer()
let pattern = PatternData(
continuousPattern: ContinuousPattern(
amplitude: [
ValuePoint(time: 0, value: 0),
ValuePoint(time: 200, value: 1),
ValuePoint(time: 400, value: 0),
],
frequency: [
ValuePoint(time: 0, value: 0.3),
ValuePoint(time: 400, value: 0.8),
]
),
discretePattern: [
DiscretePoint(time: 0, amplitude: 1, frequency: 0.5),
DiscretePoint(time: 100, amplitude: 0.5, frequency: 0.5),
]
)
composer.playPattern(hapticsData: pattern)

Provides real-time haptic control with live amplitude and frequency modulation. Useful for gesture-driven or continuously evolving haptic experiences. Get the shared instance from pulsar.getRealtimeComposer().

let realtime = pulsar.getRealtimeComposer()

Updates the ongoing haptic with new amplitude and frequency values. Automatically starts playback if it is not already active. Values are clamped to the 0-1 range.

func set(amplitude: Float, frequency: Float)

Plays a single discrete haptic event.

func playDiscrete(amplitude: Float = 1.0, frequency: Float = 0.5)

Stops the active continuous haptic.

func stop()

Returns true if a continuous haptic is currently playing.

var isActive: Bool { get }
let realtime = pulsar.getRealtimeComposer()
// Start a continuous haptic
realtime.set(amplitude: 0.5, frequency: 0.8)
// Update parameters over time
realtime.set(amplitude: 1.0, frequency: 0.3)
// Play a one-off discrete event
realtime.playDiscrete(amplitude: 0.7, frequency: 0.5)
// Stop
realtime.stop()

A .pulsar bundle exported from Pulsar Studio holds your own presets — each a haptic pattern with optional synced audio and animation. pulsar-gen turns a bundle into a typed accessor, so every preset is a real member: a renamed preset becomes a compile error rather than a silent runtime miss.

Add the .pulsar to your app target (Copy Bundle Resources), then either apply the SwiftPM build plugin so the accessor regenerates on every build:

.target(
name: "MyApp",
plugins: [.plugin(name: "PulsarGenPlugin", package: "Pulsar")]
)

…or run the CLI once and commit the result — the practical option for an Xcode app target:

Terminal window
npx @swmansion/pulsar-gen acme-pack.pulsar --target swift --out Sources/MyApp/
let pulsar = Pulsar()
let bundle = try pulsar.loadBundle(AcmePack.descriptor, strict: true)
bundle.heartbeatV2.play()
bundle.explosion.stop()

The descriptor finds the .pulsar in the app’s main bundle. strict: true checks the shipped bundle against the generated types, failing loudly on a stale copy.

Each PresetHandle exposes id, duration, play(), stop(), and animation — the Lottie bytes and timing for your own animation view. Pulsar carries and time-aligns the animation; the app renders it:

if let animation = bundle.heartbeatV2.animation {
myLottieView.load(data: animation.data)
}

Audio authored into a preset is registered as a Core Haptics audio event, so it shares the engine clock with the haptics.

For an id only known at runtime, bundle.get(id) returns a handle or nil. Call bundle.dispose() to release the native patterns when you are done.

See iOS/PulsarApp for a working screen.


Configuration methods available directly on the Pulsar instance.

MethodDescription
enableHaptics(state: Bool)Enable or disable all haptic feedback
enableSound(state: Bool)Enable or disable audio simulation
enableCache(state: Bool)Enable or disable preset caching
clearCache()Clear the preset cache
preloadPresets(presetNames: [String])Preload presets by name for faster playback
stopHaptics()Stop all currently playing haptics
shutDownEngine()Shut down the haptic engine
isHapticsSupported() -> BoolCheck if the device supports haptics
isHapticsEnabled: BoolReturns whether haptic feedback is currently enabled
let pulsar = Pulsar()
// Preload frequently used presets
pulsar.preloadPresets(presetNames: ["Earthquake", "Success"])
// Disable haptics temporarily
pulsar.enableHaptics(state: false)
// Check device support
if pulsar.isHapticsSupported() {
pulsar.getPresets().success()
}

Describes a complete haptic pattern with discrete pulses and continuous envelope curves.

class PatternData: NSObject, Codable {
let continuousPattern: ContinuousPattern
let discretePattern: [DiscretePoint]
init(
continuousPattern: ContinuousPattern,
discretePattern: [DiscretePoint]
)
}

Represents continuous haptic curves for amplitude and frequency.

class ContinuousPattern: NSObject, Codable {
let amplitude: [ValuePoint]
let frequency: [ValuePoint]
init(amplitude: [ValuePoint], frequency: [ValuePoint])
}

A single point in a continuous curve.

class ValuePoint: NSObject, Codable {
let time: Double // Milliseconds from pattern start
let value: Float // Normalized value (0-1)
init(time: Double, value: Float)
}

A single discrete haptic event.

class DiscretePoint: NSObject, Codable {
let time: Double // Milliseconds from pattern start
let amplitude: Float // Intensity (0-1)
let frequency: Float // Sharpness (0-1)
init(time: Double, amplitude: Float, frequency: Float)
}

Use discretePattern for distinct taps and impacts. Use continuousPattern envelopes to shape a sustained haptic over time.

The protocol that all preset implementations conform to.

protocol Preset {
func play()
static func getInstance(haptics: Pulsar) -> Preset
static var name: String { get }
}