Documentation Index
Fetch the complete documentation index at: https://mintlify.com/rive-app/rive-ios/llms.txt
Use this file to discover all available pages before exploring further.
Rive provides full UIKit support through RiveViewModel and RiveView. This guide covers integration patterns for UIKit apps, including Storyboard and programmatic approaches.
Quick Start
The fastest way to add a Rive animation to UIKit is using RiveViewModel:
import UIKit
import RiveRuntime
class SimpleAnimationViewController: UIViewController {
var viewModel = RiveViewModel(fileName: "truck")
override func viewWillAppear(_ animated: Bool) {
let riveView = viewModel.createRiveView()
view.addSubview(riveView)
riveView.frame = view.frame
}
}
Integration Approaches
Create and configure views entirely in code:import UIKit
import RiveRuntime
class AnimationViewController: UIViewController {
private var viewModel: RiveViewModel!
private var riveView: RiveView!
override func viewDidLoad() {
super.viewDidLoad()
setupRiveAnimation()
}
private func setupRiveAnimation() {
viewModel = RiveViewModel(fileName: "halloween")
riveView = viewModel.createRiveView()
view.addSubview(riveView)
// Setup constraints
riveView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
riveView.topAnchor.constraint(equalTo: view.topAnchor),
riveView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
riveView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
riveView.heightAnchor.constraint(equalToConstant: 300)
])
}
}
Connect a RiveView from Interface Builder:1. Add a UIView to your Storyboard2. Set the custom class to RiveView:
- Select the view in Interface Builder
- Open the Identity Inspector
- Set Class to
RiveView
- Set Module to
RiveRuntime
3. Create an IBOutlet and configure:import UIKit
import RiveRuntime
class LayoutViewController: UIViewController {
@IBOutlet weak var riveView: RiveView!
var viewModel = RiveViewModel(fileName: "truck_v7")
override func viewDidLoad() {
super.viewDidLoad()
viewModel.setView(riveView)
}
}
Controlling Animations
Playback Controls
class PlaybackViewController: UIViewController {
var viewModel = RiveViewModel(fileName: "animation")
var riveView: RiveView!
override func viewDidLoad() {
super.viewDidLoad()
riveView = viewModel.createRiveView()
view.addSubview(riveView)
riveView.frame = view.frame
}
@IBAction func playTapped(_ sender: UIButton) {
viewModel.play()
}
@IBAction func pauseTapped(_ sender: UIButton) {
viewModel.pause()
}
@IBAction func stopTapped(_ sender: UIButton) {
viewModel.stop()
}
@IBAction func resetTapped(_ sender: UIButton) {
viewModel.reset()
}
}
State Machine Interactions
class StateMachineViewController: UIViewController {
var viewModel = RiveViewModel(
fileName: "skills",
stateMachineName: "Designer's Test"
)
override func viewDidLoad() {
super.viewDidLoad()
let riveView = viewModel.createRiveView()
view.addSubview(riveView)
riveView.frame = view.frame
}
@IBAction func beginnerButtonTapped(_ sender: UIButton) {
viewModel.setInput("Level", value: 0.0)
}
@IBAction func intermediateButtonTapped(_ sender: UIButton) {
viewModel.setInput("Level", value: 1.0)
}
@IBAction func expertButtonTapped(_ sender: UIButton) {
viewModel.setInput("Level", value: 2.0)
}
}
Layout Configuration
Fit and Alignment
class LayoutViewController: UIViewController {
@IBOutlet weak var riveView: RiveView!
var viewModel = RiveViewModel(fileName: "truck_v7")
override func viewDidLoad() {
super.viewDidLoad()
viewModel.setView(riveView)
}
@IBAction func fitButtonTriggered(_ sender: UIButton) {
var fit: RiveFit = .contain
switch sender.currentTitle {
case "Fill": fit = .fill
case "Contain": fit = .contain
case "Cover": fit = .cover
case "Fit Width": fit = .fitWidth
case "Fit Height": fit = .fitHeight
case "Scale Down": fit = .scaleDown
case "None": fit = .noFit
default: fit = .contain
}
viewModel.fit = fit
}
@IBAction func alignmentButtonTriggered(_ sender: UIButton) {
var alignment: RiveAlignment = .center
switch sender.currentTitle {
case "Top Left": alignment = .topLeft
case "Top Center": alignment = .topCenter
case "Top Right": alignment = .topRight
case "Center Left": alignment = .centerLeft
case "Center": alignment = .center
case "Center Right": alignment = .centerRight
case "Bottom Left": alignment = .bottomLeft
case "Bottom Center": alignment = .bottomCenter
case "Bottom Right": alignment = .bottomRight
default: alignment = .center
}
viewModel.alignment = alignment
}
}
Available Options
Fit Modes:
.contain - Fit within bounds while maintaining aspect ratio
.fill - Fill the entire view
.cover - Cover the view while maintaining aspect ratio
.fitWidth - Match the view’s width
.fitHeight - Match the view’s height
.scaleDown - Only scale down if larger
.layout - Resize artboard to view size
.noFit - No scaling
Alignment:
.topLeft, .topCenter, .topRight
.centerLeft, .center, .centerRight
.bottomLeft, .bottomCenter, .bottomRight
Loading from URL
class HttpAnimationViewController: UIViewController {
var viewModel = RiveViewModel(
webURL: "https://cdn.rive.app/animations/truck.riv"
)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let riveView = viewModel.createRiveView()
view.addSubview(riveView)
riveView.frame = view.frame
}
}
Working with RiveView Directly
For advanced use cases, you can work with RiveView directly without a view model:
import UIKit
import RiveRuntime
class DirectRiveViewController: UIViewController {
private var riveView: RiveView!
override func viewDidLoad() {
super.viewDidLoad()
do {
// Create model
let model = try RiveModel(fileName: "truck")
// Create and configure view
riveView = RiveView(model: model, autoPlay: true)
riveView.fit = .contain
riveView.alignment = .center
view.addSubview(riveView)
riveView.frame = view.bounds
riveView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
} catch {
print("Failed to load Rive file: \(error)")
}
}
}
viewModel.triggerInput("myTrigger")
viewModel.setInput("isEnabled", value: true)
viewModel.setInput("isVisible", value: false)
viewModel.setInput("speed", value: 2.5)
viewModel.setInput("progress", value: 0.75)
// Trigger in nested artboard
viewModel.triggerInput("action", path: "NestedArtboard")
// Boolean in nested artboard
viewModel.setInput("enabled", value: true, path: "Level1/Level2")
// Number in nested artboard
viewModel.setInput("value", value: 50.0, path: "NestedArtboard")
Lifecycle Management
Proper Cleanup
class AnimationViewController: UIViewController {
var viewModel: RiveViewModel?
var riveView: RiveView?
override func viewDidLoad() {
super.viewDidLoad()
viewModel = RiveViewModel(fileName: "animation")
riveView = viewModel?.createRiveView()
if let riveView = riveView {
view.addSubview(riveView)
riveView.frame = view.frame
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
viewModel?.pause()
}
deinit {
viewModel?.stop()
viewModel?.deregisterView()
}
}
Frame Rate Control
override func viewDidLoad() {
super.viewDidLoad()
let riveView = viewModel.createRiveView()
view.addSubview(riveView)
// Set preferred FPS
viewModel.setPreferredFramesPerSecond(preferredFramesPerSecond: 30)
}
Memory Management
class EfficientViewController: UIViewController {
var viewModel: RiveViewModel?
override func viewDidLoad() {
super.viewDidLoad()
// Create view model only when needed
viewModel = RiveViewModel(fileName: "animation", autoPlay: false)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
viewModel?.play()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
viewModel?.pause()
}
}
Best Practices
- Use RiveViewModel - Prefer
RiveViewModel over direct RiveView manipulation for easier state management
- Frame Management - Set frames in
viewWillAppear or use Auto Layout constraints
- Pause When Hidden - Pause animations in
viewWillDisappear to save battery
- Cleanup Resources - Stop animations and deregister views in
deinit
- Handle Errors - Wrap
RiveModel initialization in do-catch blocks
- Reuse View Models - Create view models once and reuse them
Next Steps