Skip to content

iOS

PulsarLottie plays a Pulsar haptic pattern locked to a Lottie animation on iOS: HapticLottieView for SwiftUI, and HapticLottieController for a LottieAnimationView you already own.

Read the Lottie SDK overview for the engine modes, timing rules and simulator notes this page assumes.

Latest available version: 0.1.0

Add the package in Xcode via File > Add Package Dependencies… and depend on the PulsarLottie library product, or declare it in Package.swift:

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

With CocoaPods:

pod 'PulsarLottie', '~> 0.1.0'

Both PulsarHaptics and lottie-ios come along as dependencies — you do not need to add them separately, though nothing stops you from pinning them yourself.


A SwiftUI view that renders a Lottie animation and plays a pattern locked to its timeline.

import Pulsar
import PulsarLottie
let pattern = PatternData(
continuousPattern: ContinuousPattern(
amplitude: [ValuePoint(time: 0, value: 0), ValuePoint(time: 800, value: 1)],
frequency: [ValuePoint(time: 0, value: 0.3)]
),
discretePattern: [DiscretePoint(time: 0, amplitude: 1, frequency: 0.5)]
)
HapticLottieView("success", haptics: pattern)
.frame(width: 200, height: 200)

A preset in a .pulsar bundle already pairs an animation with the pattern its author aligned to it. Pass the preset and the view takes both — no name, no haptics:

let pack = try pulsar.loadBundleSync(AcmePack.descriptor)
HapticLottieView(preset: pack.celebration)
.frame(width: 200, height: 200)

The preset supplies the animation, haptics from its pattern, and durationMs from its authored length — each still overridable on its own. One that carries audio plays that audio too, which is why it starts in .pattern mode. See bundle presets.

ParameterTypeDefaultDescription
nameStringLottie animation to load, passed positionally. Replaced by preset: in the second initializer.
bundleBundle.mainBundle the animation is loaded from.
presetPresetHandle?nilBundle preset supplying the animation, pattern and duration.
hapticsPatternData?nilPattern to sync. Overrides the preset’s pattern; nil with no preset renders a plain animation.
hapticModeHapticMode?derived.realtime, or .pattern for a preset carrying audio.
hapticOffsetDouble0Shift the haptics by ±ms relative to the animation.
hapticsEnabledBooltrueTurn haptics off without touching the animation.
durationMsDouble?derivedClock length in ms. Overrides every derived duration.
autoPlayBooltruePlay as soon as the view is created.
loopModeLottieLoopMode.playOnceLoop behaviour of the underlying LottieAnimationView.

The view owns a Pulsar instance and a HapticLottieController for its lifetime, and sets contentMode to .scaleAspectFit.


Attaches haptics to a LottieAnimationView you already have, and becomes the transport for both. It binds haptics only — the view keeps whatever animation you gave it.

let controller = PulsarLottie.bind(animationView, pulsar: pulsar, preset: pack.celebration)
controller.play()
controller.setTimestamp(1200) // seek both animation and haptics to 1.2s
controller.pause()

bind is a convenience over the initializer, which takes the same arguments as the view — preset, haptics, hapticMode, hapticOffset, hapticsEnabled, durationMs — plus animationView and pulsar.

MethodDescription
play()Rewind to the start and play animation and haptics together.
pause()Pause both. In .pattern mode the haptic stops rather than suspending.
resume()Resume from the current position.
stop() / reset()Stop and rewind to the start.
setTimestamp(_ ms: Double)Seek both to ms from the start.
setLoop(_ loop: Bool, count: Int?, reverse: Bool)Loop the animation. count limits iterations (nil loops forever); reverse plays a boomerang.

setTimestamp(_:) clamps into 0 ... duration and repositions the discrete-event window, so seeking backwards re-fires the events you seek past. In .pattern mode it moves the animation only — a buffered pattern cannot be repositioned mid-flight.

The controller installs a CADisplayLink while playing and invalidates it on deinit, so keep a strong reference to it for as long as the view is on screen.


PresetHandle carries everything the view reads, so you can drive your own LottieAnimationView:

if let animation = preset.animation {
let animationView = LottieAnimationView(animation: try? LottieAnimation.from(data: animation.data))
let controller = PulsarLottie.bind(animationView, pulsar: pulsar, preset: preset)
}
MemberTypeDescription
id / nameStringCode-safe id, and the human label it was authored under.
durationDoubleAuthored length in ms, or 0 when the bundle carries no hint.
patternPatternDataThe authored pattern — what .realtime samples.
animationBundleAnimation?Lottie data, frameRate and totalFrames.
hasAudio / hasAnimationBoolWhat the preset was authored with.
play() / stop()Play the preset natively: haptics, plus its synced audio.

A preset with no animation renders nothing through HapticLottieView(preset:) — check hasAnimation, or use the name: initializer and pass the preset alongside it. See the iOS SDK page for how bundles are loaded.