# How Lottie Animation is Integrated in Telegram-iOS: A Technical Deep Dive

> Explore the Telegram-iOS integration of Lottie animations for UI and stickers. Discover the dual-path architecture, resource pipeline, and caching mechanisms in this technical deep dive.

- Repository: [TelegramMessenger/Telegram-iOS](https://github.com/TelegramMessenger/Telegram-iOS)
- Tags: deep-dive
- Published: 2026-04-07

---

**Telegram-iOS integrates Lottie animations through a dual-path architecture that uses `LottieComponent` and `LottieAnimationComponent` for UI elements, and `LottieMetalAnimatedStickerNode` for Metal-accelerated stickers, sharing a common resource pipeline that handles `.tgs` decompression via `TGGUnzipData` and caching through `AnimationCacheState`.**

The Telegram Messenger iOS app leverages Lottie vector animations to power everything from animated stickers to loading spinners and transition effects. According to the source code in the `TelegramMessenger/Telegram-iOS` repository, the implementation splits into two specialized tracks: a **ComponentFlow-based UI system** for interface animations and a **Metal-accelerated rendering pipeline** for stickers. Both systems rely on the same underlying resource management, decompression utilities, and caching strategies to deliver smooth 60 fps performance across the supported iOS device range.

## Architecture Overview

The Lottie animation integration in Telegram-iOS follows a layered architecture with distinct entry points for different use cases:

- **UI Components**: `LottieComponent` and `LottieAnimationComponent` wrap Lottie animations into the app's component system, handling bundle resources, remote files, and playback modes
- **Animated Stickers**: `LottieMetalAnimatedStickerNode` provides Metal-accelerated rendering for `.tgs` sticker files with pre-serialized render tree caching
- **Shared Pipeline**: Both paths use `TGGUnzipData` for `.tgs` decompression, `AnimationCacheState` for Metal render caching, and `LottieComponent.ResourceContent` for media box integration

This separation allows the UI team to add animations declaratively while the stickers team optimizes for battery efficiency through custom Metal shaders.

## UI Animation Components

### LottieComponent.swift

The `LottieComponent` class in [`submodules/TelegramUI/Components/LottieComponent/Sources/LottieComponent.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/submodules/TelegramUI/Components/LottieComponent/Sources/LottieComponent.swift) serves as the high-level wrapper for adding Lottie animations anywhere in the UI hierarchy. It abstracts file loading, decompression, and playback control through a declarative API.

The component supports multiple content sources via the `Content` protocol:

- `AppBundleContent`: Loads JSON or `.tgs` files directly from the app bundle
- `ResourceContent`: Streams animation data from `TelegramMediaFile` objects via the media box

Key configuration options include `StartingPosition` (begin, end, or fractional frame), looping behavior, and color overlays:

```swift
public final class LottieComponent: Component {
    public enum StartingPosition: Equatable {
        case begin
        case end
        case fraction(Double)
    }

    public init(
        content: Content,
        color: UIColor? = nil,
        placeholderColor: UIColor? = nil,
        startingPosition: StartingPosition = .end,
        size: CGSize? = nil,
        loop: Bool = false,
        playOnce: ActionSlot<Void>? = nil
    ) { … }
}

```

### LottieAnimationComponent.swift

For finer control within the ComponentFlow system, `LottieAnimationComponent` in [`submodules/Components/LottieAnimationComponent/Sources/LottieAnimationComponent.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/submodules/Components/LottieAnimationComponent/Sources/LottieAnimationComponent.swift) embeds a native Lottie `AnimationView` with support for tag-based matching and per-element color overrides.

The `AnimationItem` struct defines playback behavior through its `Mode` enum:

```swift
public struct AnimationItem: Equatable {
    public enum StillPosition { case begin, end }
    public enum Mode: Equatable {
        case still(position: StillPosition)
        case animating(loop: Bool)
        case animateTransitionFromPrevious
    }

    public var name: String
    public var mode: Mode
    public var range: (CGFloat, CGFloat)?
    public var speed: CGFloat = 1.0
    public var waitForCompletion: Bool = true
}

```

The `View.update(component:availableSize:transition:)` method handles view reuse logic, loading animations either from bundle paths or by decompressing `.tgs` data on the fly:

```swift
if let url = getAppBundle().url(forResource: component.animation.name, withExtension: "json"),
   let maybeAnimation = Animation.filepath(url.path) {
    animation = maybeAnimation
} else if let url = getAppBundle().url(forResource: component.animation.name,
                                      withExtension: "tgs"),
          let data = try? Data(contentsOf: URL(fileURLWithPath: url.path)),
          let unpackedData = TGGUnzipData(data, 5 * 1024 * 1024) {
    animation = try? Animation.from(data: unpackedData, strategy: .codable)
}

```

### Practical Example: Chat Input Typing Indicator

To add a looping typing animation to a chat input field using the component system:

```swift
let typingComponent = LottieAnimationComponent(
    animation: .init(name: "input_typing", mode: .animating(loop: true)),
    colors: [:],
    tag: nil,
    size: CGSize(width: 24, height: 24)
)

// Integration in component tree
let typingNode = AnyComponent(typingComponent)

```

## Animated Stickers and Metal Rendering

### Metal-Accelerated Sticker Nodes

For animated stickers, Telegram-iOS implements `LottieMetalAnimatedStickerNode` in [`submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift). This class uses a custom Metal pipeline (`LottieCpp` with custom shaders) rather than the standard Lottie iOS library to achieve hardware-accelerated rendering.

The node conforms to the `AnimatedStickerNode` protocol and handles setup through:

```swift
public final class LottieMetalAnimatedStickerNode: ASDisplayNode, AnimatedStickerNode {
    public func setup(
        source: AnimatedStickerNodeSource,
        width: Int,
        height: Int,
        playbackMode: AnimatedStickerPlaybackMode,
        mode: AnimatedStickerMode
    ) {
        // Resolves source to file path
        // Requests cached binary from AnimationCacheState
        // Decodes via LottieAnimationContainer
        // Creates Metal render pipelines
        // Drives frames via display link
    }
}

```

### Resource Loading and Caching

The Metal implementation uses `AnimationCacheState` to serialize render trees into binary buffers. When `cacheLottieMetalAnimation(path:)` is called, the system:

1. Reads the `.tgs` file and decompresses via `TGGUnzipData`
2. Builds a `LottieAnimationContainer` from the JSON
3. Serializes each frame's render tree
4. Queues the task (max 2 concurrent) and writes to disk for subsequent fast loads

This pre-serialization eliminates JSON parsing overhead during playback, enabling smooth 60 fps animation even on older devices.

### Fallback Rendering

When Metal is unavailable, the system falls back to `AnimatedStickerNode` (CPU-based RLottie rendering) located in the `AnimatedStickerNode` framework submodule. Both implementations share the same resource handling pipeline but differ in their rendering backends.

## Loading Animations from the Media Box

For custom sticker packs and remote animations, the repository provides `LottieComponent.ResourceContent` in [`submodules/TelegramUI/Components/LottieComponentResourceContent/Sources/LottieComponentResourceContent.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/submodules/TelegramUI/Components/LottieComponentResourceContent/Sources/LottieComponentResourceContent.swift). This class bridges the gap between Telegram's media storage system and the Lottie rendering pipeline.

The `load(_:)` method implements a sophisticated loading strategy:

```swift
public final class ResourceContent: LottieComponent.Content {
    private let context: AccountContext
    private let file: TelegramMediaFile
    
    override public func load(_ f: @escaping (LottieComponent.ContentData) -> Void) -> Disposable {
        // Attempts synchronous disk read if file exists
        // Subscribes to mediaBox.resourceData for download progress
        // Decompresses via TGGUnzipData upon completion
        // Returns animation data or placeholder
    }
}

```

**Usage example for a sticker from the media box:**

```swift
let resource = LottieComponent.ResourceContent(
    context: accountContext,
    file: stickerFile,
    attemptSynchronously: true,
    providesPlaceholder: true
)

let stickerComponent = LottieComponent(
    content: resource,
    size: CGSize(width: 80, height: 80),
    loop: false
)

```

The component automatically displays a placeholder while fetching the animation from Telegram's servers, then transitions to the decompressed Lottie playback once the `.tgs` data arrives.

## Key Implementation Files

| File | Purpose | Location |
|------|---------|----------|
| [`LottieComponent.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/LottieComponent.swift) | High-level UI component with bundle and remote loading | `submodules/TelegramUI/Components/LottieComponent/Sources/` |
| [`LottieAnimationComponent.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/LottieAnimationComponent.swift) | ComponentFlow wrapper exposing playback modes | `submodules/Components/LottieAnimationComponent/Sources/` |
| [`LottieComponentResourceContent.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/LottieComponentResourceContent.swift) | Media box integration for `TelegramMediaFile` | `submodules/TelegramUI/Components/LottieComponentResourceContent/Sources/` |
| [`LottieMetalAnimatedStickerNode.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/LottieMetalAnimatedStickerNode.swift) | Metal-accelerated sticker rendering | `submodules/TelegramUI/Components/LottieMetal/Sources/` |
| [`AnimatedStickerNode.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/AnimatedStickerNode.swift) | Fallback CPU-based RLottie implementation | `AnimatedStickerNode` submodule |
| `AnimationCacheState` | Render tree serialization and caching | Embedded within [`LottieMetalAnimatedStickerNode.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/LottieMetalAnimatedStickerNode.swift) |

## Summary

- **Telegram-iOS uses a dual-path architecture** where `LottieComponent` and `LottieAnimationComponent` handle UI animations, while `LottieMetalAnimatedStickerNode` manages sticker rendering through custom Metal shaders.
- **Both systems share decompression logic** via `TGGUnzipData` for `.tgs` files and leverage `AnimationCacheState` to cache pre-serialized render trees for optimal performance.
- **Resource loading adapts to source type**: bundle files load synchronously while `TelegramMediaFile` objects stream through the media box with placeholder support.
- **The Metal implementation prioritizes 60 fps performance** by serializing animation frames into binary buffers and limiting concurrent processing to 2 tasks.

## Frequently Asked Questions

### What is the difference between LottieComponent and LottieAnimationComponent in Telegram-iOS?

**`LottieComponent`** provides a higher-level abstraction with built-in support for placeholders, color overlays, and content sources from both app bundles and remote media files. **`LottieAnimationComponent`** offers finer-grained control within the ComponentFlow system, exposing direct access to Lottie's `AnimationView`, tag-based view matching, and specific frame ranges. Use `LottieComponent` for standard UI animations and `LottieAnimationComponent` when you need precise playback control or integration with complex component trees.

### How does Telegram-iOS handle the .tgs file format for stickers?

The app treats `.tgs` files as gzipped JSON payloads. When loading a sticker, the code calls `TGGUnzipData(data, 5 * 1024 * 1024)` to decompress the file (with a 5MB limit), then parses the resulting JSON through `Animation.from(data:strategy:)` or `LottieAnimationContainer` depending on whether the Metal or CPU rendering path is active. The decompressed data feeds into the respective animation pipelines for frame generation.

### Why does Telegram use Metal for animated stickers instead of standard Lottie?

The `LottieMetalAnimatedStickerNode` implementation uses **custom Metal shaders** and the `LottieCpp` framework to pre-serialize render trees into binary cache files. This approach eliminates JSON parsing during playback and enables GPU-accelerated rendering, achieving consistent 60 fps performance even on older iOS devices. The standard Lottie iOS library runs on the CPU, which consumes more battery and performs poorly with complex vector animations at sticker sizes.

### How can I load a Lottie animation from a TelegramMediaFile in my own component?

Import `LottieComponentResourceContent` and instantiate `ResourceContent` with your `AccountContext` and `TelegramMediaFile`. Pass this as the `content` parameter when initializing `LottieComponent`. Set `attemptSynchronously: true` for instant display if cached, and `providesPlaceholder: true` to show a temporary view while the file downloads from Telegram's servers. The component handles the rest, including decompression and playback setup.