Skip to main content

Quick Start Examples

This page provides examples to quickly get started with the Genies NAFKit iOS SDK.

Integrated Path

This example demonstrates the progressive path (loadAvatarProgressive) which runs the silhouette preview, the multi-asset composition, animation wiring, the smart-avatar behavior, and background per-asset LOD upgrades internally — consumers only see the final, animated avatar.

import NAFKitUI

let view = try NAFView()
addSubview(view)

let avatar = try NAFAvatar(fromJSON: avatarJSON)
try await view.loadAvatar(avatar, mode: .behavior(silhouette: true), lods: [2, 0])

// Toon shader — bundled "default" preset.
view.applyDefaultToonPreset()

// Camera framing
view.frameAvatar()

Custom Path

This example shows how to use your own renderer. The SDK still owns asset loading, animation, behavior, and the per-frame skeleton math through NAFAvatarSession; you own the Metal pipeline, scene, and camera. onMeshesRebuilt fires after every composition change (load, equip/unequip, LOD upgrade, silhouette swap) — your renderer just replaces its submesh set.

import NAFKit
import NAFKitUI
import NAFKitUITools

let metalRenderer = NAFRenderer(device: nil)
let session = NAFAvatarSession(device: metalRenderer.device)

// Your renderer conforms to NAFAvatarRenderer (two methods).
let myRenderer = MyMetalRenderer(device: metalRenderer.device)

// Receive meshes when the SDK (re)builds them.
session.onMeshesRebuilt = { [weak myRenderer] meshes in
myRenderer?.applyMeshes(meshes)
}

// Gaze hooks — set once. `tick(...)` reads them per frame.
session.cameraPositionProvider = { [weak cam] in cam?.eyePosition ?? .zero }
session.avatarModelTransform = uprightTransform

// Loading — same as Integrated
let avatar = try NAFAvatar(fromJSON: avatarJSON)
try await session.loadAvatar(avatar, mode: .behavior(silhouette: true), lods: [2, 0])

// Per-frame — one call
metalRenderer.onUpdate = { [weak session, weak myRenderer] dt in
if let session, let myRenderer {
session.tick(deltaTime: Float(dt), renderer: myRenderer)
}
}

metalRenderer.onRender = { [weak myRenderer] encoder in
let frame = FrameContext(view: viewMatrix,
viewProjection: viewProjection,
aspect: aspect)
myRenderer?.render(encoder: encoder, context: frame)
}